use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SectionRef {
pub number: u8,
pub offset: usize,
pub length: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldSections {
pub identification: SectionRef,
pub local_use: Option<SectionRef>,
pub grid: SectionRef,
pub product: SectionRef,
pub data_representation: SectionRef,
pub bitmap: Option<SectionRef>,
pub data: SectionRef,
}
pub fn scan_sections(msg_bytes: &[u8]) -> Result<Vec<SectionRef>> {
if msg_bytes.len() < 20 {
return Err(Error::InvalidMessage(format!(
"GRIB2 message too short: {} bytes",
msg_bytes.len()
)));
}
let mut sections = Vec::new();
let mut pos = 16;
while pos < msg_bytes.len() {
if pos + 4 <= msg_bytes.len() && &msg_bytes[pos..pos + 4] == b"7777" {
if pos != msg_bytes.len() - 4 {
return Err(Error::InvalidMessage(
"end section 7777 encountered before message end".into(),
));
}
sections.push(SectionRef {
number: 8,
offset: pos,
length: 4,
});
return Ok(sections);
}
if pos + 5 > msg_bytes.len() {
return Err(Error::Truncated { offset: pos as u64 });
}
let length = u32::from_be_bytes(msg_bytes[pos..pos + 4].try_into().unwrap()) as usize;
let number = msg_bytes[pos + 4];
if length < 5 {
return Err(Error::InvalidSection {
section: number,
reason: format!("section length {length} is smaller than the 5-byte header"),
});
}
let end = pos
.checked_add(length)
.filter(|end| *end <= msg_bytes.len())
.ok_or(Error::Truncated { offset: pos as u64 })?;
sections.push(SectionRef {
number,
offset: pos,
length,
});
pos = end;
}
Err(Error::InvalidMessage("missing end section 7777".into()))
}
pub fn index_fields(msg_bytes: &[u8]) -> Result<Vec<FieldSections>> {
let sections = scan_sections(msg_bytes)?;
let identification = sections
.iter()
.copied()
.find(|section| section.number == 1)
.ok_or_else(|| Error::InvalidSectionOrder("missing identification section".into()))?;
let mut fields = Vec::new();
let mut current_local_use = None;
let mut current_grid = None;
let mut current_product = None;
let mut current_representation = None;
let mut current_bitmap = None;
let mut last_defined_bitmap = None;
for section in sections {
match section.number {
1 => {}
2 => {
if current_product.is_some() || current_representation.is_some() {
return Err(Error::InvalidSectionOrder(
"local use section encountered inside a field definition".into(),
));
}
current_local_use = Some(section);
}
3 => {
current_grid = Some(section);
current_product = None;
current_representation = None;
current_bitmap = None;
}
4 => {
if current_grid.is_none() {
return Err(Error::InvalidSectionOrder(
"product definition encountered before grid definition".into(),
));
}
current_product = Some(section);
current_representation = None;
current_bitmap = None;
}
5 => {
if current_product.is_none() {
return Err(Error::InvalidSectionOrder(
"data representation encountered before product definition".into(),
));
}
current_representation = Some(section);
current_bitmap = None;
}
6 => {
if current_representation.is_none() {
return Err(Error::InvalidSectionOrder(
"bitmap encountered before data representation".into(),
));
}
let bitmap_indicator =
*msg_bytes
.get(section.offset + 5)
.ok_or(Error::InvalidSection {
section: 6,
reason: "bitmap section is missing its indicator".into(),
})?;
match bitmap_indicator {
0 => {
current_bitmap = Some(section);
last_defined_bitmap = Some(section);
}
254 => {
current_bitmap = Some(last_defined_bitmap.ok_or(Error::MissingBitmap)?);
}
255 => current_bitmap = None,
indicator => {
return Err(Error::UnsupportedBitmapIndicator(u16::from(indicator)));
}
}
}
7 => {
let grid = current_grid.ok_or_else(|| {
Error::InvalidSectionOrder(
"data section encountered before grid definition".into(),
)
})?;
let product = current_product.ok_or_else(|| {
Error::InvalidSectionOrder(
"data section encountered before product definition".into(),
)
})?;
let data_representation = current_representation.ok_or_else(|| {
Error::InvalidSectionOrder(
"data section encountered before data representation".into(),
)
})?;
fields.push(FieldSections {
identification,
local_use: current_local_use,
grid,
product,
data_representation,
bitmap: current_bitmap,
data: section,
});
current_product = None;
current_representation = None;
current_bitmap = None;
}
8 => break,
other => {
return Err(Error::InvalidSection {
section: other,
reason: "unexpected section number".into(),
});
}
}
}
if fields.is_empty() {
return Err(Error::InvalidSectionOrder(
"message did not contain a complete field".into(),
));
}
Ok(fields)
}
#[cfg(test)]
mod tests {
use crate::error::Error;
use super::{index_fields, scan_sections};
fn section(number: u8, payload_len: usize) -> Vec<u8> {
let len = (payload_len + 5) as u32;
let mut bytes = len.to_be_bytes().to_vec();
bytes.push(number);
bytes.resize(len as usize, 0);
bytes
}
fn bitmap_section(indicator: u8, bitmap: &[u8]) -> Vec<u8> {
let length = 6 + bitmap.len();
let mut bytes = (length as u32).to_be_bytes().to_vec();
bytes.push(6);
bytes.push(indicator);
bytes.extend_from_slice(bitmap);
bytes
}
#[test]
fn scan_minimal_sections() {
let mut data = vec![0u8; 16];
data.extend_from_slice(§ion(1, 16));
data.extend_from_slice(§ion(3, 20));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(b"7777");
let sections = scan_sections(&data).unwrap();
assert_eq!(sections.len(), 6);
assert_eq!(sections[0].number, 1);
assert_eq!(sections[4].number, 7);
assert_eq!(sections[5].number, 8);
}
#[test]
fn indexes_repeated_fields() {
let mut data = vec![0u8; 16];
data.extend_from_slice(§ion(1, 16));
data.extend_from_slice(§ion(3, 20));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(b"7777");
let fields = index_fields(&data).unwrap();
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].grid.number, 3);
assert_eq!(fields[0].local_use, None);
assert_eq!(fields[1].product.number, 4);
}
#[test]
fn associates_local_use_section_with_each_following_field() {
let mut data = vec![0u8; 16];
data.extend_from_slice(§ion(1, 16));
data.extend_from_slice(§ion(2, 3));
data.extend_from_slice(§ion(3, 20));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(b"7777");
let fields = index_fields(&data).unwrap();
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].local_use.unwrap().number, 2);
assert_eq!(fields[1].local_use, fields[0].local_use);
}
#[test]
fn resolves_previously_defined_bitmap_indicator() {
let mut data = vec![0u8; 16];
data.extend_from_slice(§ion(1, 16));
data.extend_from_slice(§ion(3, 20));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(&bitmap_section(0, &[0b1010_0000]));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(&bitmap_section(254, &[]));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(b"7777");
let fields = index_fields(&data).unwrap();
assert_eq!(fields.len(), 2);
assert_eq!(fields[1].bitmap, fields[0].bitmap);
assert_eq!(
data[fields[1].bitmap.unwrap().offset + 5],
0,
"resolved field must point to the defining bitmap"
);
}
#[test]
fn rejects_previous_bitmap_indicator_without_definition() {
let mut data = vec![0u8; 16];
data.extend_from_slice(§ion(1, 16));
data.extend_from_slice(§ion(3, 20));
data.extend_from_slice(§ion(4, 29));
data.extend_from_slice(§ion(5, 16));
data.extend_from_slice(&bitmap_section(254, &[]));
data.extend_from_slice(§ion(7, 3));
data.extend_from_slice(b"7777");
assert!(matches!(index_fields(&data), Err(Error::MissingBitmap)));
}
#[test]
fn rejects_early_end_section_before_message_end() {
let mut data = vec![0u8; 16];
data.extend_from_slice(§ion(1, 16));
data.extend_from_slice(b"7777");
data.extend_from_slice(&[0, 0, 0, 0]);
let err = scan_sections(&data).unwrap_err();
assert!(matches!(err, Error::InvalidMessage(_)));
}
}