This commit is contained in:
tsuki 2025-12-22 22:53:49 +08:00
commit e606448b0a
18 changed files with 5444 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

204
Cargo.lock generated Normal file
View File

@ -0,0 +1,204 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "csv"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde_core",
]
[[package]]
name = "csv-core"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
dependencies = [
"memchr",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "flate2"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rbufr"
version = "0.1.0"
dependencies = [
"csv",
"encoding_rs",
"flate2",
"nom",
"serde",
"thiserror",
]
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "simd-adler32"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "rbufr"
version = "0.1.0"
edition = "2024"
[lib]
name = "librbufr"
path = "src/lib.rs"
[dependencies]
csv = "1.4.0"
encoding_rs = "0.8.35"
flate2 = "1.1.5"
nom = "8.0.0"
serde = { version = "1.0.228", features = ["derive"] }
thiserror = "2.0.17"

77
src/block.rs Normal file
View File

@ -0,0 +1,77 @@
use crate::errors::Result;
use crate::{discriptor_table::*, structs::versions::BUFRMessage};
pub struct MessageBlock {
message: BUFRMessage,
}
impl std::fmt::Display for MessageBlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl MessageBlock {
pub fn new(message: BUFRMessage) -> Self {
MessageBlock { message }
}
pub fn load_data(&self) -> Result<()> {
let table_info = self.message.table_info();
let mut b_table_loader = TableLoader::<BTable>::new();
let master_b_sequences =
b_table_loader.load_table(TT::Standard, table_info.master_table_version)?;
let mut d_table_loader = TableLoader::<DTable>::new();
let master_d_sequences =
d_table_loader.load_table(TT::Standard, table_info.master_table_version)?;
let local_table_version = table_info.local_table_version as u32;
if local_table_version > 0 {
let local_b_sequences = b_table_loader.load_table(
TT::Localized(local_table_version),
table_info.local_table_version,
)?;
let local_d_sequences = d_table_loader.load_table(
TT::Localized(local_table_version),
table_info.local_table_version,
)?;
}
Ok(())
// master_b_table.load_table(TT::Standard);
}
}
pub struct BUFRFile {
messages: Vec<MessageBlock>,
}
impl BUFRFile {
pub fn new() -> Self {
BUFRFile {
messages: Vec::new(),
}
}
pub(crate) fn push_message(&mut self, message: BUFRMessage) {
self.messages.push(MessageBlock::new(message));
}
/// Get the number of successfully parsed messages
pub fn message_count(&self) -> usize {
self.messages.len()
}
pub fn message_at(&self, index: usize) -> Option<&MessageBlock> {
self.messages.get(index)
}
/// Get a reference to all parsed messages
pub fn messages(&self) -> &[MessageBlock] {
&self.messages
}
}

162
src/discriptor_table.rs Normal file
View File

@ -0,0 +1,162 @@
use crate::errors::Result;
use encoding_rs::WINDOWS_1252;
use std::fs;
use std::path::{Path, PathBuf};
mod btable;
mod dtable;
pub use btable::BTable;
pub use dtable::DTable;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TableType {
A,
B,
C,
D,
}
pub struct BUFRTable;
pub trait TableTrait {
fn file_path(table_type: TableType, sub_center: Option<u32>, table_version: u8) -> PathBuf;
}
impl BUFRTable {
pub fn file_path(table_type: TableType, table_version: u8) -> PathBuf {
let base_dir = Path::new("tables/bufr");
let file_name = match table_type {
TableType::A => format!("bufrtaba_{}.csv", table_version),
TableType::B => format!("bufrtabb_{}.csv", table_version),
TableType::C => format!("bufrtabc_{}.csv", table_version),
TableType::D => format!("bufrtabd_{}.csv", table_version),
};
base_dir.join(file_name)
}
}
pub struct LocalTable;
impl LocalTable {
pub fn file_path(table_type: TableType, sub_center: u32, table_version: u8) -> PathBuf {
let base_dir = Path::new("tables/local");
let file_name = match table_type {
TableType::A => format!("loctaba_{}_{}.csv", sub_center * 256, table_version),
TableType::B => format!("loctabb_{}_{}.csv", sub_center * 256, table_version),
TableType::C => format!("loctabc_{}_{}.csv", sub_center * 256, table_version),
TableType::D => format!("loctabd_{}_{}.csv", sub_center * 256, table_version),
};
base_dir.join(file_name)
}
}
impl TableTrait for BUFRTable {
fn file_path(table_type: TableType, sub_center: Option<u32>, table_version: u8) -> PathBuf {
BUFRTable::file_path(table_type, table_version)
}
}
impl TableTrait for LocalTable {
fn file_path(table_type: TableType, sub_center: Option<u32>, table_version: u8) -> PathBuf {
let sub_center = sub_center.expect("Sub-center must be provided for LocalTable");
LocalTable::file_path(table_type, sub_center, table_version)
}
}
#[derive(Debug, Clone)]
pub struct TableLoader<T: TableT> {
sequences: Vec<T::Seq>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TT {
Localized(u32), // sub_center
Standard,
}
impl<T: TableT> TableLoader<T> {
pub fn new() -> Self {
Self {
sequences: Vec::new(),
}
}
pub fn load_table(&mut self, table_type: TT, table_version: u8) -> Result<Vec<T::Seq>> {
let table_kind = T::table_type();
let local_table_path = match table_type {
TT::Localized(sc) => LocalTable::file_path(table_kind, sc, table_version),
TT::Standard => BUFRTable::file_path(table_kind, table_version),
};
// Here you would add code to actually load and parse the table from the file at `path`.
println!("Loading table from path: {:?}", local_table_path);
let raw = fs::read(&local_table_path)?;
let fixed = normalize_dashes(raw);
let text = decode_tabd_text(fixed);
let mut table = T::default();
for line in text.lines() {
if let Some(seq) = table.parse_line(line) {
self.sequences.push(seq);
}
}
if let Some(seq) = table.finish() {
self.sequences.push(seq);
}
let mut sequences = Vec::new();
std::mem::swap(&mut sequences, &mut self.sequences);
Ok(sequences)
}
}
fn normalize_dashes(mut bytes: Vec<u8>) -> Vec<u8> {
for b in &mut bytes {
match *b {
0x96 | 0x97 => *b = b'-', // EN / EM dash → '-'
_ => {}
}
}
bytes
}
fn decode_tabd_text(bytes: Vec<u8>) -> String {
let (text, _, _) = WINDOWS_1252.decode(&bytes);
text.into_owned()
}
pub trait TableT: Default {
type Seq;
fn table_type() -> TableType;
fn parse_line(&mut self, line: &str) -> Option<Self::Seq>;
fn finish(&mut self) -> Option<Self::Seq> {
None
}
}
#[derive(Debug, Clone)]
pub struct Descriptor {
pub f: i32,
pub x: i32,
pub y: i32,
}
#[cfg(test)]
mod test {
use crate::discriptor_table::{TableLoader, btable::BTable, dtable::DTable};
#[test]
fn test_read_table() {
// let mut bufr_table: super::Table<super::BUFRTable> = super::Table::new();
let mut bufr_table = TableLoader::<BTable>::new();
bufr_table.load_table(super::TT::Standard, 11).unwrap();
println!("{:#?}", bufr_table.sequences);
}
}

View File

@ -0,0 +1,51 @@
use crate::discriptor_table::{Descriptor, TableT};
#[derive(Debug, Clone, Default)]
pub struct BTable;
#[derive(Debug, Clone)]
pub struct BSeq {
pub d: Descriptor,
pub scale: i32,
pub dw: i32,
pub refval: f32,
pub unit: String,
pub elname: String,
}
impl TableT for BTable {
type Seq = BSeq;
fn table_type() -> super::TableType {
super::TableType::B
}
fn parse_line(&mut self, line: &str) -> Option<Self::Seq> {
let fields: Vec<&str> = line.split(';').collect();
if fields.len() < 8 {
return None;
}
let parse_i = |s: &str| s.parse::<i32>().ok();
let parse_f = |s: &str| s.parse::<f32>().ok();
let f = parse_i(fields[0])?;
let x = parse_i(fields[1])?;
let y = parse_i(fields[2])?;
let name = fields[3];
let unit = fields[4];
let scale = parse_i(fields[5])?;
let refval = parse_f(fields[6])?;
let dw = parse_i(fields[7])?;
Some(BSeq {
d: Descriptor { f, x, y },
scale,
dw,
refval,
unit: unit.to_string(),
elname: name.to_string(),
})
}
}

View File

@ -0,0 +1,78 @@
use crate::discriptor_table::{Descriptor, TableT, TableType};
#[derive(Debug, Clone)]
pub struct DD {
pub f: i32,
pub x: i32,
pub y: i32,
}
#[derive(Debug, Clone)]
pub struct DSeq {
pub d: Descriptor,
pub del: Vec<DD>,
}
#[derive(Debug, Clone, Default)]
pub struct DTable {
current_seq: Option<DSeq>,
}
impl TableT for DTable {
type Seq = DSeq;
fn table_type() -> TableType {
TableType::D
}
fn parse_line(&mut self, line: &str) -> Option<Self::Seq> {
let fields: Vec<&str> = line.split(';').collect();
if fields.len() < 6 {
return None;
}
let parse_i = |s: &str| s.trim().parse::<i32>().unwrap_or(0);
let isf = parse_i(fields[0]);
let isx = parse_i(fields[1]);
let isy = parse_i(fields[2]);
let idf = parse_i(fields[3]);
let idx = parse_i(fields[4]);
let idy = parse_i(fields[5]);
let mut finished_seq = None;
let current = &mut self.current_seq;
if isf == 3 || isx != 0 || isy != 0 {
if let Some(prev) = current.take() {
finished_seq = Some(prev);
}
*current = Some(DSeq {
d: Descriptor {
f: isf,
x: isx,
y: isy,
},
del: Vec::new(),
});
}
if idf != 0 || idx != 0 || idy != 0 {
if let Some(seq) = current.as_mut() {
seq.del.push(DD {
f: idf,
x: idx,
y: idy,
});
}
}
finished_seq
}
fn finish(&mut self) -> Option<Self::Seq> {
self.current_seq.take()
}
}

31
src/errors.rs Normal file
View File

@ -0,0 +1,31 @@
use nom;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("IO Error: {0}")]
Io(#[from] std::io::Error),
#[error("CSV Error: {0}")]
Csv(#[from] csv::Error),
#[error("Table not found for type {0:?}, sub_center {1:?}, version {2}")]
TableNotFound(crate::discriptor_table::TableType, Option<u32>, u8),
#[error("Parse Error: {0}")]
ParseError(String),
#[error("File is not a valid BUFR file")]
Nom(String),
#[error("Unsupported BUFR version: {0}")]
UnsupportedVersion(u8),
}
impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for Error {
fn from(value: nom::Err<nom::error::Error<&'a [u8]>>) -> Self {
Self::Nom(value.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;

5
src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
mod block;
mod discriptor_table;
mod errors;
pub mod parser;
pub mod structs;

3
src/main.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

165
src/parser.rs Normal file
View File

@ -0,0 +1,165 @@
use crate::block::{BUFRFile, MessageBlock};
use crate::errors::Result;
use crate::structs::versions::BUFRMessage;
use flate2::read::GzDecoder;
use std::{
fs::File,
io::{BufReader, Cursor, Read, Seek, SeekFrom},
path::Path,
};
const BUFR_PATTERN: &[u8] = b"BUFR";
const BUFFER_SIZE: usize = 8192; // 8KB buffer for scanning
// const MAX_MESSAGE_SIZE: usize = 500_000; // 500KB max message size
pub struct Parser;
impl Parser {
pub fn new() -> Self {
Self
}
/// Find all offsets in the file where "BUFR" appears using streaming approach
fn find_bufr_offsets<R: Read + Seek>(reader: &mut R) -> Result<Vec<u64>> {
let mut offsets = Vec::new();
let mut buffer = vec![0u8; BUFFER_SIZE];
let mut file_offset = 0u64;
let mut overlap = vec![0u8; BUFR_PATTERN.len() - 1];
let mut overlap_len = 0;
reader.seek(SeekFrom::Start(0))?;
loop {
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
// Create a combined view of overlap + new data
let mut search_buffer = Vec::with_capacity(overlap_len + bytes_read);
search_buffer.extend_from_slice(&overlap[..overlap_len]);
search_buffer.extend_from_slice(&buffer[..bytes_read]);
// Search for BUFR pattern
for i in 0..search_buffer.len().saturating_sub(BUFR_PATTERN.len() - 1) {
if search_buffer.len() >= i + BUFR_PATTERN.len()
&& &search_buffer[i..i + BUFR_PATTERN.len()] == BUFR_PATTERN
{
let actual_offset = file_offset - overlap_len as u64 + i as u64;
offsets.push(actual_offset);
}
}
// Save overlap for next iteration
if bytes_read >= BUFR_PATTERN.len() - 1 {
overlap_len = BUFR_PATTERN.len() - 1;
overlap[..overlap_len]
.copy_from_slice(&buffer[bytes_read - overlap_len..bytes_read]);
} else {
overlap_len = bytes_read;
overlap[..overlap_len].copy_from_slice(&buffer[..bytes_read]);
}
file_offset += bytes_read as u64;
}
Ok(offsets)
}
/// Read a BUFR message from file at specific offset
fn read_message_at_offset<R: Read + Seek>(reader: &mut R, offset: u64) -> Result<Vec<u8>> {
reader.seek(SeekFrom::Start(offset))?;
// Read Section 0 to get total length
let mut section0_buf = [0u8; 8];
reader.read_exact(&mut section0_buf)?;
// Parse total length (3 bytes starting at offset 4)
let total_length =
u32::from_be_bytes([0, section0_buf[4], section0_buf[5], section0_buf[6]]);
// Limit message size for safety
// if total_length as usize > MAX_MESSAGE_SIZE {
// return Err(crate::errors::Error::ParseError(format!(
// "Message too large: {} bytes",
// total_length
// )));
// }
// Read entire message
let mut message_buf = vec![0u8; total_length as usize];
reader.seek(SeekFrom::Start(offset))?;
reader.read_exact(&mut message_buf)?;
Ok(message_buf)
}
/// Parse a file containing one or more BUFR messages using streaming approach
pub fn parse<P: AsRef<Path>>(&mut self, path: P) -> Result<BUFRFile> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
// Try to detect gzip compression
let mut magic_bytes = [0u8; 2];
reader.read_exact(&mut magic_bytes)?;
reader.seek(SeekFrom::Start(0))?;
if magic_bytes == [0x1F, 0x8B] {
// Gzip magic number detected
let mut gz_decoder = GzDecoder::new(reader);
let mut bytes = vec![];
gz_decoder.read_to_end(&mut bytes)?;
self.parse_inner(&mut Cursor::new(bytes))
} else {
// Not compressed
// Rewind reader
reader.seek(SeekFrom::Start(0))?;
self.parse_inner(&mut reader)
}
}
fn parse_inner<R>(&self, buf_reader: &mut R) -> Result<BUFRFile>
where
R: Read + Seek,
{
// Find all BUFR message offsets
let offsets = Self::find_bufr_offsets(buf_reader)?;
let mut file_block = BUFRFile::new();
// Parse each BUFR message
for offset in offsets {
match Self::read_message_at_offset(buf_reader, offset) {
Ok(message_data) => match BUFRMessage::parse(&message_data) {
Ok((_, message)) => {
file_block.push_message(message);
}
Err(e) => {
eprintln!("Failed to parse BUFR message at offset {}: {:?}", offset, e);
}
},
Err(e) => {
eprintln!("Failed to read BUFR message at offset {}: {:?}", offset, e);
}
}
}
Ok(file_block)
}
}
#[cfg(test)]
mod tests {
use super::Parser;
#[test]
fn test() {
let mut parser = Parser::new();
if let Ok(file) = parser.parse("/Users/xiang.li1/Downloads/36_2025-12-22T11_00_00.bufr") {
for message in file.messages() {
println!("{}", message);
}
}
}
}

22
src/structs/bit.rs Normal file
View File

@ -0,0 +1,22 @@
use nom::IResult;
use nom::bits::{bits, bytes, complete::take};
pub type BitInput<'a> = (&'a [u8], usize);
pub fn parse_arbitrary_bits(input: BitInput, count: usize) -> IResult<BitInput, u32> {
take(count)(input)
}
#[cfg(test)]
mod test {
use crate::structs::bit::parse_arbitrary_bits;
#[test]
fn test() {
let data = [0xA0, 0xA0, 0x01, 0xA0];
let result = parse_arbitrary_bits((&data, 0), 16).unwrap();
println!("{:?}", result);
}
}

25
src/structs/mod.rs Normal file
View File

@ -0,0 +1,25 @@
use nom::{
IResult,
bytes::complete::{tag, take},
number::complete::{be_u8, be_u16, be_u24},
};
pub mod bit;
pub mod versions;
#[inline]
pub fn skip(n: usize) -> impl Fn(&[u8]) -> IResult<&[u8], ()> {
move |input: &[u8]| {
let (input, _) = take(n)(input)?;
Ok((input, ()))
}
}
#[inline]
pub fn skip1(input: &[u8]) -> IResult<&[u8], ()> {
skip(1)(input)
}
#[inline]
pub fn skip2(input: &[u8]) -> IResult<&[u8], ()> {
skip(2)(input)
}

View File

@ -0,0 +1,89 @@
pub mod v2;
pub mod v4;
pub(super) use super::{skip, skip1, skip2};
use crate::errors::{Error, Result};
use nom::{
IResult,
bytes::complete::{tag, take},
number::complete::{be_u8, be_u16, be_u24},
};
#[derive(Clone)]
pub enum BUFRMessage {
V2(v2::BUFRMessageV2),
V4(v4::BUFRMessageV4),
}
impl BUFRMessage {
pub fn parse(input: &[u8]) -> Result<(&[u8], Self)> {
let (_, section0) = parse_section0(input)?;
match section0.version {
2 => {
let (input, msg) = v2::BUFRMessageV2::parse(input)?;
Ok((input, BUFRMessage::V2(msg)))
}
4 => {
let (input, msg) = v4::BUFRMessageV4::parse(input)?;
Ok((input, BUFRMessage::V4(msg)))
}
_ => Err(Error::UnsupportedVersion(section0.version)),
}
}
pub fn version(&self) -> u8 {
match self {
BUFRMessage::V2(_) => 2,
BUFRMessage::V4(_) => 4,
}
}
pub fn table_info(&self) -> TableInfo {
match self {
BUFRMessage::V2(msg) => msg.table_info(),
BUFRMessage::V4(msg) => msg.table_info(),
}
}
}
impl std::fmt::Display for BUFRMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BUFRMessage::V2(msg) => msg.description(f),
BUFRMessage::V4(msg) => msg.description(f),
}
}
}
pub trait MessageVersion: Sized {
fn parse(input: &[u8]) -> IResult<&[u8], Self>;
fn description(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
fn table_info(&self) -> TableInfo;
}
pub struct TableInfo {
pub master_table_version: u8,
pub local_table_version: u8,
pub center_id: u16,
pub subcenter_id: u16,
}
#[derive(Clone)]
struct Section0 {
pub total_length: u32,
pub version: u8,
}
fn parse_section0(input: &[u8]) -> IResult<&[u8], Section0> {
let (input, _) = tag("BUFR")(input)?;
let (input, total_length) = be_u24(input)?;
let (input, edition) = be_u8(input)?;
Ok((
input,
Section0 {
total_length,
version: edition,
},
))
}

281
src/structs/versions/v2.rs Normal file
View File

@ -0,0 +1,281 @@
use nom::{
IResult,
bytes::complete::{tag, take},
number::complete::{be_u8, be_u16, be_u24},
};
use crate::structs::versions::MessageVersion;
use super::skip1;
#[derive(Clone)]
pub struct BUFRMessageV2 {
pub section0: Section0,
pub section1: Section1,
pub section2: Option<Section2>,
pub section3: Section3,
pub section4: Section4,
}
impl MessageVersion for BUFRMessageV2 {
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (input, section0) = parse_section0(input)?;
let (input, section1) = parse_section1(input)?;
let (input, section2) = if section1.optional_section_present {
let (input, sec2) = parse_section2(input)?;
(input, Some(sec2))
} else {
(input, None)
};
let (input, section3) = parse_section3(input)?;
let (input, section4) = parse_section4(input)?;
let (input, _section5) = parse_section5(input)?;
Ok((
input,
BUFRMessageV2 {
section0,
section1,
section2,
section3,
section4,
},
))
}
fn description(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BUFR Message V2:\n")?;
write!(f, "{}\n", self.section1)?;
Ok(())
}
fn table_info(&self) -> super::TableInfo {
super::TableInfo {
master_table_version: self.section1.master_table_version,
local_table_version: self.section1.local_table_version,
center_id: self.section1.centre as u16,
subcenter_id: self.section1.subcentre as u16,
}
}
}
#[derive(Clone)]
pub struct Section0 {
pub total_length: u32,
pub version: u8,
}
fn parse_section0(input: &[u8]) -> IResult<&[u8], Section0> {
let (input, _) = tag("BUFR")(input)?;
let (input, total_length) = be_u24(input)?;
let (input, edition) = be_u8(input)?;
Ok((
input,
Section0 {
total_length,
version: edition,
},
))
}
#[derive(Clone, Debug)]
pub struct Section1 {
pub length: usize,
pub master_table: u8, // octet 4
pub subcentre: u8, // octet 5
pub centre: u8, // octet 6
pub update_sequence_number: u8, // octet 7
pub optional_section_present: bool, // octet 8 bit1 (MSB)
pub data_category: u8, // octet 9
pub data_subcategory: u8, // octet 10
pub master_table_version: u8, // octet 11
pub local_table_version: u8, // octet 12
pub year: u8, // octet 13 (year of century)
pub month: u8, // octet 14
pub day: u8, // octet 15
pub hour: u8, // octet 16
pub minute: u8, // octet 17
// octet 18- local use: 你可以选择保存或直接跳过
}
fn parse_section1(input: &[u8]) -> IResult<&[u8], Section1> {
let (input, length) = be_u24(input)?;
let length = length as usize;
const FIXED_LEN: usize = 17;
if length < FIXED_LEN {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::LengthValue,
)));
}
let (input, master_table) = be_u8(input)?;
let (input, subcentre) = be_u8(input)?;
let (input, centre) = be_u8(input)?;
let (input, update_sequence_number) = be_u8(input)?;
let (input, optional_section_flag) = be_u8(input)?;
let optional_section_present = (optional_section_flag & 0x80) != 0;
let (input, data_category) = be_u8(input)?;
let (input, data_subcategory) = be_u8(input)?;
let (input, master_table_version) = be_u8(input)?;
let (input, local_table_version) = be_u8(input)?;
let (input, year) = be_u8(input)?;
let (input, month) = be_u8(input)?;
let (input, day) = be_u8(input)?;
let (input, hour) = be_u8(input)?;
let (input, minute) = be_u8(input)?;
// 剩余 local-use
let local_len = length - FIXED_LEN;
let (input, _) = nom::bytes::complete::take(local_len)(input)?;
Ok((
input,
Section1 {
length,
master_table,
subcentre,
centre,
update_sequence_number,
optional_section_present,
data_category,
data_subcategory,
master_table_version,
local_table_version,
year,
month,
day,
hour,
minute,
},
))
}
#[derive(Clone)]
pub struct Section2 {
pub length: usize,
pub data: Vec<u8>,
}
fn parse_section2(input: &[u8]) -> IResult<&[u8], Section2> {
let (input, length) = be_u24(input)?;
let (input, _) = skip1(input)?;
let (input, data) = take(length - 4)(input)?;
Ok((
input,
Section2 {
length: length as usize,
data: data.to_vec(),
},
))
}
#[derive(Clone)]
pub struct Section3 {
pub length: usize,
pub number_of_subsets: u16,
pub is_observation: bool,
pub is_compressed: bool,
pub data: Vec<u8>,
}
fn parse_section3(input: &[u8]) -> IResult<&[u8], Section3> {
let (input, length) = be_u24(input)?;
let (input, _) = skip1(input)?;
let (input, number_of_subsets) = be_u16(input)?;
let (input, flags) = be_u8(input)?;
let is_observation = (flags & 0b1000_0000) != 0;
let is_compressed = (flags & 0b0100_0000) != 0;
let (input, data) = take(length - 7)(input)?;
Ok((
input,
Section3 {
length: length as usize,
number_of_subsets,
is_observation,
is_compressed,
data: data.to_vec(),
},
))
}
#[derive(Clone)]
pub struct Section4 {
pub length: usize,
pub data: Vec<u8>,
}
fn parse_section4(input: &[u8]) -> IResult<&[u8], Section4> {
let (input, length) = be_u24(input)?;
let (input, _) = skip1(input)?;
let (input, data) = take(length - 4)(input)?;
Ok((
input,
Section4 {
length: length as usize,
data: data.to_vec(),
},
))
}
impl std::fmt::Display for Section1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Section 1 (BUFR v2):")?;
writeln!(f, " Length: {} bytes", self.length)?;
writeln!(f)?;
writeln!(f, " Organization:")?;
writeln!(
f,
" Centre: {:<5} (0x{:02X})",
self.centre, self.centre
)?;
writeln!(
f,
" Sub-centre: {:<5} (0x{:02X})",
self.subcentre, self.subcentre
)?;
writeln!(
f,
" Update Sequence: {}",
self.update_sequence_number
)?;
writeln!(f)?;
writeln!(f, " Data Classification:")?;
writeln!(f, " Category: {}", self.data_category)?;
writeln!(f, " Sub-category: {}", self.data_subcategory)?;
writeln!(f)?;
writeln!(f, " Table Versions:")?;
writeln!(
f,
" Master Table: {} (v{})",
self.master_table, self.master_table_version
)?;
writeln!(f, " Local Table: v{}", self.local_table_version)?;
writeln!(f)?;
writeln!(f, " Observation Time:")?;
writeln!(
f,
" DateTime: 19{:02}-{:02}-{:02} {:02}:{:02}:00 UTC",
self.year, self.month, self.day, self.hour, self.minute
)?;
writeln!(f)?;
writeln!(f, " Optional Data:")?;
write!(
f,
" Section 2 Present: {}",
if self.optional_section_present {
"Yes"
} else {
"No"
}
)
}
}
pub struct Section5;
fn parse_section5(input: &[u8]) -> IResult<&[u8], Section5> {
let (input, _) = tag("7777")(input)?;
Ok((input, Section5 {}))
}

345
src/structs/versions/v4.rs Normal file
View File

@ -0,0 +1,345 @@
use crate::structs::versions::MessageVersion;
use nom::{
IResult,
bytes::complete::{tag, take},
error::{Error, ErrorKind},
number::complete::{be_u8, be_u16, be_u24},
};
use super::skip1;
#[derive(Clone)]
pub struct BUFRMessageV4 {
pub section0: Section0,
pub section1: Section1,
pub section2: Option<Section2>,
pub section3: Section3,
pub section4: Section4,
}
impl MessageVersion for BUFRMessageV4 {
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (input, section0) = parse_section0(input)?;
let (input, section1) = parse_section1(input)?;
let (input, section2) = if section1.optional_section_present {
let (input, sec2) = parse_section2(input)?;
(input, Some(sec2))
} else {
(input, None)
};
let (input, section3) = parse_section3(input)?;
let (input, section4) = parse_section4(input)?;
let (input, _section5) = parse_section5(input)?;
Ok((
input,
BUFRMessageV4 {
section0,
section1,
section2,
section3,
section4,
},
))
}
fn description(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BUFR Message V4:\n")?;
write!(f, "{}\n", self.section1)?;
Ok(())
}
fn table_info(&self) -> super::TableInfo {
super::TableInfo {
master_table_version: self.section1.master_table_version,
local_table_version: self.section1.local_table_version,
center_id: self.section1.centre as u16,
subcenter_id: self.section1.subcentre as u16,
}
}
}
#[derive(Clone)]
pub struct Section0 {
pub total_length: u32,
pub version: u8,
}
fn parse_section0(input: &[u8]) -> IResult<&[u8], Section0> {
let (input, _) = tag("BUFR")(input)?;
let (input, total_length) = be_u24(input)?;
let (input, edition) = be_u8(input)?;
Ok((
input,
Section0 {
total_length,
version: edition,
},
))
}
#[derive(Clone, Debug)]
pub struct Section1 {
pub length: usize, // octet 1-3
pub master_table: u8, // octet 4
pub centre: u16, // octet 5-6
pub subcentre: u16, // octet 7-8
pub update_sequence_number: u8, // octet 9
pub optional_section_present: bool, // octet 10 bit1
pub data_category: u8, // octet 11
pub international_data_subcategory: u8, // octet 12
pub local_subcategory: u8, // octet 13
pub master_table_version: u8, // octet 14
pub local_table_version: u8, // octet 15
pub year: u16, // octet 16-17 (4 digits)
pub month: u8, // octet 18
pub day: u8, // octet 19
pub hour: u8, // octet 20
pub minute: u8, // octet 21
pub second: u8, // octet 22
pub local_use: Vec<u8>, // octet 23-
}
fn parse_section1(input: &[u8]) -> IResult<&[u8], Section1> {
let (input, length_u24) = be_u24(input)?;
let length = length_u24 as usize;
const FIXED_LEN: usize = 22;
if length < FIXED_LEN {
return Err(nom::Err::Error(Error::new(input, ErrorKind::LengthValue)));
}
let (input, master_table) = be_u8(input)?;
let (input, centre) = be_u16(input)?;
let (input, subcentre) = be_u16(input)?;
let (input, update_sequence_number) = be_u8(input)?;
let (input, flags) = be_u8(input)?;
let optional_section_present = (flags & 0x80) != 0;
let (input, data_category) = be_u8(input)?;
let (input, international_data_subcategory) = be_u8(input)?;
let (input, local_subcategory) = be_u8(input)?;
let (input, master_table_version) = be_u8(input)?;
let (input, local_table_version) = be_u8(input)?;
let (input, year) = be_u16(input)?;
let (input, month) = be_u8(input)?;
let (input, day) = be_u8(input)?;
let (input, hour) = be_u8(input)?;
let (input, minute) = be_u8(input)?;
let (input, second) = be_u8(input)?;
let local_len = length - FIXED_LEN;
let (input, local_bytes) = take(local_len)(input)?;
Ok((
input,
Section1 {
length,
master_table,
centre,
subcentre,
update_sequence_number,
optional_section_present,
data_category,
international_data_subcategory,
local_subcategory,
master_table_version,
local_table_version,
year,
month,
day,
hour,
minute,
second,
local_use: local_bytes.to_vec(),
},
))
}
impl std::fmt::Display for Section1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Section 1:")?;
writeln!(f, " Length: {} bytes", self.length)?;
writeln!(f)?;
writeln!(f, " Organization:")?;
writeln!(
f,
" Centre: {:<5} (0x{:04X})",
self.centre, self.centre
)?;
writeln!(
f,
" Sub-centre: {:<5} (0x{:04X})",
self.subcentre, self.subcentre
)?;
writeln!(
f,
" Update Sequence: {}",
self.update_sequence_number
)?;
writeln!(f)?;
writeln!(f, " Data Classification:")?;
writeln!(f, " Category: {}", self.data_category)?;
writeln!(
f,
" International Sub: {}",
self.international_data_subcategory
)?;
writeln!(f, " Local Sub: {}", self.local_subcategory)?;
writeln!(f)?;
writeln!(f, " Table Versions:")?;
writeln!(
f,
" Master Table: {} (v{})",
self.master_table, self.master_table_version
)?;
writeln!(f, " Local Table: v{}", self.local_table_version)?;
writeln!(f)?;
writeln!(f, " Observation Time:")?;
writeln!(
f,
" DateTime: {:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC",
self.year, self.month, self.day, self.hour, self.minute, self.second
)?;
writeln!(f)?;
writeln!(f, " Optional Data:")?;
writeln!(
f,
" Section 2 Present: {}",
if self.optional_section_present {
"Yes"
} else {
"No"
}
)?;
write!(f, " Local Use Data: {} bytes", self.local_use.len())
}
}
#[derive(Clone)]
pub struct Section2 {
pub length: usize,
pub data: Vec<u8>,
}
fn parse_section2(input: &[u8]) -> IResult<&[u8], Section2> {
let (input, length) = be_u24(input)?;
let (input, _) = skip1(input)?;
let (input, data) = take(length - 4)(input)?;
Ok((
input,
Section2 {
length: length as usize,
data: data.to_vec(),
},
))
}
#[derive(Clone)]
pub struct Section3 {
pub length: usize,
pub number_of_subsets: u16,
pub is_observation: bool,
pub is_compressed: bool,
pub data: Vec<u8>,
}
fn parse_section3(input: &[u8]) -> IResult<&[u8], Section3> {
let (input, length) = be_u24(input)?;
let (input, _) = skip1(input)?;
let (input, number_of_subsets) = be_u16(input)?;
let (input, flags) = be_u8(input)?;
let is_observation = (flags & 0b1000_0000) != 0;
let is_compressed = (flags & 0b0100_0000) != 0;
let (input, data) = take(length - 7)(input)?;
Ok((
input,
Section3 {
length: length as usize,
number_of_subsets,
is_observation,
is_compressed,
data: data.to_vec(),
},
))
}
#[derive(Clone)]
pub struct Section4 {
pub length: usize,
pub data: Vec<u8>,
}
fn parse_section4(input: &[u8]) -> IResult<&[u8], Section4> {
let (input, length) = be_u24(input)?;
let (input, _) = skip1(input)?;
let (input, data) = take(length - 4)(input)?;
Ok((
input,
Section4 {
length: length as usize,
data: data.to_vec(),
},
))
}
pub struct Section5;
fn parse_section5(input: &[u8]) -> IResult<&[u8], Section5> {
let (input, _) = tag("7777")(input)?;
Ok((input, Section5 {}))
}
fn parse_message(input: &[u8]) -> IResult<&[u8], ()> {
let (input, section0) = parse_section0(input)?;
let (input, section1) = parse_section1(input)?;
let (input, section2) = if section1.optional_section_present {
let (input, sec2) = parse_section2(input)?;
(input, Some(sec2))
} else {
(input, None)
};
let (input, section3) = parse_section3(input)?;
let (input, section4) = parse_section4(input)?;
let (input, section5) = parse_section5(input)?;
Ok((input, ()))
}
#[derive(Clone)]
pub struct BUFRMessage {
pub section0: Section0,
pub section1: Section1,
pub section2: Option<Section2>,
pub section3: Section3,
pub section4: Section4,
}
impl BUFRMessage {
pub fn parse(input: &[u8]) -> IResult<&[u8], BUFRMessage> {
let (input, section0) = parse_section0(input)?;
let (input, section1) = parse_section1(input)?;
let (input, section2) = if section1.optional_section_present {
let (input, sec2) = parse_section2(input)?;
(input, Some(sec2))
} else {
(input, None)
};
let (input, section3) = parse_section3(input)?;
let (input, section4) = parse_section4(input)?;
let (input, _section5) = parse_section5(input)?;
Ok((
input,
BUFRMessage {
section0,
section1,
section2,
section3,
section4,
},
))
}
}

1485
tables/bufr/bufrtabb_11.csv Normal file

File diff suppressed because it is too large Load Diff

2404
tables/bufr/bufrtabd_11.csv Normal file

File diff suppressed because it is too large Load Diff