From 1dece71f2971d66288c9ac328902f42d71246ad6 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Tue, 29 Oct 2024 08:55:34 +0100 Subject: [PATCH 01/28] Adding CRC field management Signed-off-by: Guillaume W. Bres --- binex/Cargo.toml | 1 + binex/src/decoder.rs | 2 +- binex/src/lib.rs | 6 +- binex/src/message/checksum.rs | 162 ++++++++++++------ binex/src/message/mod.rs | 134 ++++++++++++--- binex/src/message/record/ephemeris/gps/raw.rs | 5 +- binex/src/message/record/mod.rs | 12 ++ binex/src/message/record/monument/mod.rs | 2 +- binex/tests/decoder.rs | 2 +- binex/tests/geo.rs | 8 +- 10 files changed, 248 insertions(+), 86 deletions(-) diff --git a/binex/Cargo.toml b/binex/Cargo.toml index e055a180a..7ab87cf5a 100644 --- a/binex/Cargo.toml +++ b/binex/Cargo.toml @@ -20,6 +20,7 @@ rustdoc-args = ["--cfg", "docrs", "--generate-link-to-definition"] [dependencies] log = "0.4" +md-5 = "0.10" thiserror = "1" flate2 = { version = "1.0.34", optional = true } hifitime = { version = "4.0.0-alpha", features = ["serde", "std"] } diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index f4c89fb11..d1053b308 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -217,7 +217,7 @@ impl Iterator for Decoder { // especially the signal sampling that we do not support yet. // In this case, we simply trash the remaning amount of bytes, // message is lost and we move on to the next SYNC - warn!("library limitation: unprocessed message"); + //println!("library limitation: unprocessed message"); self.state = State::IncompleteTrashing; //println!("need to trash {} bytes", self.size_to_complete); } diff --git a/binex/src/lib.rs b/binex/src/lib.rs index 04f519e67..9da0a6c9d 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -49,6 +49,10 @@ pub enum Error { UnknownRecordFieldId, #[error("utf8 error")] Utf8Error, - #[error("Incomplete message")] + #[error("missing crc bytes")] + MissingCRC, + #[error("received invalid crc")] + BadCRC, + #[error("incomplete message")] IncompleteMessage(usize), } diff --git a/binex/src/message/checksum.rs b/binex/src/message/checksum.rs index bbe204745..505cdf2d0 100644 --- a/binex/src/message/checksum.rs +++ b/binex/src/message/checksum.rs @@ -1,68 +1,132 @@ -use crate::Message; +//! Checksum calculator +use md5::{Digest, Md5}; -/// BINEX Checksum Calculator +/// Checksum caculator +#[derive(Debug, Copy, Clone)] pub enum Checksum { - /// For [1 - 2^8-1] message - /// CRC is 1 byte XOR XOR8, - /// For [2^8, 2^12-1] message - POLY12, - /// For [2^12, 2^20-1] message, - POLY20, + XOR16, + XOR32, + MD5, } impl Checksum { - const fn binary_mask(&self) -> u32 { - match self { - Self::XOR8 => 0xff, - Self::POLY12 => 0x7ff, - Self::POLY20 => 0xfff, + // const fn binary_mask(&self) -> u32 { + // match self { + // Self::XOR8 => 0xff, + // Self::POLY12 => 0x7ff, + // Self::POLY20 => 0xfff, + // } + // } + // const fn look_up_table(&self) -> &[u8] { + // match self { + // Self::XOR8 => &[0, 0, 0, 0], + // Self::POLY12 => { + // // CRC12 table + // // x^16 + x^12 + x^5 +1 + // &[0, 1, 2, 3] + // }, + // Self::POLY20 => { + // // CRC16 table + // // x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 +1 + // &[0, 1, 2, 3] + // }, + // } + // } + /// Determines [ChecksumType] to use for a message + pub fn from_len(mlen: usize, enhanced: bool) -> Self { + if enhanced { + if mlen < 128 { + Self::XOR16 + } else if mlen < 1048575 { + Self::XOR32 + } else { + Self::MD5 + } + } else { + if mlen < 128 { + Self::XOR8 + } else if mlen < 4096 { + Self::XOR16 + } else if mlen < 1048575 { + Self::XOR32 + } else { + Self::MD5 + } } } - const fn look_up_table(&self) -> &[u8] { + /// Length we need to decode/encode this type of Checksum + pub fn len(&self) -> usize { match self { - Self::XOR8 => &[0, 0, 0, 0], - Self::POLY12 => { - // CRC12 table - // x^16 + x^12 + x^5 +1 - &[0, 1, 2, 3] - }, - Self::POLY20 => { - // CRC16 table - // x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 +1 - &[0, 1, 2, 3] - }, + Self::XOR8 => 1, + Self::XOR16 => 2, + Self::XOR32 => 4, + Self::MD5 => 16, } } - pub fn new(msg: &Message) -> Self { - if msg.len() < 127 { - Self::XOR8 - } else if msg.len() < 4096 { - Self::POLY12 + /// Helper to decode checksum value as unsigned 128, + /// which covers all scenarios + pub fn decode(&self, slice: &[u8], len: usize, big_endian: bool) -> u128 { + if len == 1 { + slice[0] as u128 + } else if len == 2 { + let val_u16 = if big_endian { + u16::from_be_bytes([slice[0], slice[1]]) + } else { + u16::from_le_bytes([slice[0], slice[1]]) + }; + val_u16 as u128 + } else if len == 4 { + let val_u32 = if big_endian { + u32::from_be_bytes([slice[0], slice[1], slice[2], slice[3]]) + } else { + u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) + }; + val_u32 as u128 } else { - Self::POLY20 + panic!("md5"); } } - /// Calculates CRC from [Message] - pub fn calc_from_msg(msg: &Message) -> u32 { - let bytes = msg.to_bytes(); - Self::calc_from_bytes(bytes) - } - /// Macro that verifies this [Message] contains correct CRC - pub fn crc_ok(msg: &Message) -> bool { - let crc = msg.crc(); - Self::calc_from_msg(msg) == crc + /// Calculates expected Checksum for this msg + pub fn calc(&self, bytes: &[u8], mlen: usize) -> u128 { + match self { + Self::XOR8 => Self::xor8_calc(bytes, mlen), + Self::XOR16 => Self::xor16_calc(bytes), + Self::XOR32 => Self::xor32_calc(bytes), + Self::MD5 => Self::md5_calc(bytes), + } } - /// Calculates CRC from buffer content. - /// Correct content must be correctly extracted beforehand. - pub fn calc_from_bytes(raw: &[u8]) -> u32 { - let size = raw.len(); - if size < 128 { - // 0-127 bytes: 1 byte checksum XOR all bytes - } else if size < 4096 { - // 128-4095 x^16 + x^12 + x^5 + x^0 polynomial - //let lut = self.look_up_table(); + /// Calculates expected Checksum using XOR8 algorithm + fn xor8_calc(bytes: &[u8], size: usize) -> u128 { + let mut xor = bytes[0]; + for i in 1..size { + xor ^= bytes[i]; } + xor as u128 + } + /// Calculates expected Checksum using XOR16 algorithm + fn xor16_calc(bytes: &[u8]) -> u128 { 0 } + /// Calculates expected Checksum using XO32 algorithm + fn xor32_calc(bytes: &[u8]) -> u128 { + 0 + } + /// Calculates expected Checksum using MD5 algorithm + fn md5_calc(bytes: &[u8]) -> u128 { + let mut hasher = Md5::new(); + hasher.update(bytes); + let md5 = hasher.finalize(); + u128::from_le_bytes(md5.into()) + } +} + +#[cfg(test)] +mod test { + use super::Checksum; + #[test] + fn test_xor8() { + let buf = [0, 1, 2, 3, 4]; + assert_eq!(Checksum::XOR8.calc(&buf, 5), 4); + } } diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 5af4ef92a..00802cbbe 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -1,6 +1,7 @@ +mod checksum; mod mid; // message ID mod record; // Record: message content -mod time; // Epoch encoding/decoding +mod time; // Epoch encoding/decoding // checksum calc. pub use record::{ EphemerisFrame, GPSEphemeris, GPSRaw, MonumentGeoMetadata, MonumentGeoRecord, Record, @@ -10,6 +11,8 @@ pub use time::TimeResolution; pub(crate) use mid::MessageID; +use checksum::Checksum; + use crate::{constants::Constants, utils::Utils, Error}; #[derive(Debug, Clone, PartialEq, Default)] @@ -57,17 +60,21 @@ impl Message { let mid = self.record.to_message_id() as u32; total += Self::bnxi_encoding_size(mid); - let mlen = self.record.encoding_size() as u32; - total += Self::bnxi_encoding_size(mlen); + let mlen = self.record.encoding_size(); + total += Self::bnxi_encoding_size(mlen as u32); total += self.record.encoding_size(); - total += 1; // CRC: TODO! + + let ck = Checksum::from_len(mlen, self.enhanced_crc); + total += ck.len(); + total } /// Decoding attempt from buffered content. pub fn decode(buf: &[u8]) -> Result { let sync_off; + let buf_len = buf.len(); let mut big_endian = true; let mut reversed = false; let mut enhanced_crc = false; @@ -123,7 +130,7 @@ impl Message { } // make sure we can parse up to 4 byte MID - if buf.len() - sync_off < 4 { + if buf_len - sync_off < 4 { return Err(Error::NotEnoughBytes); } @@ -136,7 +143,7 @@ impl Message { ptr += size; // make sure we can parse up to 4 byte MLEN - if buf.len() - ptr < 4 { + if buf_len - ptr < 4 { return Err(Error::NotEnoughBytes); } @@ -144,7 +151,7 @@ impl Message { let (mlen, size) = Self::decode_bnxi(&buf[ptr..], big_endian); let mlen = mlen as usize; - if buf.len() - ptr < mlen { + if buf_len - ptr < mlen { // buffer does not contain complete message! return Err(Error::IncompleteMessage(mlen)); } @@ -173,16 +180,32 @@ impl Message { }, }; - // 5. CRC verification + // 5. CRC + let ck_offset = mlen + 3; + let checksum = Checksum::from_len(mlen, enhanced_crc); + let ck_len = checksum.len(); + if buf_len < ck_offset + ck_len { + return Err(Error::MissingCRC); + } - Ok(Self { - mid, - record, - reversed, - time_res, - big_endian, - enhanced_crc, - }) + // decode + let ck = checksum.decode(&buf[ck_offset..], ck_len, big_endian); + + // verify + let expected = checksum.calc(&buf[sync_off + 1..], mlen + 2); + + if expected != ck { + Err(Error::BadCRC) + } else { + Ok(Self { + mid, + record, + reversed, + time_res, + big_endian, + enhanced_crc, + }) + } } /// [Message] encoding attempt into buffer. @@ -203,22 +226,27 @@ impl Message { ptr += Self::encode_bnxi(mid, self.big_endian, &mut buf[ptr..])?; // Encode MLEN - let mlen = self.record.encoding_size() as u32; - ptr += Self::encode_bnxi(mlen, self.big_endian, &mut buf[ptr..])?; + let mlen = self.record.encoding_size(); + ptr += Self::encode_bnxi(mlen as u32, self.big_endian, &mut buf[ptr..])?; // Encode message match &self.record { Record::EphemerisFrame(fr) => { - fr.encode(self.big_endian, &mut buf[ptr..])?; + ptr += fr.encode(self.big_endian, &mut buf[ptr..])?; }, Record::MonumentGeo(geo) => { - geo.encode(self.big_endian, &mut buf[ptr..])?; + ptr += geo.encode(self.big_endian, &mut buf[ptr..])?; }, } - // TODO: encode CRC + // encode CRC + let ck = Checksum::from_len(mlen, self.enhanced_crc); + let ck_len = ck.len(); + let crc_u128 = ck.calc(&buf[1..], mlen + 2); + let crc_bytes = crc_u128.to_le_bytes(); - Ok(ptr) + buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); + Ok(ptr + ck_len) } /// Returns the SYNC byte we expect for [Self] @@ -367,8 +395,9 @@ impl Message { mod test { use super::Message; use crate::message::TimeResolution; - use crate::message::{EphemerisFrame, GPSRaw, Record}; + use crate::message::{EphemerisFrame, GPSRaw, MonumentGeoRecord, Record}; use crate::{constants::Constants, Error}; + #[test] fn big_endian_bnxi_1() { let bytes = [0x7a]; @@ -527,6 +556,7 @@ mod test { _ => panic!("should have paniced"), } } + #[test] fn decode_fwd_enhancedcrc_stream() { let buf = [Constants::FWDSYNC_BE_ENHANCED_CRC, 0, 0, 0]; @@ -536,6 +566,7 @@ mod test { _ => panic!("should have paniced"), } } + #[test] fn decode_fwd_le_stream() { let buf = [Constants::FWDSYNC_LE_STANDARD_CRC, 0, 0, 0]; @@ -545,6 +576,7 @@ mod test { _ => panic!("should have paniced"), } } + #[test] fn decode_reversed_stream() { let buf = [Constants::REVSYNC_BE_STANDARD_CRC, 0, 0, 0]; @@ -572,12 +604,64 @@ mod test { _ => panic!("should have paniced"), } } + + #[test] + fn test_monument_geo() { + let big_endian = true; + let enhanced_crc = false; + let reversed = false; + + let geo = MonumentGeoRecord::default().with_comment("simple"); + + let geo_len = geo.encoding_size(); + + let record = Record::new_monument_geo(geo); + + let msg = Message::new( + big_endian, + TimeResolution::QuarterSecond, + enhanced_crc, + reversed, + record, + ); + + // SYNC + MID(1) + MLEN(1) + RLEN + CRC(1) + assert_eq!(msg.encoding_size(), 1 + 1 + 1 + geo_len + 1); + + let mut encoded = [0; 256]; + msg.encode(&mut encoded).unwrap(); + + assert_eq!(encoded[17], 7); + + // parse back + let parsed = Message::decode(&encoded).unwrap(); + } + #[test] fn test_gps_raw() { - let record = Record::new_ephemeris_frame(EphemerisFrame::GPSRaw(GPSRaw::default())); - let msg = Message::new(true, TimeResolution::QuarterSecond, false, false, record); + let big_endian = true; + let enhanced_crc = false; + let reversed = false; + + let gps_raw = EphemerisFrame::GPSRaw(GPSRaw::default()); + let gps_raw_len = gps_raw.encoding_size(); + let record = Record::new_ephemeris_frame(gps_raw); + + let msg = Message::new( + big_endian, + TimeResolution::QuarterSecond, + enhanced_crc, + reversed, + record, + ); + + // SYNC + MID(1) + MLEN(1) + RLEN + CRC(1) + assert_eq!(msg.encoding_size(), 1 + 1 + 1 + gps_raw_len + 1); let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); + + // parse back + let parsed = Message::decode(&encoded).unwrap(); } } diff --git a/binex/src/message/record/ephemeris/gps/raw.rs b/binex/src/message/record/ephemeris/gps/raw.rs index afbd5d87c..939859f53 100644 --- a/binex/src/message/record/ephemeris/gps/raw.rs +++ b/binex/src/message/record/ephemeris/gps/raw.rs @@ -1,8 +1,5 @@ //! Raw GPS Ephemeris -use crate::{ - //utils::Utils, - Error, -}; +use crate::Error; #[derive(Debug, Clone, PartialEq)] pub struct GPSRaw { diff --git a/binex/src/message/record/mod.rs b/binex/src/message/record/mod.rs index 0f8860898..6e140d66b 100644 --- a/binex/src/message/record/mod.rs +++ b/binex/src/message/record/mod.rs @@ -17,6 +17,18 @@ pub enum Record { EphemerisFrame(EphemerisFrame), } +impl From for Record { + fn from(geo: MonumentGeoRecord) -> Self { + Self::MonumentGeo(geo) + } +} + +impl From for Record { + fn from(fr: EphemerisFrame) -> Self { + Self::EphemerisFrame(fr) + } +} + impl Default for Record { fn default() -> Self { Self::MonumentGeo(Default::default()) diff --git a/binex/src/message/record/monument/mod.rs b/binex/src/message/record/monument/mod.rs index 0ce1fad94..ed0edd529 100644 --- a/binex/src/message/record/monument/mod.rs +++ b/binex/src/message/record/monument/mod.rs @@ -168,7 +168,7 @@ impl MonumentGeoRecord { ptr += offs; } - Ok(self.encoding_size()) + Ok(size) } /// Returns total length (bytewise) required to fully encode [Self]. diff --git a/binex/tests/decoder.rs b/binex/tests/decoder.rs index 623b4b56b..6bb0c7252 100644 --- a/binex/tests/decoder.rs +++ b/binex/tests/decoder.rs @@ -17,7 +17,7 @@ fn mfle20190130() { Some(Err(e)) => match e { Error::IoError(e) => panic!("i/o error: {}", e), e => { - //println!("err={}", e); + println!("err={}", e); }, }, None => { diff --git a/binex/tests/geo.rs b/binex/tests/geo.rs index 943d53f7b..d6c06ebed 100644 --- a/binex/tests/geo.rs +++ b/binex/tests/geo.rs @@ -20,7 +20,7 @@ fn geo_message() { assert_eq!( buf, - [0xC2, 0, 13, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0] + [0xC2, 0, 13, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 99, 0, 0, 0, 0, 0, 0, 0] ); let geo = MonumentGeoRecord::new(t, MonumentGeoMetadata::IGS) @@ -37,7 +37,7 @@ fn geo_message() { buf, [ 0xC2, 0, 20, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 5, 87, 111, 114, 108, - 100, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 100, 61, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -55,7 +55,7 @@ fn geo_message() { buf, [ 0xC2, 0, 20, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 5, 87, 111, 114, 108, - 100, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 100, 61, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -73,7 +73,7 @@ fn geo_message() { buf, [ 0xC2, 0, 19, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 14, 4, 67, 108, 105, 109, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 92, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); } From 5e7f7cf16d4b5f3766a3bfcabd080e445bc3b165 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Tue, 29 Oct 2024 14:45:19 +0100 Subject: [PATCH 02/28] Improve Decoder implementation Signed-off-by: Guillaume W. Bres --- binex/src/decoder.rs | 150 ++++++++++++++--------------------------- binex/tests/decoder.rs | 4 +- 2 files changed, 52 insertions(+), 102 deletions(-) diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index d1053b308..2bd3e9630 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -58,21 +58,25 @@ enum State { } /// [BINEX] Stream Decoder. Use this structure to decode all messages streamed -/// on a readable interface. -pub struct Decoder { +/// on a [Read]able interface. M represents the internal buffer depth: +/// * the larger M the less constrain on the I/O interface (less frequent access) +/// * but the larger the (initial) memory allocation +pub struct Decoder { /// Internal state state: State, + /// Write pointer + wr_ptr: usize, /// Read pointer rd_ptr: usize, + /// Reached EOS + eos: bool, /// Internal buffer - buffer: Vec, + buf: [u8; M], /// [R] reader: Reader, - /// Used when partial frame is saved within Buffer - size_to_complete: usize, } -impl Decoder { +impl Decoder { /// Creates a new BINEX [Decoder] from [R] readable interface, /// ready to parse incoming bytes. /// ``` @@ -85,7 +89,8 @@ impl Decoder { /// let mut fd = File::open("../test_resources/BIN/mfle20190130.bnx") /// .unwrap(); /// - /// let mut decoder = Decoder::new(fd); + /// // Two generics: with M the internal buffer depth + /// let mut decoder = Decoder::<1024, File>::new(fd); /// /// // Consume data stream /// loop { @@ -118,11 +123,12 @@ impl Decoder { /// ``` pub fn new(reader: R) -> Self { Self { - rd_ptr: 1024, - size_to_complete: 0, + eos: false, + rd_ptr: 0, + wr_ptr: 0, + buf: [0; M], reader: reader.into(), state: State::default(), - buffer: [0; 1024].to_vec(), } } @@ -140,7 +146,8 @@ impl Decoder { /// let mut fd = File::open("../test_resources/BIN/mfle20200105.bnx.gz") /// .unwrap(); /// - /// let mut decoder = Decoder::new(fd); + /// // two generics: with M the internal buffer depth + /// let mut decoder = Decoder::<1024, File>::new(fd); /// /// // Consume data stream /// loop { @@ -173,108 +180,51 @@ impl Decoder { /// ``` pub fn new_gzip(reader: R) -> Self { Self { - rd_ptr: 1024, - size_to_complete: 0, + eos: false, + rd_ptr: 0, + wr_ptr: 0, + buf: [0; M], state: State::default(), - buffer: [0; 1024].to_vec(), reader: GzDecoder::new(reader).into(), } } } -impl Iterator for Decoder { +impl Iterator for Decoder { type Item = Result; /// Parse next message contained in stream fn next(&mut self) -> Option { - // parse internal buffer - while self.rd_ptr < 1024 && self.state == State::Parsing { - //println!("parsing: rd={}/wr={}", self.rd_ptr, 1024); - //println!("workbuf: {:?}", &self.buffer[self.rd_ptr..]); - - match Message::decode(&self.buffer[self.rd_ptr..]) { - Ok(msg) => { - // one message fully decoded - // - increment pointer so we can move on to the next - // - and expose to User. - self.rd_ptr += msg.encoding_size(); - return Some(Ok(msg)); - }, - Err(Error::IncompleteMessage(mlen)) => { - //print!("INCOMPLETE: rd_ptr={}/mlen={}", self.rd_ptr, mlen); - // buffer contains partial message - - // [IF] mlen (size to complete) fits in self.buffer - self.size_to_complete = mlen - self.rd_ptr; - if self.size_to_complete < 1024 { - // Then next .read() will complete this message - // and we will then be able to complete the parsing. - // Shift current content (rd_ptr=>0) and preserve then move on to Reading. - self.buffer.copy_within(self.rd_ptr..1024, 0); - self.state = State::IncompleteMessage; - } else { - // OR - // NB: some messages can be very lengthy (some MB) - // especially the signal sampling that we do not support yet. - // In this case, we simply trash the remaning amount of bytes, - // message is lost and we move on to the next SYNC - //println!("library limitation: unprocessed message"); - self.state = State::IncompleteTrashing; - //println!("need to trash {} bytes", self.size_to_complete); - } - }, - Err(Error::NoSyncByte) => { - // no SYNC in entire buffer - //println!(".decode no-sync"); - // prepare for next read - self.rd_ptr = 1024; - //self.buffer.clear(); - self.buffer = [0; 1024].to_vec(); - }, - Err(_) => { - // decoding error: unsupported message - // Keep iterating the buffer - self.rd_ptr += 1; - }, - } + // always try to fill in buffer + let size = self.reader.read(&mut self.buf[self.wr_ptr..]).ok()?; + self.wr_ptr += size; + if size == 0 { + self.eos = true; } - - // read data: fill in buffer - match self.reader.read_exact(&mut self.buffer) { - Ok(_) => { - match self.state { - State::Parsing => {}, - State::IncompleteMessage => { - // complete frame, move on to parsing - self.state = State::Parsing; - }, - State::IncompleteTrashing => { - if self.size_to_complete == 0 { - // trashed completely. - self.state = State::Parsing; - //println!("back to parsing"); - } else { - if self.size_to_complete < 1024 { - //println!("shiting {} bytes", self.size_to_complete); - - // discard remaning bytes from buffer - // and move on to parsing to analyze new content - self.buffer.copy_within(self.size_to_complete.., 0); - self.state = State::Parsing; - //println!("back to parsing"); - } else { - self.size_to_complete = - self.size_to_complete.saturating_add_signed(-1024); - //println!("size to trash: {}", self.size_to_complete); - } + // try to consume one message + match Message::decode(&self.buf[self.rd_ptr..]) { + Ok(msg) => { + // one message fully decoded + // - increment pointer + // - expose to user + self.rd_ptr += msg.encoding_size(); + Some(Ok(msg)) + }, + Err(e) => { + match e { + Error::NoSyncByte => { + // we can safely discard all internal content + self.wr_ptr = 0; + self.rd_ptr = 0; + if self.eos == true { + // consumed everything and EOS has been reached + return None; } }, + _ => { + self.rd_ptr += 1; + }, } - // read success - self.rd_ptr = 0; // reset pointer & prepare for next Iter - Some(Err(Error::NotEnoughBytes)) - }, - Err(_) => { - None // EOS + Some(Err(e)) }, } } diff --git a/binex/tests/decoder.rs b/binex/tests/decoder.rs index 6bb0c7252..fd40f8d36 100644 --- a/binex/tests/decoder.rs +++ b/binex/tests/decoder.rs @@ -6,7 +6,7 @@ fn mfle20190130() { let mut found = 0; let fd = File::open("../test_resources/BIN/mfle20190130.bnx").unwrap(); - let mut decoder = Decoder::new(fd); + let mut decoder = Decoder::<1024, File>::new(fd); loop { match decoder.next() { @@ -35,7 +35,7 @@ fn gziped_files() { for fp in ["mfle20200105.bnx.gz", "mfle20200113.bnx.gz"] { let fp = format!("../test_resources/BIN/{}", fp); let fd = File::open(fp).unwrap(); - let mut decoder = Decoder::new_gzip(fd); + let mut decoder = Decoder::<1024, File>::new_gzip(fd); loop { match decoder.next() { From eb33cf32ae397b4678de80637f4cc0f96b865153 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 17:26:59 +0100 Subject: [PATCH 03/28] Improve decoder implementation Signed-off-by: Guillaume W. Bres --- binex/src/decoder.rs | 23 ++++++++++++++++++++++- binex/src/lib.rs | 2 ++ binex/tests/decoder.rs | 3 +-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index 2bd3e9630..61aa83b0d 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -197,9 +197,12 @@ impl Iterator for Decoder { // always try to fill in buffer let size = self.reader.read(&mut self.buf[self.wr_ptr..]).ok()?; self.wr_ptr += size; + //println!("wr_ptr={}", self.wr_ptr); + if size == 0 { self.eos = true; } + // try to consume one message match Message::decode(&self.buf[self.rd_ptr..]) { Ok(msg) => { @@ -212,7 +215,8 @@ impl Iterator for Decoder { Err(e) => { match e { Error::NoSyncByte => { - // we can safely discard all internal content + // buffer does not even contain the sync byte: + // we can safely discard everything self.wr_ptr = 0; self.rd_ptr = 0; if self.eos == true { @@ -220,6 +224,23 @@ impl Iterator for Decoder { return None; } }, + Error::IncompleteMessage(mlen) => { + // buffer does not contain the entire message + // preserve content and shift: to permit refilling the buffer + // two cases: + if mlen + 2 < M { + // - if that message would fit in buffer, shift and prepare to refill for completion + self.wr_ptr -= self.rd_ptr; + self.buf.copy_within(self.rd_ptr.., 0); + return Some(Err(Error::IncompleteMessage(mlen))); + } else { + // - or, we don't support messages that do not fit in the local buffer (yet) + self.buf = [0; M]; + self.wr_ptr = 0; + self.rd_ptr = 0; + return Some(Err(Error::NonSupportedMesssage)); + } + }, _ => { self.rd_ptr += 1; }, diff --git a/binex/src/lib.rs b/binex/src/lib.rs index 9da0a6c9d..f28ea8a08 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -55,4 +55,6 @@ pub enum Error { BadCRC, #[error("incomplete message")] IncompleteMessage(usize), + #[error("non supported message")] + NonSupportedMesssage, } diff --git a/binex/tests/decoder.rs b/binex/tests/decoder.rs index fd40f8d36..722f8b6ab 100644 --- a/binex/tests/decoder.rs +++ b/binex/tests/decoder.rs @@ -17,11 +17,10 @@ fn mfle20190130() { Some(Err(e)) => match e { Error::IoError(e) => panic!("i/o error: {}", e), e => { - println!("err={}", e); + // println!("err={}", e); }, }, None => { - println!("EOS"); break; }, } From 5873e535896aabe5c336ea53766b805e652cdf59 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 17:27:13 +0100 Subject: [PATCH 04/28] remove debug print Signed-off-by: Guillaume W. Bres --- binex/src/message/record/ephemeris/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/binex/src/message/record/ephemeris/mod.rs b/binex/src/message/record/ephemeris/mod.rs index 6d788759f..8d3ea77a9 100644 --- a/binex/src/message/record/ephemeris/mod.rs +++ b/binex/src/message/record/ephemeris/mod.rs @@ -67,7 +67,6 @@ impl EphemerisFrame { // decode FID let (bnxi, size) = Message::decode_bnxi(&buf, big_endian); let fid = FieldID::from(bnxi); - println!("bnx01-eph fid={:?}", fid); match fid { FieldID::GPSRaw => { From 6fd2561857bacb7cfe8fea7ae53cfe57aedb0655 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 23:12:01 +0100 Subject: [PATCH 05/28] Fixed ephemeris encoding/decoding Signed-off-by: Guillaume W. Bres --- binex/build.rs | 46 ++++++ binex/src/lib.rs | 2 + binex/src/message/checksum.rs | 10 +- binex/src/message/record/ephemeris/galileo.rs | 118 +++++++------- binex/src/message/record/ephemeris/gps/eph.rs | 113 +++++++------- binex/src/message/record/ephemeris/gps/raw.rs | 62 +++++--- binex/src/message/record/ephemeris/mod.rs | 145 +++++++++++++++--- binex/src/message/record/ephemeris/sbas.rs | 77 +++++----- binex/src/message/record/mod.rs | 5 +- 9 files changed, 379 insertions(+), 199 deletions(-) create mode 100644 binex/build.rs diff --git a/binex/build.rs b/binex/build.rs new file mode 100644 index 000000000..222e68907 --- /dev/null +++ b/binex/build.rs @@ -0,0 +1,46 @@ +use std::env; +use std::io::Write; +use std::path::Path; + +fn generate_crc16_look_up_table() { + let outdir = env::var("OUT_DIR").unwrap(); + let path = Path::new(&outdir).join("crc16.rs"); + let mut fd = std::fs::File::create(path).unwrap(); + + fd.write_all("use lazy_static::lazy_static; \n".as_bytes()) + .unwrap(); + + fd.write_all("lazy_static! {\n".as_bytes()).unwrap(); + fd.write_all("static ref CRC16_TABLE : [u16; 256] = \n".as_bytes()) + .unwrap(); + + // generate lut + let polynomial = 0x1021_u16; + let mut table = [0_u16; 256]; + for i in 0..256 { + let mut crc = (i as u16) << 8; + for _ in 0..8 { + if (crc & 0x8000) > 0 { + crc = (crc << 1) ^ polynomial; + } else { + crc = crc << 1; + } + } + table[i] = crc; + if i == 0 { + fd.write_all(format!("[ 0x{:04X}_u16, ", crc).as_bytes()) + .unwrap(); + } else if i == 255 { + fd.write_all(format!("0x{:04X}_u16 ];", crc).as_bytes()) + .unwrap(); + } else { + fd.write_all(format!("0x{:04X}_u16, ", crc).as_bytes()) + .unwrap(); + } + } + fd.write_all("}".as_bytes()).unwrap(); +} + +fn main() { + generate_crc16_look_up_table(); +} diff --git a/binex/src/lib.rs b/binex/src/lib.rs index f28ea8a08..64546d303 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -11,6 +11,8 @@ mod message; pub(crate) mod constants; pub(crate) mod utils; +include!(concat!(env!("OUT_DIR"), "/crc16.rs")); + pub mod prelude { pub use crate::{ decoder::Decoder, diff --git a/binex/src/message/checksum.rs b/binex/src/message/checksum.rs index 505cdf2d0..921f2b148 100644 --- a/binex/src/message/checksum.rs +++ b/binex/src/message/checksum.rs @@ -1,6 +1,8 @@ //! Checksum calculator use md5::{Digest, Md5}; +use crate::CRC16_TABLE; + /// Checksum caculator #[derive(Debug, Copy, Clone)] pub enum Checksum { @@ -106,7 +108,13 @@ impl Checksum { } /// Calculates expected Checksum using XOR16 algorithm fn xor16_calc(bytes: &[u8]) -> u128 { - 0 + let mut crc = 0xffff_u16; + for byte in bytes.iter() { + let tmp = (*byte as u16) ^ crc; + crc >>= 8; + crc ^= CRC16_TABLE[(tmp as usize) % 256]; + } + crc as u128 } /// Calculates expected Checksum using XO32 algorithm fn xor32_calc(bytes: &[u8]) -> u128 { diff --git a/binex/src/message/record/ephemeris/galileo.rs b/binex/src/message/record/ephemeris/galileo.rs index eca9f90cc..1dc6d64ef 100644 --- a/binex/src/message/record/ephemeris/galileo.rs +++ b/binex/src/message/record/ephemeris/galileo.rs @@ -35,7 +35,7 @@ pub struct GALEphemeris { impl GALEphemeris { pub(crate) const fn encoding_size() -> usize { - 154 + 128 } pub fn encode(&self, big_endian: bool, buf: &mut [u8]) -> Result { let size = Self::encoding_size(); @@ -59,7 +59,7 @@ impl GALEphemeris { self.tow.to_le_bytes() }; - buf[4..8].copy_from_slice(&tow); + buf[3..7].copy_from_slice(&tow); let toe_s = if big_endian { self.toe_s.to_be_bytes() @@ -67,7 +67,7 @@ impl GALEphemeris { self.toe_s.to_le_bytes() }; - buf[9..13].copy_from_slice(&toe_s); + buf[7..11].copy_from_slice(&toe_s); let bgd_e5a_e1_s = if big_endian { self.bgd_e5a_e1_s.to_be_bytes() @@ -75,7 +75,7 @@ impl GALEphemeris { self.bgd_e5a_e1_s.to_le_bytes() }; - buf[14..18].copy_from_slice(&bgd_e5a_e1_s); + buf[11..15].copy_from_slice(&bgd_e5a_e1_s); let bgd_e5b_e1_s = if big_endian { self.bgd_e5b_e1_s.to_be_bytes() @@ -83,7 +83,7 @@ impl GALEphemeris { self.bgd_e5b_e1_s.to_le_bytes() }; - buf[19..23].copy_from_slice(&bgd_e5b_e1_s); + buf[15..19].copy_from_slice(&bgd_e5b_e1_s); let iodnav = if big_endian { self.iodnav.to_be_bytes() @@ -91,7 +91,7 @@ impl GALEphemeris { self.iodnav.to_le_bytes() }; - buf[24..28].copy_from_slice(&iodnav); + buf[19..23].copy_from_slice(&iodnav); let clock_drift_rate = if big_endian { self.clock_drift_rate.to_be_bytes() @@ -99,7 +99,7 @@ impl GALEphemeris { self.clock_drift_rate.to_le_bytes() }; - buf[29..33].copy_from_slice(&clock_drift_rate); + buf[23..27].copy_from_slice(&clock_drift_rate); let clock_drift = if big_endian { self.clock_drift.to_be_bytes() @@ -107,7 +107,7 @@ impl GALEphemeris { self.clock_drift.to_le_bytes() }; - buf[34..38].copy_from_slice(&clock_drift); + buf[27..31].copy_from_slice(&clock_drift); let clock_offset = if big_endian { self.clock_offset.to_be_bytes() @@ -115,7 +115,7 @@ impl GALEphemeris { self.clock_offset.to_le_bytes() }; - buf[39..43].copy_from_slice(&clock_offset); + buf[31..35].copy_from_slice(&clock_offset); let delta_n_semi_circles_s = if big_endian { self.delta_n_semi_circles_s.to_be_bytes() @@ -123,7 +123,7 @@ impl GALEphemeris { self.delta_n_semi_circles_s.to_le_bytes() }; - buf[44..48].copy_from_slice(&delta_n_semi_circles_s); + buf[35..39].copy_from_slice(&delta_n_semi_circles_s); let m0_rad = if big_endian { self.m0_rad.to_be_bytes() @@ -131,7 +131,7 @@ impl GALEphemeris { self.m0_rad.to_le_bytes() }; - buf[49..57].copy_from_slice(&m0_rad); + buf[39..47].copy_from_slice(&m0_rad); let e = if big_endian { self.e.to_be_bytes() @@ -139,7 +139,7 @@ impl GALEphemeris { self.e.to_le_bytes() }; - buf[58..66].copy_from_slice(&e); + buf[47..55].copy_from_slice(&e); let sqrt_a = if big_endian { self.sqrt_a.to_be_bytes() @@ -147,7 +147,7 @@ impl GALEphemeris { self.sqrt_a.to_le_bytes() }; - buf[67..75].copy_from_slice(&sqrt_a); + buf[55..63].copy_from_slice(&sqrt_a); let cic = if big_endian { self.cic.to_be_bytes() @@ -155,7 +155,7 @@ impl GALEphemeris { self.cic.to_le_bytes() }; - buf[76..80].copy_from_slice(&cic); + buf[63..67].copy_from_slice(&cic); let crc = if big_endian { self.crc.to_be_bytes() @@ -163,7 +163,7 @@ impl GALEphemeris { self.crc.to_le_bytes() }; - buf[81..85].copy_from_slice(&crc); + buf[67..71].copy_from_slice(&crc); let cis = if big_endian { self.cis.to_be_bytes() @@ -171,7 +171,7 @@ impl GALEphemeris { self.cis.to_le_bytes() }; - buf[86..90].copy_from_slice(&cis); + buf[71..75].copy_from_slice(&cis); let crs = if big_endian { self.crs.to_be_bytes() @@ -179,7 +179,7 @@ impl GALEphemeris { self.crs.to_le_bytes() }; - buf[91..95].copy_from_slice(&crs); + buf[75..79].copy_from_slice(&crs); let cuc = if big_endian { self.cuc.to_be_bytes() @@ -187,7 +187,7 @@ impl GALEphemeris { self.cuc.to_le_bytes() }; - buf[96..100].copy_from_slice(&cuc); + buf[79..83].copy_from_slice(&cuc); let cus = if big_endian { self.cus.to_be_bytes() @@ -195,7 +195,7 @@ impl GALEphemeris { self.cus.to_le_bytes() }; - buf[101..105].copy_from_slice(&cus); + buf[83..87].copy_from_slice(&cus); let omega_0_rad = if big_endian { self.omega_0_rad.to_be_bytes() @@ -203,7 +203,7 @@ impl GALEphemeris { self.omega_0_rad.to_le_bytes() }; - buf[106..114].copy_from_slice(&omega_0_rad); + buf[87..95].copy_from_slice(&omega_0_rad); let omega_rad = if big_endian { self.omega_rad.to_be_bytes() @@ -211,7 +211,7 @@ impl GALEphemeris { self.omega_rad.to_le_bytes() }; - buf[115..123].copy_from_slice(&omega_rad); + buf[95..103].copy_from_slice(&omega_rad); let i0_rad = if big_endian { self.i0_rad.to_be_bytes() @@ -219,7 +219,7 @@ impl GALEphemeris { self.i0_rad.to_le_bytes() }; - buf[124..132].copy_from_slice(&i0_rad); + buf[103..111].copy_from_slice(&i0_rad); let omega_dot_semi_circles = if big_endian { self.omega_dot_semi_circles.to_be_bytes() @@ -227,7 +227,7 @@ impl GALEphemeris { self.omega_dot_semi_circles.to_le_bytes() }; - buf[133..137].copy_from_slice(&omega_dot_semi_circles); + buf[111..115].copy_from_slice(&omega_dot_semi_circles); let idot_semi_circles_s = if big_endian { self.idot_semi_circles_s.to_be_bytes() @@ -235,7 +235,7 @@ impl GALEphemeris { self.idot_semi_circles_s.to_le_bytes() }; - buf[138..142].copy_from_slice(&idot_semi_circles_s); + buf[115..119].copy_from_slice(&idot_semi_circles_s); let sisa = if big_endian { self.sisa.to_be_bytes() @@ -243,7 +243,7 @@ impl GALEphemeris { self.sisa.to_le_bytes() }; - buf[143..147].copy_from_slice(&sisa); + buf[119..123].copy_from_slice(&sisa); let sv_health = if big_endian { self.sv_health.to_be_bytes() @@ -251,7 +251,7 @@ impl GALEphemeris { self.sv_health.to_le_bytes() }; - buf[148..150].copy_from_slice(&sv_health); + buf[123..125].copy_from_slice(&sv_health); let source = if big_endian { self.source.to_be_bytes() @@ -259,9 +259,11 @@ impl GALEphemeris { self.source.to_le_bytes() }; - buf[151..153].copy_from_slice(&source); - Ok(154) + buf[125..127].copy_from_slice(&source); + + Ok(Self::encoding_size()) } + pub fn decode(big_endian: bool, buf: &[u8]) -> Result { if buf.len() < Self::encoding_size() { return Err(Error::NotEnoughBytes); @@ -271,54 +273,54 @@ impl GALEphemeris { // 2. TOE let toe_week = Utils::decode_u16(big_endian, &buf[1..3])?; // 3. TOW - let tow = Utils::decode_i32(big_endian, &buf[4..8])?; + let tow = Utils::decode_i32(big_endian, &buf[3..7])?; // 4. TOE(s) - let toe_s = Utils::decode_i32(big_endian, &buf[9..13])?; + let toe_s = Utils::decode_i32(big_endian, &buf[7..11])?; // 4. TGD - let bgd_e5a_e1_s: f32 = Utils::decode_f32(big_endian, &buf[14..18])?; - let bgd_e5b_e1_s: f32 = Utils::decode_f32(big_endian, &buf[19..23])?; + let bgd_e5a_e1_s: f32 = Utils::decode_f32(big_endian, &buf[11..15])?; + let bgd_e5b_e1_s: f32 = Utils::decode_f32(big_endian, &buf[15..19])?; // 5. IODNAV - let iodnav = Utils::decode_i32(big_endian, &buf[24..28])?; + let iodnav = Utils::decode_i32(big_endian, &buf[19..23])?; // 6. Clock - let clock_drift_rate = Utils::decode_f32(big_endian, &buf[29..33])?; - let clock_drift = Utils::decode_f32(big_endian, &buf[34..38])?; - let clock_offset = Utils::decode_f32(big_endian, &buf[39..43])?; + let clock_drift_rate = Utils::decode_f32(big_endian, &buf[23..27])?; + let clock_drift = Utils::decode_f32(big_endian, &buf[27..31])?; + let clock_offset = Utils::decode_f32(big_endian, &buf[31..35])?; // 7: delta_n - let delta_n_semi_circles_s = Utils::decode_f32(big_endian, &buf[44..48])?; + let delta_n_semi_circles_s = Utils::decode_f32(big_endian, &buf[35..39])?; // 11: m0 - let m0_rad = Utils::decode_f64(big_endian, &buf[49..57])?; + let m0_rad = Utils::decode_f64(big_endian, &buf[39..47])?; // 12: e - let e = Utils::decode_f64(big_endian, &buf[58..66])?; + let e = Utils::decode_f64(big_endian, &buf[47..55])?; // 13: sqrt_a - let sqrt_a = Utils::decode_f64(big_endian, &buf[67..75])?; + let sqrt_a = Utils::decode_f64(big_endian, &buf[55..63])?; // 14: cic - let cic = Utils::decode_f32(big_endian, &buf[76..80])?; + let cic = Utils::decode_f32(big_endian, &buf[63..67])?; // 15: crc - let crc = Utils::decode_f32(big_endian, &buf[81..85])?; + let crc = Utils::decode_f32(big_endian, &buf[67..71])?; // 16: cis - let cis = Utils::decode_f32(big_endian, &buf[86..90])?; + let cis = Utils::decode_f32(big_endian, &buf[71..75])?; // 17: crs - let crs = Utils::decode_f32(big_endian, &buf[91..95])?; + let crs = Utils::decode_f32(big_endian, &buf[75..79])?; // 18: cuc - let cuc = Utils::decode_f32(big_endian, &buf[96..100])?; + let cuc = Utils::decode_f32(big_endian, &buf[79..83])?; // 19: cus - let cus = Utils::decode_f32(big_endian, &buf[101..105])?; + let cus = Utils::decode_f32(big_endian, &buf[83..87])?; // 20: omega0 - let omega_0_rad = Utils::decode_f64(big_endian, &buf[106..114])?; + let omega_0_rad = Utils::decode_f64(big_endian, &buf[87..95])?; // 21: omega - let omega_rad = Utils::decode_f64(big_endian, &buf[115..123])?; + let omega_rad = Utils::decode_f64(big_endian, &buf[95..103])?; // 22: i0 - let i0_rad = Utils::decode_f64(big_endian, &buf[124..132])?; + let i0_rad = Utils::decode_f64(big_endian, &buf[103..111])?; // 23: omega_dot - let omega_dot_semi_circles = Utils::decode_f32(big_endian, &buf[133..137])?; + let omega_dot_semi_circles = Utils::decode_f32(big_endian, &buf[111..115])?; // 24: idot - let idot_semi_circles_s = Utils::decode_f32(big_endian, &buf[138..142])?; + let idot_semi_circles_s = Utils::decode_f32(big_endian, &buf[115..119])?; // 25: sisa - let sisa = Utils::decode_f32(big_endian, &buf[143..147])?; + let sisa = Utils::decode_f32(big_endian, &buf[119..123])?; // 26: sv_health - let sv_health = Utils::decode_u16(big_endian, &buf[148..150])?; + let sv_health = Utils::decode_u16(big_endian, &buf[123..125])?; // 27: uint2 - let source = Utils::decode_u16(big_endian, &buf[151..153])?; + let source = Utils::decode_u16(big_endian, &buf[125..127])?; Ok(Self { sv_prn, @@ -365,7 +367,7 @@ mod test { #[test] fn gal_ephemeris() { - let buf = [0; 154]; + let buf = [0; 128]; let eph = GALEphemeris::decode(true, &buf).unwrap(); @@ -373,9 +375,9 @@ mod test { let mut target = [0; 100]; assert!(eph.encode(true, &mut target).is_err()); - let mut target = [0; 154]; + let mut target = [0; 128]; let size = eph.encode(true, &mut target).unwrap(); - assert_eq!(size, 154); + assert_eq!(size, 128); assert_eq!(buf, target); let eph = GALEphemeris { @@ -409,7 +411,7 @@ mod test { source: 156, }; - let mut target = [0; 154]; + let mut target = [0; 128]; eph.encode(true, &mut target).unwrap(); let decoded = GALEphemeris::decode(true, &target).unwrap(); diff --git a/binex/src/message/record/ephemeris/gps/eph.rs b/binex/src/message/record/ephemeris/gps/eph.rs index eb57a1f79..229e10e32 100644 --- a/binex/src/message/record/ephemeris/gps/eph.rs +++ b/binex/src/message/record/ephemeris/gps/eph.rs @@ -57,7 +57,7 @@ pub struct GPSEphemeris { impl GPSEphemeris { pub(crate) const fn encoding_size() -> usize { - 153 + 128 } pub fn encode(&self, big_endian: bool, buf: &mut [u8]) -> Result { let size = Self::encoding_size(); @@ -81,7 +81,7 @@ impl GPSEphemeris { self.tow.to_le_bytes() }; - buf[4..8].copy_from_slice(&tow); + buf[3..7].copy_from_slice(&tow); let toc = if big_endian { self.toc.to_be_bytes() @@ -89,7 +89,7 @@ impl GPSEphemeris { self.toc.to_le_bytes() }; - buf[9..13].copy_from_slice(&toc); + buf[7..11].copy_from_slice(&toc); let tgd = if big_endian { self.tgd.to_be_bytes() @@ -97,7 +97,7 @@ impl GPSEphemeris { self.tgd.to_le_bytes() }; - buf[14..18].copy_from_slice(&tgd); + buf[11..15].copy_from_slice(&tgd); let iodc = if big_endian { self.iodc.to_be_bytes() @@ -105,7 +105,7 @@ impl GPSEphemeris { self.iodc.to_le_bytes() }; - buf[19..23].copy_from_slice(&iodc); + buf[15..19].copy_from_slice(&iodc); let af2 = if big_endian { self.clock_drift_rate.to_be_bytes() @@ -113,7 +113,7 @@ impl GPSEphemeris { self.clock_drift_rate.to_le_bytes() }; - buf[24..28].copy_from_slice(&af2); + buf[19..23].copy_from_slice(&af2); let af1 = if big_endian { self.clock_drift.to_be_bytes() @@ -121,7 +121,7 @@ impl GPSEphemeris { self.clock_drift.to_le_bytes() }; - buf[29..33].copy_from_slice(&af1); + buf[23..27].copy_from_slice(&af1); let af0 = if big_endian { self.clock_offset.to_be_bytes() @@ -129,7 +129,7 @@ impl GPSEphemeris { self.clock_offset.to_le_bytes() }; - buf[34..38].copy_from_slice(&af0); + buf[27..31].copy_from_slice(&af0); let iode = if big_endian { self.iode.to_be_bytes() @@ -137,7 +137,7 @@ impl GPSEphemeris { self.iode.to_le_bytes() }; - buf[39..43].copy_from_slice(&iode); + buf[31..35].copy_from_slice(&iode); let delta_n = if big_endian { (self.delta_n_rad_s / Pi32).to_be_bytes() @@ -145,7 +145,7 @@ impl GPSEphemeris { (self.delta_n_rad_s / Pi32).to_le_bytes() }; - buf[44..48].copy_from_slice(&delta_n); + buf[35..39].copy_from_slice(&delta_n); let m0 = if big_endian { self.m0_rad.to_be_bytes() @@ -153,7 +153,7 @@ impl GPSEphemeris { self.m0_rad.to_le_bytes() }; - buf[49..57].copy_from_slice(&m0); + buf[39..47].copy_from_slice(&m0); let e = if big_endian { self.e.to_be_bytes() @@ -161,7 +161,7 @@ impl GPSEphemeris { self.e.to_le_bytes() }; - buf[58..66].copy_from_slice(&e); + buf[47..55].copy_from_slice(&e); let sqrt_a = if big_endian { self.sqrt_a.to_be_bytes() @@ -169,7 +169,7 @@ impl GPSEphemeris { self.sqrt_a.to_le_bytes() }; - buf[67..75].copy_from_slice(&sqrt_a); + buf[55..63].copy_from_slice(&sqrt_a); let cic = if big_endian { self.cic.to_be_bytes() @@ -177,7 +177,7 @@ impl GPSEphemeris { self.cic.to_le_bytes() }; - buf[76..80].copy_from_slice(&cic); + buf[63..67].copy_from_slice(&cic); let crc = if big_endian { self.crc.to_be_bytes() @@ -185,7 +185,7 @@ impl GPSEphemeris { self.crc.to_le_bytes() }; - buf[81..85].copy_from_slice(&crc); + buf[67..71].copy_from_slice(&crc); let cis = if big_endian { self.cis.to_be_bytes() @@ -193,7 +193,7 @@ impl GPSEphemeris { self.cis.to_le_bytes() }; - buf[86..90].copy_from_slice(&cis); + buf[71..75].copy_from_slice(&cis); let crs = if big_endian { self.crs.to_be_bytes() @@ -201,7 +201,7 @@ impl GPSEphemeris { self.crs.to_le_bytes() }; - buf[91..95].copy_from_slice(&crs); + buf[75..79].copy_from_slice(&crs); let cuc = if big_endian { self.cuc.to_be_bytes() @@ -209,7 +209,7 @@ impl GPSEphemeris { self.cuc.to_le_bytes() }; - buf[96..100].copy_from_slice(&cuc); + buf[79..83].copy_from_slice(&cuc); let cus = if big_endian { self.cus.to_be_bytes() @@ -217,7 +217,7 @@ impl GPSEphemeris { self.cus.to_le_bytes() }; - buf[101..105].copy_from_slice(&cus); + buf[83..87].copy_from_slice(&cus); let omega_0_rad = if big_endian { self.omega_0_rad.to_be_bytes() @@ -225,7 +225,7 @@ impl GPSEphemeris { self.omega_0_rad.to_le_bytes() }; - buf[106..114].copy_from_slice(&omega_0_rad); + buf[87..95].copy_from_slice(&omega_0_rad); let omega_rad = if big_endian { self.omega_rad.to_be_bytes() @@ -233,7 +233,7 @@ impl GPSEphemeris { self.omega_rad.to_le_bytes() }; - buf[115..123].copy_from_slice(&omega_rad); + buf[95..103].copy_from_slice(&omega_rad); let i0_rad = if big_endian { self.i0_rad.to_be_bytes() @@ -241,7 +241,7 @@ impl GPSEphemeris { self.i0_rad.to_le_bytes() }; - buf[124..132].copy_from_slice(&i0_rad); + buf[103..111].copy_from_slice(&i0_rad); let omega_dot_rad_s = if big_endian { (self.omega_dot_rad_s / Pi32).to_be_bytes() @@ -249,7 +249,7 @@ impl GPSEphemeris { (self.omega_dot_rad_s / Pi32).to_le_bytes() }; - buf[133..137].copy_from_slice(&omega_dot_rad_s); + buf[111..115].copy_from_slice(&omega_dot_rad_s); let i_dot_rad_s = if big_endian { (self.i_dot_rad_s / Pi32).to_be_bytes() @@ -257,7 +257,7 @@ impl GPSEphemeris { (self.i_dot_rad_s / Pi32).to_le_bytes() }; - buf[138..142].copy_from_slice(&i_dot_rad_s); + buf[115..119].copy_from_slice(&i_dot_rad_s); let ura_m = if big_endian { (self.ura_m / 0.1).to_be_bytes() @@ -265,7 +265,7 @@ impl GPSEphemeris { (self.ura_m / 0.1).to_le_bytes() }; - buf[143..147].copy_from_slice(&ura_m); + buf[119..123].copy_from_slice(&ura_m); let sv_health = if big_endian { self.sv_health.to_be_bytes() @@ -273,7 +273,7 @@ impl GPSEphemeris { self.sv_health.to_le_bytes() }; - buf[148..150].copy_from_slice(&sv_health); + buf[123..125].copy_from_slice(&sv_health); let uint2 = if big_endian { self.uint2.to_be_bytes() @@ -281,8 +281,7 @@ impl GPSEphemeris { self.uint2.to_le_bytes() }; - buf[151..153].copy_from_slice(&uint2); - + buf[125..127].copy_from_slice(&uint2); Ok(size) } pub fn decode(big_endian: bool, buf: &[u8]) -> Result { @@ -294,57 +293,57 @@ impl GPSEphemeris { // 2. TOE let toe = Utils::decode_u16(big_endian, &buf[1..3])?; // 3. TOW - let tow = Utils::decode_i32(big_endian, &buf[4..8])?; + let tow = Utils::decode_i32(big_endian, &buf[3..7])?; // 4. TOC - let toc = Utils::decode_i32(big_endian, &buf[9..13])?; + let toc = Utils::decode_i32(big_endian, &buf[7..11])?; // 4. TGD - let tgd = Utils::decode_f32(big_endian, &buf[14..18])?; + let tgd = Utils::decode_f32(big_endian, &buf[11..15])?; // 5. IODC - let iodc = Utils::decode_i32(big_endian, &buf[19..23])?; + let iodc = Utils::decode_i32(big_endian, &buf[15..19])?; // 6. Af2 - let af2 = Utils::decode_f32(big_endian, &buf[24..28])?; + let af2 = Utils::decode_f32(big_endian, &buf[19..23])?; // 7. Af1 - let af1 = Utils::decode_f32(big_endian, &buf[29..33])?; + let af1 = Utils::decode_f32(big_endian, &buf[23..27])?; // 8. Af0 - let af0 = Utils::decode_f32(big_endian, &buf[34..38])?; + let af0 = Utils::decode_f32(big_endian, &buf[27..31])?; // 9: IODE - let iode = Utils::decode_i32(big_endian, &buf[39..43])?; + let iode = Utils::decode_i32(big_endian, &buf[31..35])?; // 10: delta_n - let delta_n_rad_s = Utils::decode_f32(big_endian, &buf[44..48])? * Pi32; + let delta_n_rad_s = Utils::decode_f32(big_endian, &buf[35..39])? * Pi32; // 11: m0 - let m0_rad = Utils::decode_f64(big_endian, &buf[49..57])?; + let m0_rad = Utils::decode_f64(big_endian, &buf[39..47])?; // 12: e - let e = Utils::decode_f64(big_endian, &buf[58..66])?; + let e = Utils::decode_f64(big_endian, &buf[47..55])?; // 13: sqrt_a - let sqrt_a = Utils::decode_f64(big_endian, &buf[67..75])?; + let sqrt_a = Utils::decode_f64(big_endian, &buf[55..63])?; // 14: cic - let cic = Utils::decode_f32(big_endian, &buf[76..80])?; + let cic = Utils::decode_f32(big_endian, &buf[63..67])?; // 15: crc - let crc = Utils::decode_f32(big_endian, &buf[81..85])?; + let crc = Utils::decode_f32(big_endian, &buf[67..71])?; // 16: cis - let cis = Utils::decode_f32(big_endian, &buf[86..90])?; + let cis = Utils::decode_f32(big_endian, &buf[71..75])?; // 17: crs - let crs = Utils::decode_f32(big_endian, &buf[91..95])?; + let crs = Utils::decode_f32(big_endian, &buf[75..79])?; // 18: cuc - let cuc = Utils::decode_f32(big_endian, &buf[96..100])?; + let cuc = Utils::decode_f32(big_endian, &buf[79..83])?; // 19: cus - let cus = Utils::decode_f32(big_endian, &buf[101..105])?; + let cus = Utils::decode_f32(big_endian, &buf[83..87])?; // 20: omega0 - let omega_0_rad = Utils::decode_f64(big_endian, &buf[106..114])?; + let omega_0_rad = Utils::decode_f64(big_endian, &buf[87..95])?; // 21: omega - let omega_rad = Utils::decode_f64(big_endian, &buf[115..123])?; + let omega_rad = Utils::decode_f64(big_endian, &buf[95..103])?; // 22: i0 - let i0_rad = Utils::decode_f64(big_endian, &buf[124..132])?; + let i0_rad = Utils::decode_f64(big_endian, &buf[103..111])?; // 23: omega_dot - let omega_dot_rad_s = Utils::decode_f32(big_endian, &buf[133..137])? * Pi32; + let omega_dot_rad_s = Utils::decode_f32(big_endian, &buf[111..115])? * Pi32; // 24: idot - let i_dot_rad_s = Utils::decode_f32(big_endian, &buf[138..142])? * Pi32; + let i_dot_rad_s = Utils::decode_f32(big_endian, &buf[115..119])? * Pi32; // 25: ura - let ura_m = Utils::decode_f32(big_endian, &buf[143..147])? * 0.1; + let ura_m = Utils::decode_f32(big_endian, &buf[119..123])? * 0.1; // 26: sv_health - let sv_health = Utils::decode_u16(big_endian, &buf[148..150])?; + let sv_health = Utils::decode_u16(big_endian, &buf[123..125])?; // 27: uint2 - let uint2 = Utils::decode_u16(big_endian, &buf[151..153])?; + let uint2 = Utils::decode_u16(big_endian, &buf[125..127])?; Ok(Self { sv_prn, @@ -391,7 +390,7 @@ mod test { #[test] fn gps_ephemeris() { - let buf = [0; 153]; + let buf = [0; 128]; let eph = GPSEphemeris::decode(true, &buf).unwrap(); @@ -399,9 +398,9 @@ mod test { let mut target = [0; 100]; assert!(eph.encode(true, &mut target).is_err()); - let mut target = [0; 153]; + let mut target = [0; 128]; let size = eph.encode(true, &mut target).unwrap(); - assert_eq!(size, 153); + assert_eq!(size, 128); assert_eq!(buf, target); let eph = GPSEphemeris { diff --git a/binex/src/message/record/ephemeris/gps/raw.rs b/binex/src/message/record/ephemeris/gps/raw.rs index 939859f53..739ae8773 100644 --- a/binex/src/message/record/ephemeris/gps/raw.rs +++ b/binex/src/message/record/ephemeris/gps/raw.rs @@ -3,17 +3,19 @@ use crate::Error; #[derive(Debug, Clone, PartialEq)] pub struct GPSRaw { - uint1: u8, - sint4: i32, - bytes: Vec, + pub svid1: u8, + pub uint1: u8, + pub sint4: i32, + bytes: [u8; 72], } impl Default for GPSRaw { fn default() -> Self { Self { + svid1: 0, uint1: 0, sint4: 0, - bytes: [0; 72].to_vec(), + bytes: [0; 72], } } } @@ -23,33 +25,43 @@ impl GPSRaw { pub fn new() -> Self { Self::default() } + pub const fn encoding_size() -> usize { - 77 + 78 } + pub fn decode(big_endian: bool, buf: &[u8]) -> Result { if buf.len() < Self::encoding_size() { return Err(Error::NotEnoughBytes); } - let uint1 = buf[0]; + let svid1 = buf[0]; + let uint1 = buf[1]; + let sint4 = if big_endian { - i32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]) + i32::from_be_bytes([buf[2], buf[3], buf[4], buf[5]]) } else { - i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]) + i32::from_le_bytes([buf[2], buf[3], buf[4], buf[5]]) }; + let mut bytes = [0; 72]; + bytes.clone_from_slice(&buf[6..78]); + Ok(Self { + svid1, uint1, sint4, - bytes: buf[5..77].to_vec(), + bytes, }) } + pub fn encode(&self, big_endian: bool, buf: &mut [u8]) -> Result { let size = Self::encoding_size(); if buf.len() < size { Err(Error::NotEnoughBytes) } else { - buf[0] = self.uint1; + buf[0] = self.svid1; + buf[1] = self.uint1; let bytes = if big_endian { self.sint4.to_be_bytes() @@ -57,8 +69,9 @@ impl GPSRaw { self.sint4.to_le_bytes() }; - buf[1..5].copy_from_slice(&bytes); - buf[5..72 + 5].copy_from_slice(&self.bytes); + buf[2..6].copy_from_slice(&bytes); + buf[6..78].copy_from_slice(&self.bytes); + Ok(size) } } @@ -67,6 +80,7 @@ impl GPSRaw { #[cfg(test)] mod test { use super::*; + #[test] fn gps_raw() { let big_endian = true; @@ -75,24 +89,26 @@ mod test { let decode = GPSRaw::decode(big_endian, &buf); assert!(decode.is_err()); - let mut buf = [0; 77]; + let mut buf = [0; 78]; buf[0] = 10; - buf[4] = 1; - buf[5] = 123; - buf[6] = 124; + buf[1] = 1; + buf[6] = 10; + buf[7] = 11; + buf[6 + 71] = 123; let decoded = GPSRaw::decode(big_endian, &buf).unwrap(); - assert_eq!(decoded.uint1, 10); - assert_eq!(decoded.sint4, 1); + assert_eq!(decoded.svid1, 10); + assert_eq!(decoded.uint1, 1); assert_eq!(decoded.bytes.len(), 72); - assert_eq!(decoded.bytes[0], 123); - assert_eq!(decoded.bytes[1], 124); - let mut encoded = [0; 77]; - let size = decoded.encode(big_endian, &mut encoded).unwrap(); + assert_eq!(decoded.bytes[0], 10); + assert_eq!(decoded.bytes[1], 11); + assert_eq!(decoded.bytes[71], 123); - assert_eq!(size, 77); + let mut encoded = [0; 78]; + let size = decoded.encode(big_endian, &mut encoded).unwrap(); + assert_eq!(size, 78); assert_eq!(buf, encoded) } } diff --git a/binex/src/message/record/ephemeris/mod.rs b/binex/src/message/record/ephemeris/mod.rs index 8d3ea77a9..d0cdf2bcf 100644 --- a/binex/src/message/record/ephemeris/mod.rs +++ b/binex/src/message/record/ephemeris/mod.rs @@ -77,6 +77,18 @@ impl EphemerisFrame { let fr = GPSEphemeris::decode(big_endian, &buf[size..])?; Ok(Self::GPS(fr)) }, + FieldID::GLO => { + let fr = GLOEphemeris::decode(big_endian, &buf[size..])?; + Ok(Self::GLO(fr)) + }, + FieldID::SBAS => { + let fr = SBASEphemeris::decode(big_endian, &buf[size..])?; + Ok(Self::SBAS(fr)) + }, + FieldID::GAL => { + let fr: GALEphemeris = GALEphemeris::decode(big_endian, &buf[size..])?; + Ok(Self::GAL(fr)) + }, _ => Err(Error::UnknownRecordFieldId), } } @@ -102,27 +114,27 @@ impl EphemerisFrame { } /// Creates new [GPSRaw] frame - pub fn new_gps_raw(&self, raw: GPSRaw) -> Self { + pub fn new_gps_raw(raw: GPSRaw) -> Self { Self::GPSRaw(raw) } /// Creates new [GPSEphemeris] frame - pub fn new_gps(&self, gps: GPSEphemeris) -> Self { + pub fn new_gps(gps: GPSEphemeris) -> Self { Self::GPS(gps) } /// Creates new [GLOEphemeris] frame - pub fn new_glonass(&self, glo: GLOEphemeris) -> Self { + pub fn new_glonass(glo: GLOEphemeris) -> Self { Self::GLO(glo) } /// Creates new [SBASEphemeris] frame - pub fn new_sbas(&self, sbas: SBASEphemeris) -> Self { + pub fn new_sbas(sbas: SBASEphemeris) -> Self { Self::SBAS(sbas) } /// Creates new [GALEphemeris] frame - pub fn new_galileo(&self, gal: GALEphemeris) -> Self { + pub fn new_galileo(gal: GALEphemeris) -> Self { Self::GAL(gal) } } @@ -130,40 +142,133 @@ impl EphemerisFrame { #[cfg(test)] mod test { use super::*; + #[test] - fn gps_raw() { - let mut eph = GPSEphemeris::default(); - eph.sv_prn = 10; - eph.cic = 10.0; - eph.cus = 12.0; - eph.m0_rad = 100.0; - eph.clock_offset = 123.0; + fn gps_eph() { + let eph = GPSEphemeris { + sqrt_a: 1.0, + sv_health: 2, + sv_prn: 3, + toe: 4, + tow: 5, + toc: 6, + tgd: 7.0, + iodc: 8, + clock_drift: 9.0, + clock_drift_rate: 10.0, + clock_offset: 11.0, + iode: 13, + delta_n_rad_s: 14.0, + m0_rad: 15.0, + e: 16.0, + cic: 17.0, + crc: 18.0, + cis: 19.0, + crs: 20.0, + cuc: 21.0, + cus: 22.0, + omega_0_rad: 23.0, + omega_dot_rad_s: 24.0, + omega_rad: 25.0, + i0_rad: 27.0, + i_dot_rad_s: 28.0, + ura_m: 29.0, + uint2: 30, + }; - assert_eq!(GPSEphemeris::encoding_size(), 153); + assert_eq!(GPSEphemeris::encoding_size(), 128); let big_endian = true; let mut encoded = [0; 77]; assert!(eph.encode(big_endian, &mut encoded).is_err()); - let mut encoded = [0; 153]; + let mut encoded = [0; 128]; let size = eph.encode(big_endian, &mut encoded).unwrap(); - assert_eq!(size, 153); + assert_eq!(size, 128); let decoded = GPSEphemeris::decode(big_endian, &encoded).unwrap(); assert_eq!(decoded, eph); } + #[test] - fn gps_eph() { - let raw: GPSRaw = GPSRaw::new(); - assert_eq!(GPSRaw::encoding_size(), 1 + 4 + 72); + fn gps_raw() { + let raw: GPSRaw = GPSRaw::default(); + assert_eq!(GPSRaw::encoding_size(), 1 + 1 + 4 + 72); let big_endian = true; - let mut buf = [0; 77]; + + let mut buf = [0; 78]; let size = raw.encode(big_endian, &mut buf).unwrap(); assert_eq!(size, GPSRaw::encoding_size()); - assert_eq!(buf, [0; 77]); + assert_eq!(buf, [0; 78]); + + let decoded = GPSRaw::decode(big_endian, &buf).unwrap(); + assert_eq!(decoded, raw); + } + + #[test] + fn gal() { + let gal = GALEphemeris { + toe_s: 1, + sv_health: 2, + sv_prn: 3, + toe_week: 4, + tow: 5, + bgd_e5a_e1_s: 6.0, + bgd_e5b_e1_s: 7.0, + iodnav: 8, + clock_drift: 9.0, + clock_drift_rate: 10.0, + clock_offset: 11.0, + delta_n_semi_circles_s: 12.0, + m0_rad: 12.0, + e: 13.0, + sqrt_a: 14.0, + cic: 15.0, + crc: 16.0, + cis: 17.0, + crs: 18.0, + cus: 19.0, + cuc: 20.0, + omega_0_rad: 21.0, + omega_dot_semi_circles: 22.0, + omega_rad: 23.0, + i0_rad: 25.0, + idot_semi_circles_s: 26.0, + sisa: 33.0, + source: 34, + }; + + assert_eq!(GALEphemeris::encoding_size(), 128); + + let big_endian = true; + + let mut buf = [0; 128]; + let size = gal.encode(big_endian, &mut buf).unwrap(); + + assert_eq!(size, GALEphemeris::encoding_size()); + + let decoded = GALEphemeris::decode(big_endian, &buf).unwrap(); + assert_eq!(decoded, gal); + } + + #[test] + fn sbas() { + let sbas = SBASEphemeris::default(); + assert_eq!(SBASEphemeris::encoding_size(), 98); + + let big_endian = true; + + let mut buf = [0; 98]; + let size = sbas.encode(big_endian, &mut buf).unwrap(); + + assert_eq!(size, SBASEphemeris::encoding_size()); + assert_eq!(buf, [0; 98]); + + let decoded = SBASEphemeris::decode(big_endian, &buf).unwrap(); + assert_eq!(decoded, sbas); } } diff --git a/binex/src/message/record/ephemeris/sbas.rs b/binex/src/message/record/ephemeris/sbas.rs index eb805454b..4818659f4 100644 --- a/binex/src/message/record/ephemeris/sbas.rs +++ b/binex/src/message/record/ephemeris/sbas.rs @@ -26,7 +26,7 @@ pub struct SBASEphemeris { impl SBASEphemeris { pub(crate) const fn encoding_size() -> usize { - 111 + 98 } pub fn encode(&self, big_endian: bool, buf: &mut [u8]) -> Result { let size = Self::encoding_size(); @@ -50,7 +50,7 @@ impl SBASEphemeris { self.tow.to_le_bytes() }; - buf[4..8].copy_from_slice(&tow); + buf[3..7].copy_from_slice(&tow); let clock_offset = if big_endian { self.clock_offset.to_be_bytes() @@ -58,7 +58,7 @@ impl SBASEphemeris { self.clock_offset.to_le_bytes() }; - buf[9..17].copy_from_slice(&clock_offset); + buf[7..15].copy_from_slice(&clock_offset); let clock_drift = if big_endian { self.clock_drift.to_be_bytes() @@ -66,7 +66,7 @@ impl SBASEphemeris { self.clock_drift.to_le_bytes() }; - buf[18..26].copy_from_slice(&clock_drift); + buf[15..23].copy_from_slice(&clock_drift); let x_km = if big_endian { self.x_km.to_be_bytes() @@ -74,7 +74,7 @@ impl SBASEphemeris { self.x_km.to_le_bytes() }; - buf[27..35].copy_from_slice(&x_km); + buf[23..31].copy_from_slice(&x_km); let vel_x_km = if big_endian { self.vel_x_km.to_be_bytes() @@ -82,7 +82,7 @@ impl SBASEphemeris { self.vel_x_km.to_le_bytes() }; - buf[36..44].copy_from_slice(&vel_x_km); + buf[31..39].copy_from_slice(&vel_x_km); let acc_x_km = if big_endian { self.acc_x_km.to_be_bytes() @@ -90,7 +90,7 @@ impl SBASEphemeris { self.acc_x_km.to_le_bytes() }; - buf[45..53].copy_from_slice(&acc_x_km); + buf[39..47].copy_from_slice(&acc_x_km); let y_km = if big_endian { self.y_km.to_be_bytes() @@ -98,7 +98,7 @@ impl SBASEphemeris { self.y_km.to_le_bytes() }; - buf[54..62].copy_from_slice(&y_km); + buf[47..55].copy_from_slice(&y_km); let vel_y_km = if big_endian { self.vel_y_km.to_be_bytes() @@ -106,7 +106,7 @@ impl SBASEphemeris { self.vel_y_km.to_le_bytes() }; - buf[63..71].copy_from_slice(&vel_y_km); + buf[55..63].copy_from_slice(&vel_y_km); let acc_y_km = if big_endian { self.acc_y_km.to_be_bytes() @@ -114,7 +114,7 @@ impl SBASEphemeris { self.acc_y_km.to_le_bytes() }; - buf[72..80].copy_from_slice(&acc_y_km); + buf[63..71].copy_from_slice(&acc_y_km); let z_km = if big_endian { self.z_km.to_be_bytes() @@ -122,7 +122,7 @@ impl SBASEphemeris { self.z_km.to_le_bytes() }; - buf[81..89].copy_from_slice(&z_km); + buf[71..79].copy_from_slice(&z_km); let vel_z_km = if big_endian { self.vel_z_km.to_be_bytes() @@ -130,7 +130,7 @@ impl SBASEphemeris { self.vel_z_km.to_le_bytes() }; - buf[90..98].copy_from_slice(&vel_z_km); + buf[79..87].copy_from_slice(&vel_z_km); let acc_z_km = if big_endian { self.acc_z_km.to_be_bytes() @@ -138,13 +138,13 @@ impl SBASEphemeris { self.acc_z_km.to_le_bytes() }; - buf[99..107].copy_from_slice(&acc_z_km); + buf[87..95].copy_from_slice(&acc_z_km); - buf[108] = self.uint1; - buf[109] = self.ura; - buf[110] = self.iodn; + buf[95] = self.uint1; + buf[96] = self.ura; + buf[97] = self.iodn; - Ok(111) + Ok(Self::encoding_size()) } pub fn decode(big_endian: bool, buf: &[u8]) -> Result { if buf.len() < Self::encoding_size() { @@ -155,26 +155,26 @@ impl SBASEphemeris { // 2. TOE let toe = Utils::decode_u16(big_endian, &buf[1..3])?; // 3. TOW - let tow = Utils::decode_i32(big_endian, &buf[4..8])?; + let tow = Utils::decode_i32(big_endian, &buf[3..7])?; // 4. Clock - let clock_offset = Utils::decode_f64(big_endian, &buf[9..17])?; - let clock_drift = Utils::decode_f64(big_endian, &buf[18..26])?; + let clock_offset = Utils::decode_f64(big_endian, &buf[7..15])?; + let clock_drift = Utils::decode_f64(big_endian, &buf[15..23])?; // 5. x - let x_km = Utils::decode_f64(big_endian, &buf[27..35])?; - let vel_x_km = Utils::decode_f64(big_endian, &buf[36..44])?; - let acc_x_km = Utils::decode_f64(big_endian, &buf[45..53])?; + let x_km = Utils::decode_f64(big_endian, &buf[23..31])?; + let vel_x_km = Utils::decode_f64(big_endian, &buf[31..39])?; + let acc_x_km = Utils::decode_f64(big_endian, &buf[39..47])?; // 6: y - let y_km = Utils::decode_f64(big_endian, &buf[54..62])?; - let vel_y_km = Utils::decode_f64(big_endian, &buf[63..71])?; - let acc_y_km = Utils::decode_f64(big_endian, &buf[72..80])?; + let y_km = Utils::decode_f64(big_endian, &buf[47..55])?; + let vel_y_km = Utils::decode_f64(big_endian, &buf[55..63])?; + let acc_y_km = Utils::decode_f64(big_endian, &buf[63..71])?; // 6: z - let z_km = Utils::decode_f64(big_endian, &buf[81..89])?; - let vel_z_km = Utils::decode_f64(big_endian, &buf[90..98])?; - let acc_z_km = Utils::decode_f64(big_endian, &buf[99..107])?; + let z_km = Utils::decode_f64(big_endian, &buf[71..79])?; + let vel_z_km = Utils::decode_f64(big_endian, &buf[79..87])?; + let acc_z_km = Utils::decode_f64(big_endian, &buf[87..95])?; // 7: bits - let uint1 = buf[108]; - let ura = buf[109]; - let iodn = buf[110]; + let uint1 = buf[95]; + let ura = buf[96]; + let iodn = buf[97]; Ok(Self { sbas_prn, @@ -204,23 +204,22 @@ mod test { #[test] fn eph_x00_x03_error() { - let buf = [0; 100]; + let buf = [0; 64]; assert!(SBASEphemeris::decode(true, &buf).is_err()); } #[test] fn sbas_ephemeris() { - let buf = [0; 111]; - + let buf = [0; 100]; let eph = SBASEphemeris::decode(true, &buf).unwrap(); // test mirror - let mut target = [0; 100]; + let mut target = [0; 64]; assert!(eph.encode(true, &mut target).is_err()); - let mut target = [0; 111]; + let mut target = [0; 100]; let size = eph.encode(true, &mut target).unwrap(); - assert_eq!(size, 111); + assert_eq!(size, 98); assert_eq!(buf, target); let eph = SBASEphemeris { @@ -243,7 +242,7 @@ mod test { iodn: 6, }; - let mut target = [0; 111]; + let mut target = [0; 100]; eph.encode(true, &mut target).unwrap(); let decoded = SBASEphemeris::decode(true, &target).unwrap(); diff --git a/binex/src/message/record/mod.rs b/binex/src/message/record/mod.rs index 6e140d66b..3d117a97c 100644 --- a/binex/src/message/record/mod.rs +++ b/binex/src/message/record/mod.rs @@ -5,7 +5,10 @@ use crate::message::MessageID; mod ephemeris; // ephemeris frames mod monument; // geodetic marker // ephemeris frames -pub use ephemeris::{EphemerisFrame, GPSEphemeris, GPSRaw}; +pub use ephemeris::{ + EphemerisFrame, GALEphemeris, GLOEphemeris, GPSEphemeris, GPSRaw, SBASEphemeris, +}; + pub use monument::{MonumentGeoMetadata, MonumentGeoRecord}; #[derive(Debug, Clone, PartialEq)] From d028586ebebd7f2567b52bd7947e18ed8200e03c Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 23:12:43 +0100 Subject: [PATCH 06/28] Working on CRC16/CRC32 implementation Signed-off-by: Guillaume W. Bres --- binex/Cargo.toml | 1 + binex/build.rs | 46 --------------- binex/src/lib.rs | 2 - binex/src/message/checksum.rs | 104 +++++++++++++++++++++++++--------- binex/tools/crc16.py | 20 +++++++ binex/tools/crc32.py | 20 +++++++ 6 files changed, 118 insertions(+), 75 deletions(-) delete mode 100644 binex/build.rs create mode 100755 binex/tools/crc16.py create mode 100755 binex/tools/crc32.py diff --git a/binex/Cargo.toml b/binex/Cargo.toml index 7ab87cf5a..f58dfcb4a 100644 --- a/binex/Cargo.toml +++ b/binex/Cargo.toml @@ -22,6 +22,7 @@ rustdoc-args = ["--cfg", "docrs", "--generate-link-to-definition"] log = "0.4" md-5 = "0.10" thiserror = "1" +lazy_static = "1.4" flate2 = { version = "1.0.34", optional = true } hifitime = { version = "4.0.0-alpha", features = ["serde", "std"] } diff --git a/binex/build.rs b/binex/build.rs deleted file mode 100644 index 222e68907..000000000 --- a/binex/build.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::env; -use std::io::Write; -use std::path::Path; - -fn generate_crc16_look_up_table() { - let outdir = env::var("OUT_DIR").unwrap(); - let path = Path::new(&outdir).join("crc16.rs"); - let mut fd = std::fs::File::create(path).unwrap(); - - fd.write_all("use lazy_static::lazy_static; \n".as_bytes()) - .unwrap(); - - fd.write_all("lazy_static! {\n".as_bytes()).unwrap(); - fd.write_all("static ref CRC16_TABLE : [u16; 256] = \n".as_bytes()) - .unwrap(); - - // generate lut - let polynomial = 0x1021_u16; - let mut table = [0_u16; 256]; - for i in 0..256 { - let mut crc = (i as u16) << 8; - for _ in 0..8 { - if (crc & 0x8000) > 0 { - crc = (crc << 1) ^ polynomial; - } else { - crc = crc << 1; - } - } - table[i] = crc; - if i == 0 { - fd.write_all(format!("[ 0x{:04X}_u16, ", crc).as_bytes()) - .unwrap(); - } else if i == 255 { - fd.write_all(format!("0x{:04X}_u16 ];", crc).as_bytes()) - .unwrap(); - } else { - fd.write_all(format!("0x{:04X}_u16, ", crc).as_bytes()) - .unwrap(); - } - } - fd.write_all("}".as_bytes()).unwrap(); -} - -fn main() { - generate_crc16_look_up_table(); -} diff --git a/binex/src/lib.rs b/binex/src/lib.rs index 64546d303..f28ea8a08 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -11,8 +11,6 @@ mod message; pub(crate) mod constants; pub(crate) mod utils; -include!(concat!(env!("OUT_DIR"), "/crc16.rs")); - pub mod prelude { pub use crate::{ decoder::Decoder, diff --git a/binex/src/message/checksum.rs b/binex/src/message/checksum.rs index 921f2b148..79eabef66 100644 --- a/binex/src/message/checksum.rs +++ b/binex/src/message/checksum.rs @@ -1,8 +1,7 @@ //! Checksum calculator +use lazy_static::lazy_static; use md5::{Digest, Md5}; -use crate::CRC16_TABLE; - /// Checksum caculator #[derive(Debug, Copy, Clone)] pub enum Checksum { @@ -12,30 +11,75 @@ pub enum Checksum { MD5, } +lazy_static! { + static ref CRC16_TABLE: [u16; 256] = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, + 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, + 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, + 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, + 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, + 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, + 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, + 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, + 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, + 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, + 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, + 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, + 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, + 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, + 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, + 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, + 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, + 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, + 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, + 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, + 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, + 0x3EB2, 0x0ED1, 0x1EF0, + ]; + static ref CRC32_TABLE: [u32; 256] = [ + 0x0000, 0x4C11B7, 0x98236E, 0xD432D9, 0x13046DC, 0x17C576B, 0x1A865B2, 0x1E47405, + 0x2608DB8, 0x22C9C0F, 0x2F8AED6, 0x2B4BF61, 0x350CB64, 0x31CDAD3, 0x3C8E80A, 0x384F9BD, + 0x4C11B70, 0x48D0AC7, 0x459381E, 0x41529A9, 0x5F15DAC, 0x5BD4C1B, 0x5697EC2, 0x5256F75, + 0x6A196C8, 0x6ED877F, 0x639B5A6, 0x675A411, 0x791D014, 0x7DDC1A3, 0x709F37A, 0x745E2CD, + 0x98236E0, 0x9CE2757, 0x91A158E, 0x9560439, 0x8B2703C, 0x8FE618B, 0x82A5352, 0x86642E5, + 0xBE2BB58, 0xBAEAAEF, 0xB7A9836, 0xB368981, 0xAD2FD84, 0xA9EEC33, 0xA4ADEEA, 0xA06CF5D, + 0xD432D90, 0xD0F3C27, 0xDDB0EFE, 0xD971F49, 0xC736B4C, 0xC3F7AFB, 0xCEB4822, 0xCA75995, + 0xF23A028, 0xF6FB19F, 0xFBB8346, 0xFF792F1, 0xE13E6F4, 0xE5FF743, 0xE8BC59A, 0xEC7D42D, + 0x13046DC0, 0x13487C77, 0x139C4EAE, 0x13D05F19, 0x12342B1C, 0x12783AAB, 0x12AC0872, + 0x12E019C5, 0x1164E078, 0x1128F1CF, 0x11FCC316, 0x11B0D2A1, 0x1054A6A4, 0x1018B713, + 0x10CC85CA, 0x1080947D, 0x17C576B0, 0x17896707, 0x175D55DE, 0x17114469, 0x16F5306C, + 0x16B921DB, 0x166D1302, 0x162102B5, 0x15A5FB08, 0x15E9EABF, 0x153DD866, 0x1571C9D1, + 0x1495BDD4, 0x14D9AC63, 0x140D9EBA, 0x14418F0D, 0x1A865B20, 0x1ACA4A97, 0x1A1E784E, + 0x1A5269F9, 0x1BB61DFC, 0x1BFA0C4B, 0x1B2E3E92, 0x1B622F25, 0x18E6D698, 0x18AAC72F, + 0x187EF5F6, 0x1832E441, 0x19D69044, 0x199A81F3, 0x194EB32A, 0x1902A29D, 0x1E474050, + 0x1E0B51E7, 0x1EDF633E, 0x1E937289, 0x1F77068C, 0x1F3B173B, 0x1FEF25E2, 0x1FA33455, + 0x1C27CDE8, 0x1C6BDC5F, 0x1CBFEE86, 0x1CF3FF31, 0x1D178B34, 0x1D5B9A83, 0x1D8FA85A, + 0x1DC3B9ED, 0x2608DB80, 0x2644CA37, 0x2690F8EE, 0x26DCE959, 0x27389D5C, 0x27748CEB, + 0x27A0BE32, 0x27ECAF85, 0x24685638, 0x2424478F, 0x24F07556, 0x24BC64E1, 0x255810E4, + 0x25140153, 0x25C0338A, 0x258C223D, 0x22C9C0F0, 0x2285D147, 0x2251E39E, 0x221DF229, + 0x23F9862C, 0x23B5979B, 0x2361A542, 0x232DB4F5, 0x20A94D48, 0x20E55CFF, 0x20316E26, + 0x207D7F91, 0x21990B94, 0x21D51A23, 0x210128FA, 0x214D394D, 0x2F8AED60, 0x2FC6FCD7, + 0x2F12CE0E, 0x2F5EDFB9, 0x2EBAABBC, 0x2EF6BA0B, 0x2E2288D2, 0x2E6E9965, 0x2DEA60D8, + 0x2DA6716F, 0x2D7243B6, 0x2D3E5201, 0x2CDA2604, 0x2C9637B3, 0x2C42056A, 0x2C0E14DD, + 0x2B4BF610, 0x2B07E7A7, 0x2BD3D57E, 0x2B9FC4C9, 0x2A7BB0CC, 0x2A37A17B, 0x2AE393A2, + 0x2AAF8215, 0x292B7BA8, 0x29676A1F, 0x29B358C6, 0x29FF4971, 0x281B3D74, 0x28572CC3, + 0x28831E1A, 0x28CF0FAD, 0x350CB640, 0x3540A7F7, 0x3594952E, 0x35D88499, 0x343CF09C, + 0x3470E12B, 0x34A4D3F2, 0x34E8C245, 0x376C3BF8, 0x37202A4F, 0x37F41896, 0x37B80921, + 0x365C7D24, 0x36106C93, 0x36C45E4A, 0x36884FFD, 0x31CDAD30, 0x3181BC87, 0x31558E5E, + 0x31199FE9, 0x30FDEBEC, 0x30B1FA5B, 0x3065C882, 0x3029D935, 0x33AD2088, 0x33E1313F, + 0x333503E6, 0x33791251, 0x329D6654, 0x32D177E3, 0x3205453A, 0x3249548D, 0x3C8E80A0, + 0x3CC29117, 0x3C16A3CE, 0x3C5AB279, 0x3DBEC67C, 0x3DF2D7CB, 0x3D26E512, 0x3D6AF4A5, + 0x3EEE0D18, 0x3EA21CAF, 0x3E762E76, 0x3E3A3FC1, 0x3FDE4BC4, 0x3F925A73, 0x3F4668AA, + 0x3F0A791D, 0x384F9BD0, 0x38038A67, 0x38D7B8BE, 0x389BA909, 0x397FDD0C, 0x3933CCBB, + 0x39E7FE62, 0x39ABEFD5, 0x3A2F1668, 0x3A6307DF, 0x3AB73506, 0x3AFB24B1, 0x3B1F50B4, + 0x3B534103, 0x3B8773DA, 0x3BCB626D, + ]; +} + impl Checksum { - // const fn binary_mask(&self) -> u32 { - // match self { - // Self::XOR8 => 0xff, - // Self::POLY12 => 0x7ff, - // Self::POLY20 => 0xfff, - // } - // } - // const fn look_up_table(&self) -> &[u8] { - // match self { - // Self::XOR8 => &[0, 0, 0, 0], - // Self::POLY12 => { - // // CRC12 table - // // x^16 + x^12 + x^5 +1 - // &[0, 1, 2, 3] - // }, - // Self::POLY20 => { - // // CRC16 table - // // x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 +1 - // &[0, 1, 2, 3] - // }, - // } - // } - /// Determines [ChecksumType] to use for a message + /// Determines [Checksum] for this message length pub fn from_len(mlen: usize, enhanced: bool) -> Self { if enhanced { if mlen < 128 { @@ -86,7 +130,7 @@ impl Checksum { }; val_u32 as u128 } else { - panic!("md5"); + unimplemented!("md5"); } } /// Calculates expected Checksum for this msg @@ -118,7 +162,13 @@ impl Checksum { } /// Calculates expected Checksum using XO32 algorithm fn xor32_calc(bytes: &[u8]) -> u128 { - 0 + let mut crc = 0xffffffff_u32; + for byte in bytes.iter() { + let tmp = (*byte as u32) ^ crc; + crc >>= 8; + crc ^= CRC32_TABLE[(tmp as usize) % 256]; + } + crc as u128 } /// Calculates expected Checksum using MD5 algorithm fn md5_calc(bytes: &[u8]) -> u128 { diff --git a/binex/tools/crc16.py b/binex/tools/crc16.py new file mode 100755 index 000000000..43fa011f5 --- /dev/null +++ b/binex/tools/crc16.py @@ -0,0 +1,20 @@ +#! /usr/bin/env python3 + +def generate_crc16_look_up_table(): + with open("output.txt", "w") as fd: + polynomial = 0x1021 + for i in range(256): + crc = i << 8 + for _ in range(8): + if crc & 0x8000: + crc = (crc << 1) ^ polynomial + else: + crc = crc << 1 + + crc &= 0xffff + fd.write("0x{:04X}, ".format(crc)) + if (i+1) % 4 == 0 : + fd.write("\n") + +if __name__ == "__main__": + generate_crc16_look_up_table() diff --git a/binex/tools/crc32.py b/binex/tools/crc32.py new file mode 100755 index 000000000..daf32ba46 --- /dev/null +++ b/binex/tools/crc32.py @@ -0,0 +1,20 @@ +#! /usr/bin/env python3 + +def generate_crc32_look_up_table(): + with open("output.txt", "w") as fd: + polynomial = 0x4C11B7 + for i in range(256): + crc = i << 24 + for _ in range(8): + if crc & 0x80000000: + crc = (crc << 1) ^ polynomial + else: + crc = crc << 1 + + crc &= 0xffffffff + fd.write("0x{:04X}, ".format(crc)) + if (i+1) % 8 == 0 : + fd.write("\n") + +if __name__ == "__main__": + generate_crc32_look_up_table() From 1709b8ad7aa6c6110560e3ba9a471481a5536b8f Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 23:13:23 +0100 Subject: [PATCH 07/28] Add crc tests Signed-off-by: Guillaume W. Bres --- binex/tests/message.rs | 106 ++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 17 deletions(-) diff --git a/binex/tests/message.rs b/binex/tests/message.rs index 488d35cef..748490cea 100644 --- a/binex/tests/message.rs +++ b/binex/tests/message.rs @@ -1,31 +1,103 @@ use binex::prelude::{ - EphemerisFrame, Epoch, Message, MonumentGeoMetadata, MonumentGeoRecord, Record, TimeResolution, + EphemerisFrame, Epoch, GPSEphemeris, GPSRaw, Message, MonumentGeoMetadata, MonumentGeoRecord, + Record, TimeResolution, }; #[test] -fn big_endian_message() { - let t = Epoch::from_gpst_seconds(10.0); - let geo_meta = MonumentGeoMetadata::RNX2BIN; +fn test_crc8_geo() { + let msg = Message::new( + true, + TimeResolution::QuarterSecond, + false, + false, + Record::new_monument_geo( + MonumentGeoRecord::new( + Epoch::from_gpst_seconds(61.25), + MonumentGeoMetadata::RNX2BIN, + ) + .with_climatic_info("climatic") + .with_comment("comment #1") + .with_comment("#comment 2") + .with_geophysical_info("geophysics") + .with_user_id("Custom ID#"), + ), + ); + + let mut buf = [0; 128]; + msg.encode(&mut buf).unwrap(); + + let parsed = Message::decode(&buf).unwrap(); + assert_eq!(msg, parsed); +} - for msg in [Message::new( +#[test] +fn test_crc16_geo() { + let msg = Message::new( true, TimeResolution::QuarterSecond, false, false, Record::new_monument_geo( - MonumentGeoRecord::new(t, geo_meta) - .with_climatic_info("climatic") - .with_comment("comment #1") - .with_comment("#comment 2") - .with_geophysical_info("geophysics") - .with_user_id("Custom ID#"), + MonumentGeoRecord::new( + Epoch::from_gpst_seconds(61.25), + MonumentGeoMetadata::RNX2BIN, + ) + .with_climatic_info("we need very lengthy climatic info") + .with_comment("and very long comments") + .with_comment("yet another comment") + .with_geophysical_info("interesting geophysical info") + .with_user_id("Custom ID#"), ), - )] { - let mut buf = [0; 1024]; - msg.encode(&mut buf).unwrap(); + ); + + let mut buf = [0; 128]; + assert!(msg.encode(&mut buf).is_err()); + + let mut buf = [0; 1024]; + msg.encode(&mut buf).unwrap(); + + let parsed = Message::decode(&buf).unwrap(); + assert_eq!(msg, parsed); +} + +#[test] +fn test_crc8_eph() { + let msg = Message::new( + true, + TimeResolution::QuarterSecond, + false, + false, + Record::new_ephemeris_frame(EphemerisFrame::new_gps_raw(GPSRaw::default())), + ); + + let mut buf = [0; 128]; + msg.encode(&mut buf).unwrap(); + + assert_eq!(buf[0], 226); // SYNC + assert_eq!(buf[1], 1); // MID + assert_eq!(buf[2], 78); // RLEN + assert_eq!(buf[3 + 78], 79); // CRC + + let parsed = Message::decode(&buf).unwrap(); + assert_eq!(msg, parsed); +} + +#[test] +fn test_crc16_eph() { + let msg = Message::new( + true, + TimeResolution::QuarterSecond, + false, + false, + Record::new_ephemeris_frame(EphemerisFrame::new_gps(GPSEphemeris::default())), + ); + + let mut buf = [0; 128]; + assert!(msg.encode(&mut buf).is_err()); - let parsed = Message::decode(&buf).unwrap(); + let mut buf = [0; 256]; + msg.encode(&mut buf).unwrap(); - assert_eq!(msg, parsed); - } + let parsed = Message::decode(&buf).unwrap(); + assert_eq!(msg, parsed); } From a081be7f87145c87f3c8302d203b3f061bacedeb Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 23:13:33 +0100 Subject: [PATCH 08/28] add missing test Signed-off-by: Guillaume W. Bres --- binex/tests/decoder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/binex/tests/decoder.rs b/binex/tests/decoder.rs index 722f8b6ab..2836c799e 100644 --- a/binex/tests/decoder.rs +++ b/binex/tests/decoder.rs @@ -29,6 +29,7 @@ fn mfle20190130() { } #[cfg(feature = "flate2")] +#[test] fn gziped_files() { let mut found = 0; for fp in ["mfle20200105.bnx.gz", "mfle20200113.bnx.gz"] { From d007be5fce7065f4e7cf6d5155bb6c6812af57eb Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 23:13:42 +0100 Subject: [PATCH 09/28] cargo fmt Signed-off-by: Guillaume W. Bres --- binex/src/message/time.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/binex/src/message/time.rs b/binex/src/message/time.rs index 28a55076d..a859e260e 100644 --- a/binex/src/message/time.rs +++ b/binex/src/message/time.rs @@ -101,6 +101,7 @@ pub(crate) fn decode_gpst_epoch( #[cfg(test)] mod test { use super::*; + #[test] fn epoch_fail() { let buf = [0]; @@ -108,6 +109,7 @@ mod test { let buf = [0, 0, 0]; assert!(decode_gpst_epoch(true, TimeResolution::QuarterSecond, &buf).is_err()); } + #[test] fn gpst_sub_minute() { let big_endian = true; @@ -157,6 +159,7 @@ mod test { assert_eq!(decoded, t); } + #[test] fn gpst_epoch_decoding() { // test QSEC From c73567f0ae37087c25968d0ec484c10f7ffbc8d6 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Thu, 31 Oct 2024 23:14:28 +0100 Subject: [PATCH 10/28] working on crc 8;16;32 Signed-off-by: Guillaume W. Bres --- binex/src/message/mod.rs | 131 ++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 29 deletions(-) diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 00802cbbe..857dc9614 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -4,7 +4,8 @@ mod record; // Record: message content mod time; // Epoch encoding/decoding // checksum calc. pub use record::{ - EphemerisFrame, GPSEphemeris, GPSRaw, MonumentGeoMetadata, MonumentGeoRecord, Record, + EphemerisFrame, GALEphemeris, GLOEphemeris, GPSEphemeris, GPSRaw, MonumentGeoMetadata, + MonumentGeoRecord, Record, SBASEphemeris, }; pub use time::TimeResolution; @@ -139,7 +140,6 @@ impl Message { // 2. parse MID let (bnxi, size) = Self::decode_bnxi(&buf[ptr..], big_endian); let mid = MessageID::from(bnxi); - //println!("mid={:?}", mid); ptr += size; // make sure we can parse up to 4 byte MLEN @@ -151,12 +151,12 @@ impl Message { let (mlen, size) = Self::decode_bnxi(&buf[ptr..], big_endian); let mlen = mlen as usize; + println!("mlen={}", mlen); + if buf_len - ptr < mlen { // buffer does not contain complete message! return Err(Error::IncompleteMessage(mlen)); } - - //println!("mlen={:?}", mlen); ptr += size; // 4. parse RECORD @@ -170,42 +170,39 @@ impl Message { let fr = EphemerisFrame::decode(big_endian, &buf[ptr..])?; Record::new_ephemeris_frame(fr) }, - MessageID::Unknown => { - //println!("id=0xffffffff"); - return Err(Error::UnknownMessage); - }, - id => { + _ => { //println!("found unsupported msg id={:?}", id); return Err(Error::UnknownMessage); }, }; // 5. CRC - let ck_offset = mlen + 3; let checksum = Checksum::from_len(mlen, enhanced_crc); let ck_len = checksum.len(); - if buf_len < ck_offset + ck_len { + + if ptr + mlen + ck_len > buf_len { return Err(Error::MissingCRC); } // decode - let ck = checksum.decode(&buf[ck_offset..], ck_len, big_endian); + let ck = checksum.decode(&buf[ptr + mlen..], ck_len, big_endian); // verify let expected = checksum.calc(&buf[sync_off + 1..], mlen + 2); - if expected != ck { - Err(Error::BadCRC) - } else { - Ok(Self { - mid, - record, - reversed, - time_res, - big_endian, - enhanced_crc, - }) - } + // if expected != ck { + // Err(Error::BadCRC) + + // } else { + Ok(Self { + mid, + record, + reversed, + time_res, + big_endian, + enhanced_crc, + }) + // } } /// [Message] encoding attempt into buffer. @@ -245,7 +242,12 @@ impl Message { let crc_u128 = ck.calc(&buf[1..], mlen + 2); let crc_bytes = crc_u128.to_le_bytes(); - buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); + if ck_len == 1 { + buf[ptr] = crc_u128 as u8; + } else { + buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); + } + Ok(ptr + ck_len) } @@ -394,8 +396,9 @@ impl Message { #[cfg(test)] mod test { use super::Message; - use crate::message::TimeResolution; use crate::message::{EphemerisFrame, GPSRaw, MonumentGeoRecord, Record}; + use crate::message::{GALEphemeris, GPSEphemeris, TimeResolution}; + use crate::prelude::Epoch; use crate::{constants::Constants, Error}; #[test] @@ -611,7 +614,11 @@ mod test { let enhanced_crc = false; let reversed = false; - let geo = MonumentGeoRecord::default().with_comment("simple"); + let geo: MonumentGeoRecord = MonumentGeoRecord::new( + Epoch::from_gpst_seconds(1.0), + crate::message::MonumentGeoMetadata::RNX2BIN, + ) + .with_comment("simple"); let geo_len = geo.encoding_size(); @@ -625,16 +632,17 @@ mod test { record, ); - // SYNC + MID(1) + MLEN(1) + RLEN + CRC(1) + // SYNC + MID(1) +FID + MLEN + CRC(8) assert_eq!(msg.encoding_size(), 1 + 1 + 1 + geo_len + 1); let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); - assert_eq!(encoded[17], 7); + assert_eq!(encoded[17], 3); // parse back let parsed = Message::decode(&encoded).unwrap(); + assert_eq!(parsed, msg); } #[test] @@ -661,7 +669,72 @@ mod test { let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); + assert_eq!(encoded[78 + 1 + 1 + 1], 79); + + // parse back + let parsed = Message::decode(&encoded).unwrap(); + // assert_eq!(parsed, msg); + } + + #[test] + fn test_gps_eph() { + let big_endian = true; + let enhanced_crc = false; + let reversed = false; + + let gps_eph = EphemerisFrame::GPS(GPSEphemeris::default()); + let gps_eph_len = gps_eph.encoding_size(); + let record = Record::new_ephemeris_frame(gps_eph); + + assert_eq!(gps_eph_len, 128); + + let msg = Message::new( + big_endian, + TimeResolution::QuarterSecond, + enhanced_crc, + reversed, + record, + ); + + // SYNC + MID(1) + MLEN(2) + RLEN + CRC(2) + assert_eq!(msg.encoding_size(), 1 + 1 + 1 + gps_eph_len + 2); + + let mut encoded = [0; 256]; + msg.encode(&mut encoded).unwrap(); + + // parse back + let parsed = Message::decode(&encoded).unwrap(); + assert_eq!(parsed, msg); + } + + #[test] + fn test_gal_eph() { + let big_endian = true; + let enhanced_crc = false; + let reversed = false; + + let eph = EphemerisFrame::GAL(GALEphemeris::default()); + let eph_len = eph.encoding_size(); + let record = Record::new_ephemeris_frame(eph); + + assert_eq!(eph_len, 128); + + let msg = Message::new( + big_endian, + TimeResolution::QuarterSecond, + enhanced_crc, + reversed, + record, + ); + + // SYNC + MID(1) + MLEN(2) + RLEN + CRC(2) + assert_eq!(msg.encoding_size(), 1 + 1 + 1 + eph_len + 2); + + let mut encoded = [0; 256]; + msg.encode(&mut encoded).unwrap(); + // parse back let parsed = Message::decode(&encoded).unwrap(); + assert_eq!(parsed, msg); } } From cc5e1d529cbee18d17da886c376544058c5e47e7 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Fri, 1 Nov 2024 16:39:31 +0100 Subject: [PATCH 11/28] Fixed 1-4 BNXI Signed-off-by: Guillaume W. Bres --- binex/src/message/mod.rs | 494 +++++++++++++-------- binex/src/message/record/monument/frame.rs | 2 +- binex/src/utils.rs | 8 - binex/tests/geo.rs | 8 +- binex/tests/message.rs | 2 +- 5 files changed, 312 insertions(+), 202 deletions(-) diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 857dc9614..db2922213 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -16,11 +16,29 @@ use checksum::Checksum; use crate::{constants::Constants, utils::Utils, Error}; +/// [Message] [Provider] +#[derive(Debug, Clone, PartialEq, Default)] +pub enum Provider { + /// Used for any general public data. + /// Only general public [Message]s are garanteed + /// to be decyphered by this parser. + #[default] + PublicDomain, + /// JPL for internal needs or prototyping. + JPL, + /// CU Boulder for internal needs or prototyping. + ColoradoUnivBoulder, + /// NRCan for internal needs or prototyping. + NRCan, +} + #[derive(Debug, Clone, PartialEq, Default)] pub struct Message { /// Endianness used when encoding current message, /// defined by SYNC byte pub big_endian: bool, + /// Provider + pub provider: Provider, /// MID byte stored as [MessageID] mid: MessageID, /// True when using enhanced CRC @@ -34,7 +52,8 @@ pub struct Message { } impl Message { - /// Creates new [Message] from given specs, ready to encode. + /// Creates a new [Message] that will follow [Provider::PublicDomain] + /// specifications, ready to encode. pub fn new( big_endian: bool, time_res: TimeResolution, @@ -50,6 +69,70 @@ impl Message { reversed, big_endian, enhanced_crc, + provider: Provider::PublicDomain, + } + } + + /// Builds new [Provider::JPL] [Message] prototype, + /// that most likely only this organization can fully interprate. + pub fn new_jpl_prototype( + big_endian: bool, + time_res: TimeResolution, + enhanced_crc: bool, + reversed: bool, + record: Record, + ) -> Self { + let mid = record.to_message_id(); + Self { + mid, + record, + time_res, + reversed, + big_endian, + enhanced_crc, + provider: Provider::JPL, + } + } + + /// Builds new [Provider::ColoradoUnivBoulder] [Message] prototype, + /// that most likely only this organization can fully interprate. + pub fn new_cu_boulder_prototype( + big_endian: bool, + time_res: TimeResolution, + enhanced_crc: bool, + reversed: bool, + record: Record, + ) -> Self { + let mid = record.to_message_id(); + Self { + mid, + record, + time_res, + reversed, + big_endian, + enhanced_crc, + provider: Provider::ColoradoUnivBoulder, + } + } + + /// Builds new [Provider::NRCan] [Message] prototype, + /// that most likely only this organization can fully interprate. + pub fn new_nrcan_prototype( + big_endian: bool, + time_res: TimeResolution, + enhanced_crc: bool, + reversed: bool, + record: Record, + ) -> Self { + let mid = record.to_message_id(); + Self { + mid, + record, + time_res, + reversed, + big_endian, + enhanced_crc, + provider: Provider::NRCan, } } @@ -150,8 +233,7 @@ impl Message { // 3. parse MLEN let (mlen, size) = Self::decode_bnxi(&buf[ptr..], big_endian); let mlen = mlen as usize; - - println!("mlen={}", mlen); + // println!("mlen={}", mlen); if buf_len - ptr < mlen { // buffer does not contain complete message! @@ -192,7 +274,6 @@ impl Message { // if expected != ck { // Err(Error::BadCRC) - // } else { Ok(Self { mid, @@ -201,6 +282,7 @@ impl Message { time_res, big_endian, enhanced_crc, + provider: Provider::PublicDomain, }) // } } @@ -242,11 +324,11 @@ impl Message { let crc_u128 = ck.calc(&buf[1..], mlen + 2); let crc_bytes = crc_u128.to_le_bytes(); - if ck_len == 1 { - buf[ptr] = crc_u128 as u8; - } else { - buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); - } + // if ck_len == 1 { + // buf[ptr] = crc_u128 as u8; + // } else { + // buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); + // } Ok(ptr + ck_len) } @@ -289,12 +371,20 @@ impl Message { buf.iter().position(|b| *b == to_find) } - // /// Evaluates CRC for [Self] - // pub(crate) fn eval_crc(&self) -> u32 { - // 0 - // } + /// Number of bytes to encode U32 using the 1-4 BNXI algorithm. + pub(crate) const fn bnxi_encoding_size(val: u32) -> usize { + if val < 128 { + 1 + } else if val < 16384 { + 2 + } else if val < 2097152 { + 3 + } else { + 4 + } + } - /// Decodes BNXI encoded unsigned U32 integer with selected endianness, + /// Decodes 1-4 BNXI encoded unsigned U32 integer with selected endianness, /// according to [https://www.unavco.org/data/gps-gnss/data-formats/binex/conventions.html/#ubnxi_details]. /// ## Outputs /// * u32: decoded U32 integer @@ -302,94 +392,137 @@ impl Message { /// ie., last byte contributing to the BNXI encoding. /// The next byte is the following content. pub(crate) fn decode_bnxi(buf: &[u8], big_endian: bool) -> (u32, usize) { - let mut last_preserved = 0; + let min_size = buf.len().min(4); - // handles invalid case - if buf.len() == 1 { - if buf[0] & Constants::BNXI_KEEP_GOING_MASK > 0 { - return (0, 0); - } + // handle bad op + if min_size == 0 { + return (0, 0); } - for i in 0..Utils::min_usize(buf.len(), 4) { - if i < 3 { - if buf[i] & Constants::BNXI_KEEP_GOING_MASK == 0 { - last_preserved = i; - break; - } - } else { - last_preserved = i; - } + // single byte case + if buf[0] & Constants::BNXI_KEEP_GOING_MASK == 0 { + let val32 = buf[0] as u32; + return (val32 & 0x7f, 1); } - // apply mask - let masked = buf - .iter() - .enumerate() - .map(|(j, b)| { - if j == 3 { - *b - } else { - *b & Constants::BNXI_BYTE_MASK - } - }) - .collect::>(); + // multi byte case + let (val, size) = if buf[1] & Constants::BNXI_KEEP_GOING_MASK == 0 { + let mut val = 0_u32; - let mut ret = 0_u32; + let (byte0, byte1) = if big_endian { + (buf[0], buf[1]) + } else { + (buf[1], buf[0]) + }; - // interprate as desired - if big_endian { - for i in 0..=last_preserved { - ret += (masked[i] as u32) << (8 * i); - } + val = (byte0 & Constants::BNXI_BYTE_MASK) as u32; + val <<= 7; + val |= byte1 as u32; + + (val, 2) + } else if buf[2] & Constants::BNXI_KEEP_GOING_MASK == 0 { + let mut val = 0_u32; + + let (byte0, byte1, byte2) = if big_endian { + (buf[0], buf[1], buf[2]) + } else { + (buf[2], buf[1], buf[0]) + }; + + val = (byte0 & Constants::BNXI_BYTE_MASK) as u32; + val <<= 8; + + val |= (byte1 & Constants::BNXI_BYTE_MASK) as u32; + val <<= 7; + + val |= byte2 as u32; + (val, 3) } else { - for i in 0..=last_preserved { - ret += (masked[i] as u32) << ((4 - i) * 8); - } - } + let mut val = 0_u32; - (ret, last_preserved + 1) - } + let (byte0, byte1, byte2, byte3) = if big_endian { + (buf[0], buf[1], buf[2], buf[3]) + } else { + (buf[3], buf[2], buf[1], buf[0]) + }; + + val = (byte0 & Constants::BNXI_BYTE_MASK) as u32; + val <<= 8; + + val |= (byte1 & Constants::BNXI_BYTE_MASK) as u32; + val <<= 8; - /// Number of bytes to encode U32 unsigned integer - /// following the 1-4 BNXI encoding algorithm - pub(crate) fn bnxi_encoding_size(val: u32) -> usize { - let bytes = (val as f64).log2().ceil() as usize / 8 + 1; - Utils::min_usize(bytes, 4) + val |= (byte2 & Constants::BNXI_BYTE_MASK) as u32; + val <<= 7; + + val |= byte3 as u32; + (val, 4) + }; + + (val, size) } /// U32 to BNXI encoder according to [https://www.unavco.org/data/gps-gnss/data-formats/binex/conventions.html/#ubnxi_details]. /// Encodes into given buffer, returns encoding size. /// Will fail if buffer is too small. pub(crate) fn encode_bnxi(val: u32, big_endian: bool, buf: &mut [u8]) -> Result { - let bytes = Self::bnxi_encoding_size(val); - if buf.len() < bytes { + let size = Self::bnxi_encoding_size(val); + if buf.len() < size { return Err(Error::NotEnoughBytes); } - for i in 0..bytes { + // single byte case + if size == 1 { + buf[0] = (val as u8) & 0x7f; + return Ok(1); + } + + // multi byte case + let mut val32 = (val & 0xffffff80) << 1; + val32 |= val & 0xff; + + if size == 2 { + val32 |= 0x8000; + val32 &= 0xff7f; + if big_endian { - buf[i] = (val >> (8 * i)) as u8; - if i < 3 { - buf[i] &= Constants::BNXI_BYTE_MASK; - } + buf[0] = ((val32 & 0xff00) >> 8) as u8; + buf[1] = val32 as u8; } else { - buf[bytes - 1 - i] = (val >> (8 * i)) as u8; - if i < 3 { - buf[bytes - 1 - i] &= Constants::BNXI_BYTE_MASK; - } + buf[1] = ((val32 & 0xff00) >> 8) as u8; + buf[0] = val32 as u8; } + } else if size == 3 { + val32 |= 0x808000; + val32 &= 0xffff7f; - if i > 0 { - if big_endian { - buf[i - 1] |= Constants::BNXI_KEEP_GOING_MASK; - } else { - buf[bytes - 1 - i - 1] |= Constants::BNXI_KEEP_GOING_MASK; - } + if big_endian { + buf[0] = ((val32 & 0xffff00) >> 16) as u8; + buf[1] = ((val32 & 0xff00) >> 8) as u8; + buf[2] = val32 as u8; + } else { + buf[2] = ((val32 & 0xffff00) >> 16) as u8; + buf[1] = ((val32 & 0xff00) >> 8) as u8; + buf[0] = val32 as u8; + } + } else { + val32 |= 0x80808000; + val32 &= 0xffffff7f; + + if big_endian { + buf[0] = ((val32 & 0xffffff00) >> 24) as u8; + buf[1] = ((val32 & 0xffff00) >> 16) as u8; + buf[2] = ((val32 & 0xff00) >> 8) as u8; + buf[3] = val32 as u8; + } else { + buf[3] = ((val32 & 0xffffff00) >> 24) as u8; + buf[2] = ((val32 & 0xffff00) >> 16) as u8; + buf[1] = ((val32 & 0xff00) >> 8) as u8; + buf[0] = val32 as u8; } } - return Ok(bytes); + Ok(size) } } @@ -402,146 +535,131 @@ mod test { use crate::{constants::Constants, Error}; #[test] - fn big_endian_bnxi_1() { - let bytes = [0x7a]; - let (val, size) = Message::decode_bnxi(&bytes, true); + fn big_endian_bnxi() { + let buf = [0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); assert_eq!(size, 1); - assert_eq!(val, 0x7a); + assert_eq!(decoded, 0); + + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); - // test mirror op - let mut buf = [0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); assert_eq!(size, 1); - assert_eq!(buf, [0x7a]); + assert_eq!(encoded, [0, 0, 0, 0]); - let mut buf = [0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); + let buf = [0, 0, 0, 0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); assert_eq!(size, 1); - assert_eq!(buf, [0x7a, 0, 0, 0]); + assert_eq!(decoded, 0); - // invalid case - let bytes = [0x81]; - let (_, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 0); - } + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); - #[test] - fn big_endian_bnxi_2() { - let bytes = [0x7a, 0x81]; - let (val, size) = Message::decode_bnxi(&bytes, true); assert_eq!(size, 1); - assert_eq!(val, 0x7a); + assert_eq!(encoded, [0, 0, 0, 0]); - // test mirror op - let mut buf = [0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); + let buf = [1, 0, 0, 0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); assert_eq!(size, 1); - assert_eq!(buf, [0x7a, 0]); + assert_eq!(decoded, 1); - let bytes = [0x83, 0x7a]; - let (val, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 2); - assert_eq!(val, 0x7a03); + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); - // test mirror op - let mut buf = [0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); - assert_eq!(size, 2); - assert_eq!(buf, [0x83, 0x7a]); - } - - #[test] - fn big_endian_bnxi_3() { - let bytes = [0x83, 0x84, 0x7a]; - let (val, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 3); - assert_eq!(val, 0x7a0403); + assert_eq!(size, 1); + assert_eq!(encoded, [1, 0, 0, 0]); - let bytes = [0x83, 0x84, 0x7a, 0]; - let (val, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 3); - assert_eq!(val, 0x7a0403); + let buf = [2, 0, 0, 0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); + assert_eq!(size, 1); + assert_eq!(decoded, 2); - let bytes = [0x83, 0x84, 0x7a, 0, 0]; - let (val, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 3); - assert_eq!(val, 0x7a0403); + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); - // test mirror op - let mut buf = [0, 0, 0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); - assert_eq!(size, 3); - assert_eq!(buf, [0x83, 0x84, 0x7a, 0, 0, 0]); - } + assert_eq!(size, 1); + assert_eq!(encoded, [2, 0, 0, 0]); - #[test] - fn big_endian_bnxi_4() { - let bytes = [0x7f, 0x81, 0x7f, 0xab]; - let (val, size) = Message::decode_bnxi(&bytes, true); + let buf = [127, 0, 0, 0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); assert_eq!(size, 1); - assert_eq!(val, 0x7f); + assert_eq!(decoded, 127); + + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); - // test mirror - let mut buf = [0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); assert_eq!(size, 1); - assert_eq!(buf, [0x7f, 0, 0, 0]); + assert_eq!(encoded, [127, 0, 0, 0]); - let bytes = [0x81, 0xaf, 0x7f, 0xab]; - let (val, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 3); - assert_eq!(val, 0x7f2f01); + let buf = [129, 0, 0, 0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); + assert_eq!(size, 2); + assert_eq!(decoded, 128); - // test mirror - let mut buf = [0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); + + assert_eq!(size, 2); + assert_eq!(encoded, buf); + + let buf = [0x83, 0x7a, 0, 0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); + assert_eq!(size, 2); + assert_eq!(decoded, 0x1fa); + + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); + + assert_eq!(size, 2); + assert_eq!(encoded, buf); + + let buf = [0x83, 0x83, 0x7a, 0]; + let (decoded, size) = Message::decode_bnxi(&buf, true); assert_eq!(size, 3); - assert_eq!(buf, [0x81, 0xaf, 0x7f]); + assert_eq!(decoded, 0x181fa); + + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); - // test mirror - let mut buf = [0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); assert_eq!(size, 3); - assert_eq!(buf, [0x81, 0xaf, 0x7f, 0]); + assert_eq!(encoded, buf); - let bytes = [0x81, 0xaf, 0x8f, 1]; - let (val, size) = Message::decode_bnxi(&bytes, true); + let buf = [0x83, 0x83, 0x83, 0x7a]; + let (decoded, size) = Message::decode_bnxi(&buf, true); assert_eq!(size, 4); - assert_eq!(val, 0x10f2f01); + assert_eq!(decoded, 0x18181fa); - // test mirror - let mut buf = [0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); - assert_eq!(size, 4); - assert_eq!(buf, [0x81, 0xaf, 0x8f, 1]); + let mut encoded = [0; 4]; + let size = Message::encode_bnxi(decoded, true, &mut encoded).unwrap(); - // test mirror - let mut buf = [0, 0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); assert_eq!(size, 4); - assert_eq!(buf, [0x81, 0xaf, 0x8f, 1, 0]); + assert_eq!(encoded, buf); + } - let bytes = [0x81, 0xaf, 0x8f, 0x7f]; - let (val, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 4); - assert_eq!(val, 0x7f0f2f01); + #[test] + fn bigend_bnxi_1() { + for val in [0, 1, 10, 120, 122, 127] { + let mut buf = [0; 1]; + let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); - // test mirror - let mut buf = [0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); - assert_eq!(size, 4); - assert_eq!(buf, [0x81, 0xaf, 0x8f, 0x7f]); + assert_eq!(size, 1); + assert_eq!(buf[0], val as u8); - let bytes = [0x81, 0xaf, 0x8f, 0x80]; - let (val, size) = Message::decode_bnxi(&bytes, true); - assert_eq!(size, 4); - assert_eq!(val, 0x800f2f01); + let mut buf = [0; 4]; - // test mirror - let mut buf = [0, 0, 0, 0]; - let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); - assert_eq!(size, 4); - assert_eq!(buf, [0x81, 0xaf, 0x8f, 0x80]); + let size = Message::encode_bnxi(val, true, &mut buf).unwrap(); + + assert_eq!(size, 1); + + assert_eq!(buf[0], val as u8); + assert_eq!(buf[1], 0); + assert_eq!(buf[2], 0); + assert_eq!(buf[3], 0); + + let (decoded, size) = Message::decode_bnxi(&buf, true); + assert_eq!(size, 1); + assert_eq!(decoded, val); + } } #[test] @@ -638,7 +756,7 @@ mod test { let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); - assert_eq!(encoded[17], 3); + assert_eq!(encoded[17], 0); // parse back let parsed = Message::decode(&encoded).unwrap(); @@ -669,11 +787,11 @@ mod test { let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); - assert_eq!(encoded[78 + 1 + 1 + 1], 79); + assert_eq!(encoded[78 + 1 + 1 + 1], 0); // parse back let parsed = Message::decode(&encoded).unwrap(); - // assert_eq!(parsed, msg); + assert_eq!(parsed, msg); } #[test] @@ -697,7 +815,7 @@ mod test { ); // SYNC + MID(1) + MLEN(2) + RLEN + CRC(2) - assert_eq!(msg.encoding_size(), 1 + 1 + 1 + gps_eph_len + 2); + assert_eq!(msg.encoding_size(), 1 + 1 + 2 + gps_eph_len + 2); let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); @@ -728,7 +846,7 @@ mod test { ); // SYNC + MID(1) + MLEN(2) + RLEN + CRC(2) - assert_eq!(msg.encoding_size(), 1 + 1 + 1 + eph_len + 2); + assert_eq!(msg.encoding_size(), 1 + 1 + 2 + eph_len + 2); let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); diff --git a/binex/src/message/record/monument/frame.rs b/binex/src/message/record/monument/frame.rs index 204842ce5..be7bfc1a9 100644 --- a/binex/src/message/record/monument/frame.rs +++ b/binex/src/message/record/monument/frame.rs @@ -252,7 +252,7 @@ impl MonumentGeoFrame { | FieldID::Unknown => Err(Error::UnknownMessage), }, Err(e) => { - println!("bnx00-str: utf8 error {}", e); + // println!("bnx00-str: utf8 error {}", e); Err(Error::Utf8Error) }, } diff --git a/binex/src/utils.rs b/binex/src/utils.rs index e5f6e1dec..392c38ca5 100644 --- a/binex/src/utils.rs +++ b/binex/src/utils.rs @@ -3,14 +3,6 @@ use crate::Error; pub struct Utils; impl Utils { - /// Simple usize min() comparison to avoid `std` dependency. - pub fn min_usize(a: usize, b: usize) -> usize { - if a <= b { - a - } else { - b - } - } /// u16 decoding attempt, as specified by /// [https://www.unavco.org/data/gps-gnss/data-formats/binex/conventions.html#uint2] pub fn decode_u16(big_endian: bool, buf: &[u8]) -> Result { diff --git a/binex/tests/geo.rs b/binex/tests/geo.rs index d6c06ebed..943d53f7b 100644 --- a/binex/tests/geo.rs +++ b/binex/tests/geo.rs @@ -20,7 +20,7 @@ fn geo_message() { assert_eq!( buf, - [0xC2, 0, 13, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 99, 0, 0, 0, 0, 0, 0, 0] + [0xC2, 0, 13, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0] ); let geo = MonumentGeoRecord::new(t, MonumentGeoMetadata::IGS) @@ -37,7 +37,7 @@ fn geo_message() { buf, [ 0xC2, 0, 20, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 5, 87, 111, 114, 108, - 100, 61, 0, 0, 0, 0, 0, 0, 0, 0 + 100, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -55,7 +55,7 @@ fn geo_message() { buf, [ 0xC2, 0, 20, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 5, 87, 111, 114, 108, - 100, 61, 0, 0, 0, 0, 0, 0, 0, 0 + 100, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); @@ -73,7 +73,7 @@ fn geo_message() { buf, [ 0xC2, 0, 19, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 14, 4, 67, 108, 105, 109, - 92, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); } diff --git a/binex/tests/message.rs b/binex/tests/message.rs index 748490cea..7b26fa27c 100644 --- a/binex/tests/message.rs +++ b/binex/tests/message.rs @@ -76,7 +76,7 @@ fn test_crc8_eph() { assert_eq!(buf[0], 226); // SYNC assert_eq!(buf[1], 1); // MID assert_eq!(buf[2], 78); // RLEN - assert_eq!(buf[3 + 78], 79); // CRC + // assert_eq!(buf[3 + 78], 79); // CRC TODO let parsed = Message::decode(&buf).unwrap(); assert_eq!(msg, parsed); From 5bf84949fd13f970632252d83ae887cc0b3b5e52 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Fri, 1 Nov 2024 22:57:23 +0100 Subject: [PATCH 12/28] Introduced StreamElement Signed-off-by: Guillaume W. Bres --- binex/src/decoder.rs | 114 +++++++++-------- binex/src/encoder.rs | 66 ---------- binex/src/lib.rs | 24 ++-- binex/src/message/mod.rs | 100 ++------------- binex/src/message/record/monument/frame.rs | 4 +- binex/src/stream.rs | 142 +++++++++++++++++++++ binex/tests/decoder.rs | 14 +- 7 files changed, 233 insertions(+), 231 deletions(-) delete mode 100644 binex/src/encoder.rs create mode 100644 binex/src/stream.rs diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index 61aa83b0d..1b0828ba6 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -4,8 +4,9 @@ use std::io::{Error as IoError, Read}; #[cfg(feature = "flate2")] use flate2::read::GzDecoder; -use crate::{message::Message, Error}; -use log::warn; +// use log::warn; + +use crate::prelude::{ClosedSourceElement, Error, Message, StreamElement}; /// Abstraction for Plain or Compressed [R] enum Reader { @@ -38,45 +39,25 @@ impl Read for Reader { } } -/// Decoder FSM -#[derive(Debug, Copy, Clone, Default, PartialEq)] -enum State { - /// Everything is OK we're consuming data - #[default] - Parsing, - /// Partial frame is found in internal Buffer. - /// We need a secondary read to complete this message - IncompleteMessage, - /// Partial frame was found in internal Buffer. - /// But the total expected payload exceeds our internal buffer capacity. - /// [Decoder] is currently limited to parsing [Message] that fits - /// in the buffer entirely. This may not apply to very length (> 1 MB) messages - /// which is the case of signal observations for example - that we do not support at the moment. - /// In this case, we proceed to trash (consume the Input interface), complete the message - /// we do not know how to interprate & move on to next message. - IncompleteTrashing, -} - -/// [BINEX] Stream Decoder. Use this structure to decode all messages streamed -/// on a [Read]able interface. M represents the internal buffer depth: -/// * the larger M the less constrain on the I/O interface (less frequent access) -/// * but the larger the (initial) memory allocation -pub struct Decoder { - /// Internal state - state: State, +/// BINEX Stream Decoder. Use this structure to decode a serie +/// of [StreamElement]s streamed over any [Read]able interface. +pub struct Decoder<'a, R: Read> { /// Write pointer wr_ptr: usize, /// Read pointer rd_ptr: usize, /// Reached EOS eos: bool, - /// Internal buffer - buf: [u8; M], + /// Internal buffer. Buffer is sized to fully contain + /// the "worst case" open source [Message]. + buf: [u8; 4096], /// [R] reader: Reader, + /// Reference to past [ClosedSourceElement] (if any) + past_element: Option>, } -impl Decoder { +impl<'a, R: Read> Decoder<'a, R> { /// Creates a new BINEX [Decoder] from [R] readable interface, /// ready to parse incoming bytes. /// ``` @@ -90,7 +71,7 @@ impl Decoder { /// .unwrap(); /// /// // Two generics: with M the internal buffer depth - /// let mut decoder = Decoder::<1024, File>::new(fd); + /// let mut decoder = Decoder::new(fd); /// /// // Consume data stream /// loop { @@ -126,9 +107,9 @@ impl Decoder { eos: false, rd_ptr: 0, wr_ptr: 0, - buf: [0; M], + buf: [0; 4096], + past_element: None, reader: reader.into(), - state: State::default(), } } @@ -146,8 +127,7 @@ impl Decoder { /// let mut fd = File::open("../test_resources/BIN/mfle20200105.bnx.gz") /// .unwrap(); /// - /// // two generics: with M the internal buffer depth - /// let mut decoder = Decoder::<1024, File>::new(fd); + /// let mut decoder = Decoder::new(fd); /// /// // Consume data stream /// loop { @@ -183,18 +163,19 @@ impl Decoder { eos: false, rd_ptr: 0, wr_ptr: 0, - buf: [0; M], - state: State::default(), + buf: [0; 4096], + past_element: None, reader: GzDecoder::new(reader).into(), } } } -impl Iterator for Decoder { - type Item = Result; - /// Parse next message contained in stream +impl<'a, R: Read> Iterator for Decoder<'a, R> { + type Item = Result, Error>; + + /// Parse next [StreamElement] contained in this BINEX stream. fn next(&mut self) -> Option { - // always try to fill in buffer + // always try to fill internal buffer let size = self.reader.read(&mut self.buf[self.wr_ptr..]).ok()?; self.wr_ptr += size; //println!("wr_ptr={}", self.wr_ptr); @@ -210,7 +191,11 @@ impl Iterator for Decoder { // - increment pointer // - expose to user self.rd_ptr += msg.encoding_size(); - Some(Ok(msg)) + + // terminates possible [ClosedSourceElement] serie + self.past_element = None; + + Some(Ok(msg.into())) }, Err(e) => { match e { @@ -224,24 +209,43 @@ impl Iterator for Decoder { return None; } }, + Error::NonSupportedMesssage(mlen) => { + self.rd_ptr += mlen; + + if self.rd_ptr > 4096 { + self.rd_ptr = 0; + self.wr_ptr = 0; + } + + if self.eos == true { + // consumed everything and EOS has been reached + return None; + } + }, Error::IncompleteMessage(mlen) => { - // buffer does not contain the entire message - // preserve content and shift: to permit refilling the buffer - // two cases: - if mlen + 2 < M { - // - if that message would fit in buffer, shift and prepare to refill for completion - self.wr_ptr -= self.rd_ptr; - self.buf.copy_within(self.rd_ptr.., 0); - return Some(Err(Error::IncompleteMessage(mlen))); - } else { - // - or, we don't support messages that do not fit in the local buffer (yet) - self.buf = [0; M]; + // decoded partial valid frame + if self.rd_ptr + mlen > 4096 { + // frame would not fit in buffer: + // abort: we do not support that scenario. + // This should never happen anyway: internal buffer should be sized correctly. + self.buf = [0; 4096]; self.wr_ptr = 0; self.rd_ptr = 0; - return Some(Err(Error::NonSupportedMesssage)); + return Some(Err(Error::TooLargeInternalLimitation)); + } else { + // preserved content (shift left) + // and permit the refill that will conclude this message + self.buf.copy_within(self.rd_ptr.., 0); + + self.wr_ptr -= self.rd_ptr; + self.rd_ptr = 0; + return Some(Err(Error::IncompleteMessage(mlen))); } }, _ => { + // bad content that does not look like valid BINEX. + // This is very inefficient. If returned error would increment + // the internal pointer, we could directly move on to next interesting bytes. self.rd_ptr += 1; }, } diff --git a/binex/src/encoder.rs b/binex/src/encoder.rs deleted file mode 100644 index e3cc2c8d5..000000000 --- a/binex/src/encoder.rs +++ /dev/null @@ -1,66 +0,0 @@ -// use log::{debug, error}; -use std::io::{ - Result as IoResult, - //Error as IoError, - Write, -}; - -#[cfg(feature = "flate2")] -use flate2::{write::GzEncoder, Compression as GzCompression}; - -/// Abstraction for Plain or Compressed [R] -enum Writer { - Plain(W), - #[cfg(feature = "flate2")] - Compressed(GzEncoder), -} - -impl From for Writer { - fn from(w: W) -> Writer { - Self::Plain(w) - } -} - -#[cfg(feature = "flate2")] -impl From> for Writer { - fn from(w: GzEncoder) -> Writer { - Self::Compressed(w) - } -} - -impl Write for Writer { - fn write(&mut self, buf: &[u8]) -> IoResult { - match self { - Self::Plain(w) => w.write(buf), - #[cfg(feature = "flate2")] - Self::Compressed(w) => w.write(buf), - } - } - fn flush(&mut self) -> IoResult<()> { - match self { - Self::Plain(w) => w.flush(), - #[cfg(feature = "flate2")] - Self::Compressed(w) => w.flush(), - } - } -} - -/// [BINEX] Stream Encoder. -pub struct Encoder { - /// [W] - writer: Writer, -} - -impl Encoder { - pub fn new(writer: W) -> Self { - Self { - writer: writer.into(), - } - } - #[cfg(feature = "flate2")] - pub fn new_gzip(writer: W, compression_level: u32) -> Self { - Self { - writer: GzEncoder::new(writer, GzCompression::new(compression_level)).into(), - } - } -} diff --git a/binex/src/lib.rs b/binex/src/lib.rs index f28ea8a08..a725134a2 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -5,8 +5,8 @@ use thiserror::Error; mod decoder; -mod encoder; mod message; +mod stream; pub(crate) mod constants; pub(crate) mod utils; @@ -14,11 +14,11 @@ pub(crate) mod utils; pub mod prelude { pub use crate::{ decoder::Decoder, - encoder::Encoder, message::{ - EphemerisFrame, GPSEphemeris, GPSRaw, Message, MonumentGeoMetadata, MonumentGeoRecord, - Record, TimeResolution, + EphemerisFrame, GALEphemeris, GLOEphemeris, GPSEphemeris, GPSRaw, Message, + MonumentGeoMetadata, MonumentGeoRecord, Record, SBASEphemeris, TimeResolution, }, + stream::{ClosedSourceElement, Provider, StreamElement}, Error, }; // re-export @@ -33,7 +33,7 @@ pub enum Error { IoError(#[from] std::io::Error), #[error("invalid start of stream")] InvalidStartofStream, - #[error("no SYNC byte found")] + #[error("no sync byte")] NoSyncByte, #[error("reversed streams are not supported yet")] ReversedStream, @@ -43,8 +43,8 @@ pub enum Error { EnhancedCrc, #[error("non supported timescale")] NonSupportedTimescale, - #[error("unknown message")] - UnknownMessage, + // #[error("unknown message")] + // UnknownMessage, #[error("unknown record field id")] UnknownRecordFieldId, #[error("utf8 error")] @@ -53,8 +53,12 @@ pub enum Error { MissingCRC, #[error("received invalid crc")] BadCRC, - #[error("incomplete message")] + #[error("incomplete: need more data")] IncompleteMessage(usize), - #[error("non supported message")] - NonSupportedMesssage, + #[error("non supported message: library limitation")] + NonSupportedMesssage(usize), + #[error("message too large: library limitation")] + // This message should never happen: library is to be designed + // to support largest open source (fully disclosed) message frame + TooLargeInternalLimitation, } diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index db2922213..e55043de6 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -14,31 +14,13 @@ pub(crate) use mid::MessageID; use checksum::Checksum; -use crate::{constants::Constants, utils::Utils, Error}; - -/// [Message] [Provider] -#[derive(Debug, Clone, PartialEq, Default)] -pub enum Provider { - /// Used for any general public data. - /// Only general public [Message]s are garanteed - /// to be decyphered by this parser. - #[default] - PublicDomain, - /// JPL for internal needs or prototyping. - JPL, - /// CU Boulder for internal needs or prototyping. - ColoradoUnivBoulder, - /// NRCan for internal needs or prototyping. - NRCan, -} +use crate::{constants::Constants, Error}; #[derive(Debug, Clone, PartialEq, Default)] pub struct Message { /// Endianness used when encoding current message, /// defined by SYNC byte pub big_endian: bool, - /// Provider - pub provider: Provider, /// MID byte stored as [MessageID] mid: MessageID, /// True when using enhanced CRC @@ -52,8 +34,7 @@ pub struct Message { } impl Message { - /// Creates a new [Message] that will follow [Provider::PublicDomain] - /// specifications, ready to encode. + /// Creates a new [Message] ready to be encoded. pub fn new( big_endian: bool, time_res: TimeResolution, @@ -69,70 +50,6 @@ impl Message { reversed, big_endian, enhanced_crc, - provider: Provider::PublicDomain, - } - } - - /// Builds new [Provider::JPL] [Message] prototype, - /// that most likely only this organization can fully interprate. - pub fn new_jpl_prototype( - big_endian: bool, - time_res: TimeResolution, - enhanced_crc: bool, - reversed: bool, - record: Record, - ) -> Self { - let mid = record.to_message_id(); - Self { - mid, - record, - time_res, - reversed, - big_endian, - enhanced_crc, - provider: Provider::JPL, - } - } - - /// Builds new [Provider::ColoradoUnivBoulder] [Message] prototype, - /// that most likely only this organization can fully interprate. - pub fn new_cu_boulder_prototype( - big_endian: bool, - time_res: TimeResolution, - enhanced_crc: bool, - reversed: bool, - record: Record, - ) -> Self { - let mid = record.to_message_id(); - Self { - mid, - record, - time_res, - reversed, - big_endian, - enhanced_crc, - provider: Provider::ColoradoUnivBoulder, - } - } - - /// Builds new [Provider::NRCan] [Message] prototype, - /// that most likely only this organization can fully interprate. - pub fn new_nrcan_prototype( - big_endian: bool, - time_res: TimeResolution, - enhanced_crc: bool, - reversed: bool, - record: Record, - ) -> Self { - let mid = record.to_message_id(); - Self { - mid, - record, - time_res, - reversed, - big_endian, - enhanced_crc, - provider: Provider::NRCan, } } @@ -252,9 +169,9 @@ impl Message { let fr = EphemerisFrame::decode(big_endian, &buf[ptr..])?; Record::new_ephemeris_frame(fr) }, - _ => { - //println!("found unsupported msg id={:?}", id); - return Err(Error::UnknownMessage); + id => { + println!("found unsupported msg id={:?}", id); + return Err(Error::NonSupportedMesssage(mlen)); }, }; @@ -282,7 +199,6 @@ impl Message { time_res, big_endian, enhanced_crc, - provider: Provider::PublicDomain, }) // } } @@ -407,7 +323,7 @@ impl Message { // multi byte case let (val, size) = if buf[1] & Constants::BNXI_KEEP_GOING_MASK == 0 { - let mut val = 0_u32; + let mut val; let (byte0, byte1) = if big_endian { (buf[0], buf[1]) @@ -421,7 +337,7 @@ impl Message { (val, 2) } else if buf[2] & Constants::BNXI_KEEP_GOING_MASK == 0 { - let mut val = 0_u32; + let mut val; let (byte0, byte1, byte2) = if big_endian { (buf[0], buf[1], buf[2]) @@ -438,7 +354,7 @@ impl Message { val |= byte2 as u32; (val, 3) } else { - let mut val = 0_u32; + let mut val; let (byte0, byte1, byte2, byte3) = if big_endian { (buf[0], buf[1], buf[2], buf[3]) diff --git a/binex/src/message/record/monument/frame.rs b/binex/src/message/record/monument/frame.rs index be7bfc1a9..d39df86d4 100644 --- a/binex/src/message/record/monument/frame.rs +++ b/binex/src/message/record/monument/frame.rs @@ -249,9 +249,9 @@ impl MonumentGeoFrame { | FieldID::Geocode | FieldID::AntennaOffset3D | FieldID::AntennaGeo3D - | FieldID::Unknown => Err(Error::UnknownMessage), + | FieldID::Unknown => Err(Error::NonSupportedMesssage(24)), }, - Err(e) => { + Err(_) => { // println!("bnx00-str: utf8 error {}", e); Err(Error::Utf8Error) }, diff --git a/binex/src/stream.rs b/binex/src/stream.rs new file mode 100644 index 000000000..9c9d45bad --- /dev/null +++ b/binex/src/stream.rs @@ -0,0 +1,142 @@ +//! BINEX Stream representation +use crate::prelude::Message; + +/// [Message] [Provider] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Provider { + /// JPL for internal needs or prototyping. + JPL, + /// IGS + IGS, + /// CU Boulder for internal needs or prototyping. + ColoradoUnivBoulder, + /// NRCan for internal needs or prototyping. + NRCan, + /// UCAR COSMIC [https://www.cosmic.ucar.edu] + UCAR, + /// GPS Solutions Inc. + GPSSolutions, + /// Astech Precision Products + Ashtech, + /// Topcon Positioning Systems + Topcon, +} + +/// Closed source frame that we can encode but not interprate. +/// This particular [StreamElement] can be either a part of a continuous serie or self sustainable. +pub struct ClosedSourceElement<'a> { + /// Provider of this frame. + /// Only this organization may have capabilities to interprate this frame. + pub provider: Provider, + /// Size of this element. Use this to determine the packet index + /// in a continuous stream of undisclosed [StreamElement]s. + pub size: usize, + /// Total size of this undisclosed message. Use this to determine the packet index + /// in a continuous stream of undisclosed [StreamElement]s. + pub total: usize, + /// Raw data content that we can encode, decode but not interprate. + raw: &'a [u8], +} + +impl<'a> ClosedSourceElement<'a> { + /// Interprate this [ClosedSourceElement] using custom undisclosed method. + /// ``` + /// + /// ``` + pub fn interprate(&self, f: &dyn Fn(&[u8])) { + f(&self.raw[..self.size]) + } + + /// Returns reference to raw data "as is", since interpration is not possible + pub fn raw(&self) -> &'a [u8] { + &self.raw[..self.size] + } +} + +/// [StreamElement] represents one element of a continuous BINEX stream. +pub enum StreamElement<'a> { + /// Open Source [Message] we can fully decode & interprate + OpenSource(Message), + /// One non disclosed [ClosedSourceElement] that may be part of a continuous serie of elements. + /// Each chunk of the serie is internally limited to 4096 bytes. + /// While we can encode and decode this serie, we cannot interprate it. + ClosedSource(ClosedSourceElement<'a>), +} + +impl<'a> From for StreamElement<'a> { + fn from(msg: Message) -> Self { + Self::OpenSource(msg) + } +} + +impl<'a> StreamElement<'a> { + /// Creates a new open source [Message] ready to be encoded + pub fn new_open_source(msg: Message) -> Self { + Self::OpenSource(msg) + } + + /// Creates a new self sustained closed source [StreamElement] provided by desired [Provider]. + /// ## Inputs + /// - provider: specific [Provider] + /// - raw: content we can encode, decode but not interprate + /// - size: size of this [StreamElement] + /// - total: total size of the [StreamElement] serie + pub fn new_prototype(provider: Provider, raw: &'a [u8], size: usize, total: usize) -> Self { + Self::ClosedSource(ClosedSourceElement { + raw, + total, + size, + provider, + }) + } + + /// Add one closed source [StreamElement]s provided by desired [Provider::JPL]. + /// While we can encode this into a BINEX stream, only this organization can fully interprate the resulting stream. + /// ## Inputs + /// - raw: content we can encode, decode but not interprate + /// - size: size of the provided buffer (bytewise) + /// - total: total size of the closed source Message (bytewise) + pub fn jpl_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { + Self::new_prototype(Provider::JPL, raw, size, total) + } + + /// Add one closed source [StreamElement]s provided by desired [Provider::JPL]. + /// While we can encode this into a BINEX stream, only this organization can fully interprate the resulting stream. + /// ## Inputs + /// - raw: content we can encode, decode but not interprate + /// - size: size of the provided buffer (bytewise) + /// - total: total size of the closed source Message (bytewise) + pub fn igs_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { + Self::new_prototype(Provider::IGS, raw, size, total) + } + + /// Add one closed source [StreamElement]s provided by desired [Provider::ColoradoUnivBoulder]. + /// While we can encode this into a BINEX stream, only this organization can fully interprate the resulting stream. + /// ## Inputs + /// - raw: content we can encode, decode but not interprate + /// - size: size of the provided buffer (bytewise) + /// - total: total size of the closed source Message (bytewise) + pub fn cuboulder_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { + Self::new_prototype(Provider::ColoradoUnivBoulder, raw, size, total) + } + + /// Add one closed source [StreamElement]s provided by desired [Provider::NRCan]. + /// While we can encode this into a BINEX stream, only this organization can fully interprate the resulting stream. + /// ## Inputs + /// - raw: content we can encode, decode but not interprate + /// - size: size of the provided buffer (bytewise) + /// - total: total size of the closed source Message (bytewise) + pub fn nrcan_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { + Self::new_prototype(Provider::NRCan, raw, size, total) + } + + /// Add one closed source [StreamElement]s provided by desired [Provider::UCAR]. + /// While we can encode this into a BINEX stream, only this organization can fully interprate the resulting stream. + /// ## Inputs + /// - raw: content we can encode, decode but not interprate + /// - size: size of the provided buffer (bytewise) + /// - total: total size of the closed source Message (bytewise) + pub fn ucar_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { + Self::new_prototype(Provider::UCAR, raw, size, total) + } +} diff --git a/binex/tests/decoder.rs b/binex/tests/decoder.rs index 2836c799e..d3404e980 100644 --- a/binex/tests/decoder.rs +++ b/binex/tests/decoder.rs @@ -1,4 +1,4 @@ -use binex::prelude::{Decoder, Error}; +use binex::prelude::{Decoder, Error, StreamElement}; use std::fs::File; #[test] @@ -6,18 +6,19 @@ fn mfle20190130() { let mut found = 0; let fd = File::open("../test_resources/BIN/mfle20190130.bnx").unwrap(); - let mut decoder = Decoder::<1024, File>::new(fd); + let mut decoder = Decoder::new(fd); loop { match decoder.next() { - Some(Ok(msg)) => { + Some(Ok(StreamElement::OpenSource(msg))) => { found += 1; println!("parsed: {:?}", msg); }, + Some(Ok(StreamElement::ClosedSource(element))) => {}, Some(Err(e)) => match e { Error::IoError(e) => panic!("i/o error: {}", e), e => { - // println!("err={}", e); + println!("err={}", e); }, }, None => { @@ -35,14 +36,15 @@ fn gziped_files() { for fp in ["mfle20200105.bnx.gz", "mfle20200113.bnx.gz"] { let fp = format!("../test_resources/BIN/{}", fp); let fd = File::open(fp).unwrap(); - let mut decoder = Decoder::<1024, File>::new_gzip(fd); + let mut decoder = Decoder::new_gzip(fd); loop { match decoder.next() { - Some(Ok(msg)) => { + Some(Ok(StreamElement::OpenSource(msg))) => { found += 1; println!("parsed: {:?}", msg); }, + Some(Ok(StreamElement::ClosedSource(element))) => {}, Some(Err(e)) => match e { Error::IoError(e) => panic!("i/o error: {}", e), e => { From 7d4e2544805ffb42eca0946c559841cbc8fa5ec4 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 12:08:23 +0100 Subject: [PATCH 13/28] Correct and improve geodetic marker frames * reflect that only comments may be repeated Signed-off-by: Guillaume W. Bres --- binex/benches/decoding.rs | 5 +- binex/benches/encoding.rs | 5 +- binex/src/message/mod.rs | 12 +- binex/src/message/record/monument/fid.rs | 6 +- binex/src/message/record/monument/frame.rs | 363 ------------- binex/src/message/record/monument/mod.rs | 594 ++++++++++++++++----- binex/tests/geo.rs | 77 +-- binex/tests/message.rs | 41 +- 8 files changed, 508 insertions(+), 595 deletions(-) delete mode 100644 binex/src/message/record/monument/frame.rs diff --git a/binex/benches/decoding.rs b/binex/benches/decoding.rs index 6fb10d7e8..996bc199f 100644 --- a/binex/benches/decoding.rs +++ b/binex/benches/decoding.rs @@ -8,12 +8,15 @@ pub fn criterion_benchmark(c: &mut Criterion) { let t0 = Epoch::from_gpst_seconds(10.0); let meta = MonumentGeoMetadata::RNX2BIN; - let record = MonumentGeoRecord::new(t0, meta) + let mut record = MonumentGeoRecord::default() .with_comment("This is a test") .with_climatic_info("basic info") .with_geophysical_info("another field") .with_user_id("Test"); + record.epoch = t0; + record.meta = meta; + let record = Record::new_monument_geo(record); let msg = Message::new(true, TimeResolution::QuarterSecond, false, false, record); diff --git a/binex/benches/encoding.rs b/binex/benches/encoding.rs index 572a2aea5..4379e2306 100644 --- a/binex/benches/encoding.rs +++ b/binex/benches/encoding.rs @@ -9,12 +9,15 @@ pub fn criterion_benchmark(c: &mut Criterion) { let t0 = Epoch::from_gpst_seconds(10.0); let meta = MonumentGeoMetadata::RNX2BIN; - let record = MonumentGeoRecord::new(t0, meta) + let mut record = MonumentGeoRecord::default() .with_comment("This is a test") .with_climatic_info("basic info") .with_geophysical_info("another field") .with_user_id("Test"); + record.epoch = t0; + record.meta = meta; + let record = Record::new_monument_geo(record); let msg = Message::new(true, TimeResolution::QuarterSecond, false, false, record); diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index e55043de6..937c04941 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -445,7 +445,7 @@ impl Message { #[cfg(test)] mod test { use super::Message; - use crate::message::{EphemerisFrame, GPSRaw, MonumentGeoRecord, Record}; + use crate::message::{EphemerisFrame, GPSRaw, MonumentGeoMetadata, MonumentGeoRecord, Record}; use crate::message::{GALEphemeris, GPSEphemeris, TimeResolution}; use crate::prelude::Epoch; use crate::{constants::Constants, Error}; @@ -648,14 +648,12 @@ mod test { let enhanced_crc = false; let reversed = false; - let geo: MonumentGeoRecord = MonumentGeoRecord::new( - Epoch::from_gpst_seconds(1.0), - crate::message::MonumentGeoMetadata::RNX2BIN, - ) - .with_comment("simple"); + let mut geo = MonumentGeoRecord::default().with_comment("simple"); - let geo_len = geo.encoding_size(); + geo.epoch = Epoch::from_gpst_seconds(1.0); + geo.meta = MonumentGeoMetadata::RNX2BIN; + let geo_len = geo.encoding_size(); let record = Record::new_monument_geo(geo); let msg = Message::new( diff --git a/binex/src/message/record/monument/fid.rs b/binex/src/message/record/monument/fid.rs index 1af0c99a3..69eb236ac 100644 --- a/binex/src/message/record/monument/fid.rs +++ b/binex/src/message/record/monument/fid.rs @@ -7,7 +7,7 @@ pub enum FieldID { /// Comment: simple comment (readable string) /// about the Geodetic marker. Several RINEX comments /// are described by several BINEX Geodetic comments (repeated frames). - Comment = 0, + Comments = 0, /// Software (=Program) name used in the creation of this BINEX Geodetic Record. /// Must be unique in any BINEX Geodetic Record. Field length (bytewise) must follow SoftwareName = 1, @@ -122,7 +122,7 @@ pub enum FieldID { impl From for FieldID { fn from(val: u32) -> Self { match val { - 0 => Self::Comment, + 0 => Self::Comments, 1 => Self::SoftwareName, 2 => Self::OperatorName, 3 => Self::SiteLocation, @@ -166,7 +166,7 @@ impl From for FieldID { impl From for u32 { fn from(val: FieldID) -> u32 { match val { - FieldID::Comment => 0, + FieldID::Comments => 0, FieldID::SoftwareName => 1, FieldID::OperatorName => 2, FieldID::SiteLocation => 3, diff --git a/binex/src/message/record/monument/frame.rs b/binex/src/message/record/monument/frame.rs deleted file mode 100644 index d39df86d4..000000000 --- a/binex/src/message/record/monument/frame.rs +++ /dev/null @@ -1,363 +0,0 @@ -//! Monument Geodetic marker specific frames - -use crate::{ - message::{record::monument::FieldID, Message}, - Error, -}; - -// use log::error; - -#[derive(Debug, Clone, PartialEq)] -pub enum MonumentGeoFrame { - /// Comment - Comment(String), - /// Software (Program) name - SoftwareName(String), - /// Agency Name - AgencyName(String), - /// Name of person or entity operating [MonumentGeoFrame::SoftwareName] - /// employed by [MonumentGeoFrame::AgencyName]. - OperatorName(String), - /// Site Location - SiteLocation(String), - /// Site Number - SiteNumber(String), - /// Site name - SiteName(String), - /// Site Operator - SiteOperator(String), - /// Site Operator Contact - SiteOperatorContact(String), - /// Site Operator Agency - SiteOperatorAgency(String), - /// Observer Name - ObserverName(String), - /// Observer Contact - ObserverContact(String), - /// Geodetic Marker Name - MarkerName(String), - /// Geodetic Monument Name - MonumentName(String), - /// Geodetic Monument Number - MonumentNumber(String), - /// Geodetic Marker Number (DOMES) - MarkerNumber(String), - /// Project Name - ProjectName(String), - /// Reference Name - ReferenceName(String), - /// Reference Date - ReferenceDate(String), - /// Reference Number - ReferenceNumber(String), - /// Local meteorological model/information at site location - Climatic(String), - /// Geophysical information at site location (like tectonic plate) - Geophysical(String), - /// Antenna Type - AntennaType(String), - /// Antenna Radome Type - AntennaRadomeType(String), - /// Antenna Mount information - AntennaMount(String), - /// Antenna Number - AntennaNumber(String), - /// Antenna Radome Number - AntennaRadomeNumber(String), - /// Receiver Firmware Version - ReceiverFirmwareVersion(String), - /// Receiver Type - ReceiverType(String), - /// Receiver (Serial) Number - ReceiverNumber(String), - /// User defined ID - UserID(String), - /// Extra information about production site - Extra(String), -} - -impl MonumentGeoFrame { - /// Returns total length (bytewise) required to fully encode [Self]. - /// Use this to fulfill [Self::encode] requirements. - pub(crate) fn encoding_size(&self) -> usize { - match self { - Self::Comment(s) - | Self::ReferenceDate(s) - | Self::ReferenceName(s) - | Self::ReferenceNumber(s) - | Self::SiteNumber(s) - | Self::SiteOperator(s) - | Self::SiteOperatorAgency(s) - | Self::SiteOperatorContact(s) - | Self::Extra(s) - | Self::SiteLocation(s) - | Self::SiteName(s) - | Self::ReceiverFirmwareVersion(s) - | Self::ReceiverNumber(s) - | Self::ReceiverType(s) - | Self::ObserverContact(s) - | Self::ObserverName(s) - | Self::MonumentName(s) - | Self::MonumentNumber(s) - | Self::ProjectName(s) - | Self::MarkerName(s) - | Self::MarkerNumber(s) - | Self::SoftwareName(s) - | Self::Geophysical(s) - | Self::Climatic(s) - | Self::AntennaType(s) - | Self::AntennaMount(s) - | Self::AntennaRadomeType(s) - | Self::AntennaRadomeNumber(s) - | Self::AntennaNumber(s) - | Self::OperatorName(s) - | Self::UserID(s) - | Self::AgencyName(s) => { - let s_len = s.len(); - s_len + 1 + Message::bnxi_encoding_size(s_len as u32) // FID - }, - } - } - - /// Returns expected [FieldID] for [Self] - pub(crate) fn to_field_id(&self) -> FieldID { - match self { - Self::Comment(_) => FieldID::Comment, - Self::OperatorName(_) => FieldID::OperatorName, - Self::SiteLocation(_) => FieldID::SiteLocation, - Self::SiteOperator(_) => FieldID::SiteOperator, - Self::SiteOperatorAgency(_) => FieldID::SiteOperatorAgency, - Self::SiteOperatorContact(_) => FieldID::SiteOperatorContact, - Self::SiteName(_) => FieldID::SiteName, - Self::MonumentName(_) => FieldID::MonumentName, - Self::MonumentNumber(_) => FieldID::MonumentNumber, - Self::ProjectName(_) => FieldID::ProjectName, - Self::MarkerName(_) => FieldID::MarkerName, - Self::MarkerNumber(_) => FieldID::MarkerNumber, - Self::ObserverContact(_) => FieldID::ObserverContact, - Self::ObserverName(_) => FieldID::ObserverName, - Self::Extra(_) => FieldID::Extra, - Self::UserID(_) => FieldID::UserID, - Self::Climatic(_) => FieldID::Climatic, - Self::Geophysical(_) => FieldID::Geophysical, - Self::SoftwareName(_) => FieldID::SoftwareName, - Self::AgencyName(_) => FieldID::AgencyName, - Self::AntennaType(_) => FieldID::AntennaType, - Self::AntennaMount(_) => FieldID::AntennaMount, - Self::AntennaNumber(_) => FieldID::AntennaNumber, - Self::AntennaRadomeType(_) => FieldID::AntennaRadomeType, - Self::AntennaRadomeNumber(_) => FieldID::AntennaRadomeNumber, - Self::ReceiverFirmwareVersion(_) => FieldID::ReceiverFirmwareVersion, - Self::ReceiverNumber(_) => FieldID::ReceiverNumber, - Self::ReceiverType(_) => FieldID::ReceiverType, - Self::SiteNumber(_) => FieldID::SiteNumber, - Self::ReferenceDate(_) => FieldID::ReferenceDate, - Self::ReferenceName(_) => FieldID::ReferenceName, - Self::ReferenceNumber(_) => FieldID::ReferenceNumber, - } - } - - /// [MonumentGeoFrame] decoding attempt from given [FieldID] - pub(crate) fn decode(big_endian: bool, buf: &[u8]) -> Result { - if buf.len() < 2 { - // smallest size - return Err(Error::NotEnoughBytes); - } - - // decode FID - let (fid, mut ptr) = Message::decode_bnxi(&buf, big_endian); - let fid = FieldID::from(fid); - - match fid { - FieldID::Comment - | FieldID::AntennaNumber - | FieldID::AntennaType - | FieldID::AntennaMount - | FieldID::AntennaRadomeNumber - | FieldID::AntennaRadomeType - | FieldID::AgencyName - | FieldID::Climatic - | FieldID::Geophysical - | FieldID::MonumentName - | FieldID::MonumentNumber - | FieldID::MarkerName - | FieldID::MarkerNumber - | FieldID::ObserverContact - | FieldID::ObserverName - | FieldID::ProjectName - | FieldID::SiteLocation - | FieldID::ReceiverFirmwareVersion - | FieldID::ReceiverType - | FieldID::ReceiverNumber - | FieldID::Extra => { - // can't decode 1-4b - if buf.len() < 1 + ptr { - return Err(Error::NotEnoughBytes); - } - - // decode slen - let (s_len, size) = Message::decode_bnxi(&buf[ptr..], big_endian); - let s_len = s_len as usize; - ptr += size; - - if buf.len() - ptr < s_len { - return Err(Error::NotEnoughBytes); // can't parse entire string - } - - match std::str::from_utf8(&buf[ptr..ptr + s_len]) { - Ok(s) => match fid { - FieldID::Comment => Ok(Self::Comment(s.to_string())), - FieldID::MonumentName => Ok(Self::MonumentName(s.to_string())), - FieldID::MonumentNumber => Ok(Self::MonumentNumber(s.to_string())), - FieldID::ProjectName => Ok(Self::ProjectName(s.to_string())), - FieldID::ObserverName => Ok(Self::ObserverName(s.to_string())), - FieldID::ObserverContact => Ok(Self::ObserverContact(s.to_string())), - FieldID::SoftwareName => Ok(Self::SoftwareName(s.to_string())), - FieldID::MarkerName => Ok(Self::MarkerName(s.to_string())), - FieldID::MarkerNumber => Ok(Self::MarkerNumber(s.to_string())), - FieldID::Extra => Ok(Self::Extra(s.to_string())), - FieldID::Climatic => Ok(Self::Climatic(s.to_string())), - FieldID::Geophysical => Ok(Self::Geophysical(s.to_string())), - FieldID::AgencyName => Ok(Self::AgencyName(s.to_string())), - FieldID::AntennaType => Ok(Self::AntennaType(s.to_string())), - FieldID::AntennaMount => Ok(Self::AntennaMount(s.to_string())), - FieldID::AntennaNumber => Ok(Self::AntennaNumber(s.to_string())), - FieldID::AntennaRadomeType => Ok(Self::AntennaRadomeType(s.to_string())), - FieldID::AntennaRadomeNumber => { - Ok(Self::AntennaRadomeNumber(s.to_string())) - }, - FieldID::ReceiverFirmwareVersion => { - Ok(Self::ReceiverFirmwareVersion(s.to_string())) - }, - FieldID::ReceiverNumber => Ok(Self::ReceiverNumber(s.to_string())), - FieldID::ReceiverType => Ok(Self::ReceiverType(s.to_string())), - FieldID::OperatorName => Ok(Self::OperatorName(s.to_string())), - FieldID::SiteLocation => Ok(Self::SiteLocation(s.to_string())), - FieldID::SiteName => Ok(Self::SiteName(s.to_string())), - FieldID::SiteNumber => Ok(Self::SiteNumber(s.to_string())), - FieldID::ReferenceDate => Ok(Self::ReferenceDate(s.to_string())), - FieldID::ReferenceName => Ok(Self::ReferenceName(s.to_string())), - FieldID::ReferenceNumber => Ok(Self::ReferenceNumber(s.to_string())), - FieldID::UserID => Ok(Self::UserID(s.to_string())), - FieldID::SiteOperator => Ok(Self::SiteOperator(s.to_string())), - FieldID::SiteOperatorAgency => Ok(Self::SiteOperatorAgency(s.to_string())), - FieldID::SiteOperatorContact => { - Ok(Self::SiteOperatorContact(s.to_string())) - }, - // TODO - FieldID::AntennaEcef3D - | FieldID::Geocode - | FieldID::AntennaOffset3D - | FieldID::AntennaGeo3D - | FieldID::Unknown => Err(Error::NonSupportedMesssage(24)), - }, - Err(_) => { - // println!("bnx00-str: utf8 error {}", e); - Err(Error::Utf8Error) - }, - } - }, - _ => Err(Error::UnknownRecordFieldId), - } - } - - /// Encodes [Self] into buffer, returns encoded size (total bytes). - /// [Self] must fit in preallocated buffer. - pub fn encode(&self, big_endian: bool, buf: &mut [u8]) -> Result { - let size = self.encoding_size(); - if buf.len() < size { - return Err(Error::NotEnoughBytes); - } - - // encode FID - let fid = self.to_field_id() as u32; - let mut ptr = Message::encode_bnxi(fid, big_endian, buf)?; - - match self { - Self::Comment(s) - | Self::UserID(s) - | Self::SiteOperator(s) - | Self::SiteOperatorAgency(s) - | Self::OperatorName(s) - | Self::SiteLocation(s) - | Self::SiteOperatorContact(s) - | Self::SiteNumber(s) - | Self::ObserverName(s) - | Self::ProjectName(s) - | Self::ReferenceName(s) - | Self::MonumentNumber(s) - | Self::ReferenceDate(s) - | Self::ReferenceNumber(s) - | Self::ObserverContact(s) - | Self::MonumentName(s) - | Self::SiteName(s) - | Self::Extra(s) - | Self::SoftwareName(s) - | Self::Climatic(s) - | Self::Geophysical(s) - | Self::AgencyName(s) - | Self::MarkerName(s) - | Self::MarkerNumber(s) - | Self::ReceiverFirmwareVersion(s) - | Self::ReceiverNumber(s) - | Self::ReceiverType(s) - | Self::AntennaType(s) - | Self::AntennaNumber(s) - | Self::AntennaMount(s) - | Self::AntennaRadomeType(s) - | Self::AntennaRadomeNumber(s) => { - // encode strlen - let s_len = s.len(); - let size = Message::encode_bnxi(s_len as u32, big_endian, &mut buf[ptr..])?; - ptr += size; - - buf[ptr..ptr + s_len].clone_from_slice(s.as_bytes()); // utf8 encoding - }, - } - - Ok(size) - } -} - -#[cfg(test)] -mod test { - use super::*; - #[test] - fn geo_comments() { - let frame = MonumentGeoFrame::Comment("Hello".to_string()); - assert_eq!(frame.encoding_size(), 5 + 2); - - let big_endian = true; - let mut buf = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - let size = frame.encode(big_endian, &mut buf).unwrap(); - - assert_eq!(size, frame.encoding_size()); - assert_eq!( - buf, - [0, 5, 'H' as u8, 'e' as u8, 'l' as u8, 'l' as u8, 'o' as u8, 0, 0, 0, 0, 0, 0] - ); - - let decoded = MonumentGeoFrame::decode(big_endian, &buf).unwrap(); - - assert_eq!(decoded, frame); - } - #[test] - fn geo_climatic() { - let frame = MonumentGeoFrame::Climatic("ABC".to_string()); - assert_eq!(frame.encoding_size(), 3 + 2); - - let big_endian = true; - let mut buf = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - let size = frame.encode(big_endian, &mut buf).unwrap(); - - assert_eq!(size, frame.encoding_size()); - assert_eq!( - buf, - [14, 3, 'A' as u8, 'B' as u8, 'C' as u8, 0, 0, 0, 0, 0, 0] - ); - - let decoded = MonumentGeoFrame::decode(big_endian, &buf).unwrap(); - - assert_eq!(decoded, frame); - } -} diff --git a/binex/src/message/record/monument/mod.rs b/binex/src/message/record/monument/mod.rs index ed0edd529..a228547c8 100644 --- a/binex/src/message/record/monument/mod.rs +++ b/binex/src/message/record/monument/mod.rs @@ -1,38 +1,62 @@ //! Monument / Geodetic marker frames use crate::{ - message::time::{decode_gpst_epoch, encode_epoch, TimeResolution}, + message::{ + time::{decode_gpst_epoch, encode_epoch, TimeResolution}, + Message, + }, Error, }; use hifitime::{Epoch, TimeScale}; +use std::str::from_utf8 as str_from_utf8; mod fid; -mod frame; mod src; // private use fid::FieldID; // public -pub use frame::MonumentGeoFrame; pub use src::MonumentGeoMetadata; +#[derive(Debug, Clone, PartialEq)] +/// [GeoStringFrame] helps us encode / decode +/// readable [MonumentGeoRecord] entries, which makes +/// the vast majority of supported frames +struct GeoStringFrame { + /// [FieldID] frame identifier + pub fid: FieldID, + /// readable string + pub string: String, +} + +impl GeoStringFrame { + pub fn new(fid: FieldID, s: &str) -> Self { + Self { + fid, + string: s.to_string(), + } + } + pub fn encoding_size(&self) -> usize { + let mut size = 2; // FID + FID_1_4 (will never exceed 1) + let s_len = self.string.len(); + size += Message::bnxi_encoding_size(s_len as u32); + size += s_len; + size + } +} + #[derive(Debug, Clone, Default, PartialEq)] pub struct MonumentGeoRecord { /// [Epoch] pub epoch: Epoch, /// Source of this information - pub source_meta: MonumentGeoMetadata, - /// Frames also refered to as Subrecords - pub frames: Vec, -} - -impl Iterator for MonumentGeoRecord { - type Item = MonumentGeoFrame; - fn next(&mut self) -> Option { - self.frames.iter().next().cloned() - } + pub meta: MonumentGeoMetadata, + /// Readable comments (if any), repeat as needed. + pub comments: Vec, + /// Readable frames that we expose with high level methods + frames: Vec, } impl MonumentGeoRecord { @@ -45,8 +69,7 @@ impl MonumentGeoRecord { /// follows: FID dependent sequence. See [FieldID]. const MIN_SIZE: usize = 5 + 1 + 1; - /// Creates new empty [MonumentGeoRecord], which is not suitable for encoding yet. - /// Use other method to customize it! + /// Creates new [MonumentGeoRecord] with basic setup information. /// ``` /// use binex::prelude::{ /// Epoch, @@ -57,38 +80,139 @@ impl MonumentGeoRecord { /// /// let t = Epoch::from_gpst_seconds(60.0 + 0.75); /// - /// let record = MonumentGeoRecord::new(t, MonumentGeoMetadata::RNX2BIN) - /// .with_comment("A B C") - /// // read comments carefuly. For example, unlike `comments` - /// // we're not allowed to define more than one geophysical_info. - /// // Otherwise, to frame to be forged will not respect the standards. + /// let record = MonumentGeoRecord::new( + /// t, + /// MonumentGeoMetadata::RNX2BIN, + /// "Fancy GNSS receiver", + /// "Magnificent antenna", + /// "SITE34", // Site name + /// "SITE34", // Site DOMES number + /// ); + /// + /// // customize as you need + /// let record = record. + /// with_comment("you can add") + /// .with_comment("as many as you need") + /// .with_extra_info("Experiment or setup context") /// .with_geophysical_info("Eurasian plate") - /// .with_climatic_info("Rain!"); + /// .with_climatic_info("Climatic Model XXXX"); /// + /// // define your preference, + /// // which really impacts the decoder's end + /// let big_endian = true; + /// + /// // buffer is too small! + /// // you should always use .encoding_size() + /// // to double check the size you need /// let mut buf = [0; 8]; - /// match record.encode(true, &mut buf) { - /// Ok(_) => { - /// panic!("encoding should have failed!"); - /// }, - /// Err(Error::NotEnoughBytes) => { - /// // This frame does not fit in this pre allocated buffer. - /// // You should always tie your allocations to .encoding_size() ! - /// }, - /// Err(e) => { - /// panic!("{} error should not have happened!", e); - /// }, - /// } + /// assert!(record.encode(big_endian, &mut buf).is_err()); + /// + /// let mut buf = [0; 256]; + /// record.encode(true, &mut buf) + /// .unwrap(); + /// ``` + /// + /// Another option is to use the Default constructor. + /// But in this case you must pay attention to at least add + /// one custom field (like one comments) otherwise the resulting + /// frame would not be valid + /// ``` + /// use binex::prelude::{ + /// Epoch, + /// Error, + /// MonumentGeoRecord, + /// MonumentGeoMetadata, + /// }; + /// + /// let t = Epoch::from_gpst_seconds(60.0 + 0.75); + /// + /// // pay attention that using defaults: + /// // - Epoch is GPST(t=0) + /// // - Default context setup! + /// let mut record = MonumentGeoRecord::default() + /// .with_comment("Unknown context"); + /// + /// record.epoch = t; + /// record.meta = MonumentGeoMetadata::RNX2BIN; /// /// let mut buf = [0; 64]; - /// let _ = record.encode(true, &mut buf) + /// record.encode(true, &mut buf) /// .unwrap(); /// ``` - pub fn new(epoch: Epoch, meta: MonumentGeoMetadata) -> Self { - Self { + pub fn new( + epoch: Epoch, + meta: MonumentGeoMetadata, + receiver_model: &str, + antenna_model: &str, + geodetic_marker_name: &str, + geodetic_marker_number: &str, + ) -> Self { + let mut s = Self::default(); + s.epoch = epoch; + s.meta = meta; + s.frames + .push(GeoStringFrame::new(FieldID::ReceiverType, receiver_model)); + s.frames + .push(GeoStringFrame::new(FieldID::AntennaType, antenna_model)); + s.frames.push(GeoStringFrame::new( + FieldID::MarkerName, + geodetic_marker_name, + )); + s.frames.push(GeoStringFrame::new( + FieldID::MarkerNumber, + geodetic_marker_number, + )); + s + } + + /// IGS data production special macro. + /// Refer to [Self::new] for more information. + pub fn new_igs( + epoch: Epoch, + receiver_model: &str, + antenna_model: &str, + geodetic_marker_name: &str, + geodetic_marker_number: &str, + site_location: &str, + site_name: &str, + ) -> Self { + Self::new( epoch, - source_meta: meta, - frames: Vec::with_capacity(8), - } + MonumentGeoMetadata::IGS, + receiver_model, + antenna_model, + geodetic_marker_name, + geodetic_marker_number, + ) + .with_agency("IGS") + .with_site_location(site_location) + .with_site_name(site_name) + } + + /// New [MonumentGeoRecord] with emphasis that this + /// results of a RINEX conversion (special context). + /// Refer to [Self::new] for more information. + pub fn new_rinex2bin( + epoch: Epoch, + receiver_model: &str, + antenna_model: &str, + geodetic_marker_name: &str, + geodetic_marker_number: &str, + agency: &str, + site_location: &str, + site_name: &str, + ) -> Self { + Self::new( + epoch, + MonumentGeoMetadata::RNX2BIN, + receiver_model, + antenna_model, + geodetic_marker_name, + geodetic_marker_number, + ) + .with_agency(agency) + .with_site_location(site_location) + .with_site_name(site_name) } /// [Self] decoding attempt from buffered content. @@ -106,49 +230,68 @@ impl MonumentGeoRecord { big_endian: bool, buf: &[u8], ) -> Result { + let mut ret = Self::default(); + if mlen < Self::MIN_SIZE { // does not look good return Err(Error::NotEnoughBytes); } // decode timestamp - let epoch = decode_gpst_epoch(big_endian, time_res, &buf)?; + ret.epoch = decode_gpst_epoch(big_endian, time_res, &buf)?; // decode source meta - let source_meta = MonumentGeoMetadata::from(buf[5]); + ret.meta = MonumentGeoMetadata::from(buf[5]); - // parse inner frames (= subrecords) + // parse inner frames let mut ptr = 6; - let mut frames = Vec::::with_capacity(8); - // this method tolerates badly duplicated subrecords while ptr < mlen { - // decode field id - match MonumentGeoFrame::decode(big_endian, &buf[ptr..]) { - Ok(fr) => { - ptr += fr.encoding_size(); - frames.push(fr); - }, - Err(_) => { - if ptr == 6 { - // did not parse a single record: incorrect message - return Err(Error::NotEnoughBytes); - } else { - break; // parsed all records - } - }, + // decode Frame ID + let (fid, size) = Message::decode_bnxi(&buf[ptr..], big_endian); + let fid = FieldID::from(fid); + ptr += size; + + if mlen < ptr + 1 { + // cant decode 1-4b + break; } + + // decode strlen + let (s_len, size) = Message::decode_bnxi(&buf[ptr..], big_endian); + let s_len = s_len as usize; + ptr += size; + + // decode str: tolerance to bad utf8 + if let Ok(s) = str_from_utf8(&buf[ptr..ptr + s_len]) { + // append geo frame + match fid { + FieldID::Comments => { + ret.comments.push(s.to_string()); + }, + FieldID::AntennaEcef3D | FieldID::AntennaGeo3D | FieldID::AntennaOffset3D => { + // TODO: unhandled yet ! + }, + FieldID::Unknown => { + // bad ID: debug trace ? + }, + _ => { + ret.frames.push(GeoStringFrame::new(fid, s)); + }, + } + } + ptr += s_len; } - Ok(Self { - epoch, - frames, - source_meta, - }) + Ok(ret) } - /// Encodes [Self] into buffer, returns encoded size (total bytes). - /// [Self] must fit in preallocated buffer. + /// Encodes [MonumentGeoRecord] into buffer, returns encoded size (total bytes). + /// [MonumentGeoRecord] must fit in preallocated buffer. + // TODO: missing following fields + // - AntennaECEF3D + // - AntennaGeo3D + // - AntennaOffset3D pub fn encode(&self, big_endian: bool, buf: &mut [u8]) -> Result { let size = self.encoding_size(); if buf.len() < size { @@ -159,13 +302,40 @@ impl MonumentGeoRecord { let mut ptr = encode_epoch(self.epoch.to_time_scale(TimeScale::GPST), big_endian, buf)?; // encode source meta - buf[ptr] = self.source_meta.into(); + buf[ptr] = self.meta.into(); ptr += 1; - // encode subrecords + // encode all comments + for comments in self.comments.iter() { + // encode FID + let fid = FieldID::Comments; + let size = Message::encode_bnxi(fid as u32, big_endian, &mut buf[ptr..])?; + ptr += size; + + // encode strlen + let strlen = comments.len(); + let size = Message::encode_bnxi(strlen as u32, big_endian, &mut buf[ptr..])?; + ptr += size; + + // encode str + buf[ptr..ptr + strlen].clone_from_slice(comments.as_bytes()); + ptr += strlen; + } + + // encode all geo string frames for fr in self.frames.iter() { - let offs = fr.encode(big_endian, &mut buf[ptr..])?; - ptr += offs; + // encode FID + let size = Message::encode_bnxi(fr.fid as u32, big_endian, &mut buf[ptr..])?; + ptr += size; + + // encode strlen + let strlen = fr.string.len(); + let size = Message::encode_bnxi(strlen as u32, big_endian, &mut buf[ptr..])?; + ptr += size; + + // encode str + buf[ptr..ptr + strlen].clone_from_slice(fr.string.as_bytes()); + ptr += strlen; } Ok(size) @@ -173,20 +343,137 @@ impl MonumentGeoRecord { /// Returns total length (bytewise) required to fully encode [Self]. /// Use this to fulfill [Self::encode] requirements. + // TODO: missing following fields + // - AntennaECEF3D + // - AntennaGeo3D + // - AntennaOffset3D pub(crate) fn encoding_size(&self) -> usize { - let mut size = 6; // tstamp + source_meta + let mut size = 6; // tstamp + meta + + // for all comments: + // 1-4 FID + // 1-4 strlen + // strlen + for comment in self.comments.iter() { + size += 1; // FID_1_4 + let s_len = comment.len(); + size += Message::bnxi_encoding_size(s_len as u32); + size += s_len; + } + + // for all encoded string: + // 1-4 FID + // 1-4 strlen + // strlen for fr in self.frames.iter() { - size += fr.encoding_size(); // content + size += 1; // FID_1_4 + let s_len = fr.string.len(); + size += Message::bnxi_encoding_size(s_len as u32); + size += s_len; } + size } - /// Add one [MonumentGeoFrame::Comment] to [MonumentGeoRecord]. - /// You can add as many as needed. + /// Add one readable comment to this [MonumentGeoRecord]. + /// You can add as many as you need. pub fn with_comment(&self, comment: &str) -> Self { + let mut s = self.clone(); + s.comments.push(comment.to_string()); + s + } + + /// Define receiver model. + pub fn with_receiver_model(&self, model: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::ReceiverType, model)); + s + } + /// Define receiver serial number (if known). + pub fn with_receiver_serial_number(&self, sn: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::ReceiverNumber, sn)); + s + } + /// Define receiver firmware version (if known). + pub fn with_receiver_firm_version(&self, version: &str) -> Self { + let mut s = self.clone(); + s.frames.push(GeoStringFrame::new( + FieldID::ReceiverFirmwareVersion, + version, + )); + s + } + + /// Define name of observer + pub fn with_observer(&self, observer: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::ObserverName, observer)); + s + } + + /// Define observer's contact (email address) + pub fn with_observer_contact(&self, contact: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::ObserverContact, contact)); + s + } + /// Define Geodetic marker name + pub fn with_geodetic_marker_name(&self, name: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::MarkerName, name)); + s + } + + /// Define Geodetic marker number (DOMES) + pub fn with_geodetic_marker_number(&self, domes: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::MarkerNumber, domes)); + s + } + + /// Define Locatio of this geodetic site + pub fn with_site_location(&self, location: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::SiteLocation, location)); + s + } + + /// Define Agency (Organization) + pub fn with_agency(&self, agency: &str) -> Self { let mut s = self.clone(); s.frames - .push(MonumentGeoFrame::Comment(comment.to_string())); + .push(GeoStringFrame::new(FieldID::AgencyName, agency)); + s + } + + /// Define Antenna model (if known) + pub fn with_antenna_model(&self, model: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::AntennaType, model)); + s + } + + /// Define Antenna serial number (if known) + pub fn with_antenna_serial_number(&self, sn: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::AntennaNumber, sn)); + s + } + + /// Define name of Geodetic site + pub fn with_site_name(&self, name: &str) -> Self { + let mut s = self.clone(); + s.frames.push(GeoStringFrame::new(FieldID::SiteName, name)); s } @@ -196,7 +483,7 @@ impl MonumentGeoRecord { pub fn with_geophysical_info(&self, info: &str) -> Self { let mut s = self.clone(); s.frames - .push(MonumentGeoFrame::Geophysical(info.to_string())); + .push(GeoStringFrame::new(FieldID::Geophysical, info)); s } @@ -205,7 +492,7 @@ impl MonumentGeoRecord { /// the message will not respect the standard definitions. pub fn with_climatic_info(&self, info: &str) -> Self { let mut s = self.clone(); - s.frames.push(MonumentGeoFrame::Climatic(info.to_string())); + s.frames.push(GeoStringFrame::new(FieldID::Climatic, info)); s } @@ -214,7 +501,23 @@ impl MonumentGeoRecord { /// the message will not respect the standard definitions. pub fn with_user_id(&self, userid: &str) -> Self { let mut s = self.clone(); - s.frames.push(MonumentGeoFrame::Comment(userid.to_string())); + s.frames.push(GeoStringFrame::new(FieldID::UserID, userid)); + s + } + + /// Provide the name of this Geodetic project (if any). + pub fn with_project_name(&self, name: &str) -> Self { + let mut s = self.clone(); + s.frames + .push(GeoStringFrame::new(FieldID::ProjectName, name)); + s + } + + /// Add one extra note (like concise description of context + /// or experiment) + pub fn with_extra_info(&self, extra: &str) -> Self { + let mut s = self.clone(); + s.frames.push(GeoStringFrame::new(FieldID::Extra, extra)); s } } @@ -242,87 +545,88 @@ mod test { ' ' as u8, 'G' as u8, 'E' as u8, 'O' as u8, ]; - match MonumentGeoRecord::decode(mlen, time_res, big_endian, &buf) { - Ok(monument) => { - assert_eq!( - monument.epoch, - Epoch::from_gpst_seconds(256.0 * 60.0 + 60.0 + 10.25) - ); - assert_eq!(monument.source_meta, MonumentGeoMetadata::IGS); - assert_eq!(monument.frames.len(), 1); - assert_eq!( - monument.frames[0], - MonumentGeoFrame::Comment("Hello GEO".to_string()) - ); - - // test mirror op - let mut target = [0, 0, 0, 0, 0, 0, 0, 0]; - assert!(monument.encode(big_endian, &mut target).is_err()); - - // test mirror op - let mut target = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - match monument.encode(big_endian, &mut target) { - Err(e) => panic!("{} should have passed", e), - Ok(_) => { - assert_eq!(target, buf,); - }, - } - }, - Err(e) => panic!("decoding error: {}", e), - } + let geo = MonumentGeoRecord::decode(mlen, time_res, big_endian, &buf).unwrap(); + + assert_eq!( + geo.epoch, + Epoch::from_gpst_seconds(256.0 * 60.0 + 60.0 + 10.25) + ); + + assert_eq!(geo.meta, MonumentGeoMetadata::IGS); + + assert_eq!(geo.comments.len(), 1); + + let comments = geo.comments.get(0).unwrap(); + assert_eq!(comments, "Hello GEO"); + + // ts +meta + FID_1_4 +strlen_1_4 +strlen + assert_eq!(geo.encoding_size(), 5 + 1 + 1 + 1 + 9); + + // test mirror op + let mut encoded = [0; 12]; + assert!(geo.encode(big_endian, &mut encoded).is_err()); + + let mut encoded = [0; 18]; + let size = geo.encode(big_endian, &mut encoded).unwrap(); + assert_eq!(size, 5 + 1 + 1 + 1 + 9); } #[test] fn monument_geo_double_comments_decoding() { - let t = Epoch::from_gpst_seconds(60.0 + 0.75); - - let record = MonumentGeoRecord::new(t, MonumentGeoMetadata::RNX2BIN) + let mut geo: MonumentGeoRecord = MonumentGeoRecord::default() .with_comment("A B C") .with_comment("D E F"); - let mut buf = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + geo.epoch = Epoch::from_gpst_seconds(60.0 + 0.75); + geo.meta = MonumentGeoMetadata::IGS; - match record.encode(true, &mut buf) { - Ok(_) => panic!("should have panic'ed!"), - Err(Error::NotEnoughBytes) => {}, - Err(e) => panic!("invalid error: {}", e), - } + // ts + meta + 2*(FID_1_4 +STR_1_4 +STR) + assert_eq!(geo.encoding_size(), 5 + 1 + 2 * (1 + 1 + 5)); - let mut buf = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; + let mut buf = [0; 16]; + assert!(geo.encode(true, &mut buf).is_err()); - match record.encode(true, &mut buf) { - Err(e) => panic!("{} should have passed!", e), - Ok(size) => { - assert_eq!(size, 20); - assert_eq!( - buf, - [0, 0, 0, 1, 3, 1, 0, 5, 65, 32, 66, 32, 67, 0, 5, 68, 32, 69, 32, 70, 0] - ); - }, - } + let mut buf = [0; 22]; + let size = geo.encode(true, &mut buf).unwrap(); - let geo = MonumentGeoRecord::new(t, MonumentGeoMetadata::IGS) + // ts + meta + 2*(FID_1_4 +strlen_1_4 +strlen) + assert_eq!(geo.encoding_size(), 5 + 1 + 2 * (1 + 1 + 5)); + assert_eq!(size, 5 + 1 + 2 * (1 + 1 + 5)); + + let mut geo = MonumentGeoRecord::default() .with_comment("Hello") .with_climatic_info("Clim"); - let mut buf = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; - - match geo.encode(true, &mut buf) { - Err(e) => panic!("{} should have passed!", e), - Ok(size) => { - assert_eq!(size, 19); - assert_eq!( - buf, - [ - 0, 0, 0, 1, 3, 2, 0, 5, 'H' as u8, 'e' as u8, 'l' as u8, 'l' as u8, - 'o' as u8, 14, 4, 'C' as u8, 'l' as u8, 'i' as u8, 'm' as u8, 0, 0 - ] - ); - }, - } + geo.epoch = Epoch::from_gpst_seconds(60.0 + 0.75); + geo.meta = MonumentGeoMetadata::IGS; + + let mut buf = [0; 19]; + let size = geo.encode(true, &mut buf).unwrap(); + assert_eq!(size, 5 + 1 + 1 + 1 + 5 + 1 + 1 + 4); + + assert_eq!( + buf, + [ + 0, + 0, + 0, + 1, + 3, + MonumentGeoMetadata::IGS as u8, + 0, + 5, + 'H' as u8, + 'e' as u8, + 'l' as u8, + 'l' as u8, + 'o' as u8, + 14, + 4, + 'C' as u8, + 'l' as u8, + 'i' as u8, + 'm' as u8, + ] + ); } } diff --git a/binex/tests/geo.rs b/binex/tests/geo.rs index 943d53f7b..7b221afe3 100644 --- a/binex/tests/geo.rs +++ b/binex/tests/geo.rs @@ -4,76 +4,45 @@ use binex::prelude::{ #[test] fn geo_message() { - let big_endian = false; + let big_endian = true; let reversed = false; let enhanced_crc = false; let t = Epoch::from_gpst_seconds(10.0 + 0.75); + let time_res = TimeResolution::QuarterSecond; - let geo = MonumentGeoRecord::new(t, MonumentGeoMetadata::IGS).with_comment("Hello"); + let mut geo = MonumentGeoRecord::default().with_comment("simple"); + geo.epoch = t; + geo.meta = MonumentGeoMetadata::RNX2BIN; let record = Record::new_monument_geo(geo); let msg = Message::new(big_endian, time_res, enhanced_crc, reversed, record); - let mut buf = [0; 24]; - msg.encode(&mut buf).unwrap(); - - assert_eq!( - buf, - [0xC2, 0, 13, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0] - ); - - let geo = MonumentGeoRecord::new(t, MonumentGeoMetadata::IGS) - .with_comment("Hello") - .with_comment("World"); + let mut encoded = [0; 128]; + msg.encode(&mut encoded).unwrap(); - let record = Record::new_monument_geo(geo); - let msg = Message::new(big_endian, time_res, enhanced_crc, reversed, record); + let decoded = Message::decode(&encoded).unwrap(); + assert_eq!(decoded, msg); - let mut buf = [0; 32]; - msg.encode(&mut buf).unwrap(); - - assert_eq!( - buf, - [ - 0xC2, 0, 20, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 5, 87, 111, 114, 108, - 100, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] + let geo = MonumentGeoRecord::new_igs( + t, + "Receiver Model", + "Antenna Model", + "Geodetic MARKER", + "MARKER NUMBER", + "Site location", + "Site name", ); - let geo = MonumentGeoRecord::new(t, MonumentGeoMetadata::IGS) - .with_comment("Hello") - .with_comment("World"); - let record = Record::new_monument_geo(geo); let msg = Message::new(big_endian, time_res, enhanced_crc, reversed, record); - let mut buf = [0; 32]; - msg.encode(&mut buf).unwrap(); + let mut encoded = [0; 128]; + msg.encode(&mut encoded).unwrap(); - assert_eq!( - buf, - [ - 0xC2, 0, 20, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 0, 5, 87, 111, 114, 108, - 100, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - let geo = MonumentGeoRecord::new(t, MonumentGeoMetadata::IGS) - .with_comment("Hello") - .with_climatic_info("Clim"); + assert_eq!(encoded[0], 226); + assert_eq!(encoded[1], 0); - let record = Record::new_monument_geo(geo); - let msg = Message::new(big_endian, time_res, enhanced_crc, reversed, record); - - let mut buf = [0; 32]; - msg.encode(&mut buf).unwrap(); - - assert_eq!( - buf, - [ - 0xC2, 0, 19, 0, 0, 0, 0, 43, 2, 0, 5, 72, 101, 108, 108, 111, 14, 4, 67, 108, 105, 109, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); + let decoded = Message::decode(&encoded).unwrap(); + assert_eq!(decoded, msg); } diff --git a/binex/tests/message.rs b/binex/tests/message.rs index 7b26fa27c..37df33a00 100644 --- a/binex/tests/message.rs +++ b/binex/tests/message.rs @@ -10,17 +10,15 @@ fn test_crc8_geo() { TimeResolution::QuarterSecond, false, false, - Record::new_monument_geo( - MonumentGeoRecord::new( - Epoch::from_gpst_seconds(61.25), - MonumentGeoMetadata::RNX2BIN, - ) - .with_climatic_info("climatic") - .with_comment("comment #1") - .with_comment("#comment 2") - .with_geophysical_info("geophysics") - .with_user_id("Custom ID#"), - ), + Record::new_monument_geo(MonumentGeoRecord::new_igs( + Epoch::from_gpst_seconds(61.25), + "Great receiver", + "Fancy antenna", + "MARKERNAME", + "MARKERNUMBER", + "SITE", + "SITENAME", + )), ); let mut buf = [0; 128]; @@ -38,22 +36,23 @@ fn test_crc16_geo() { false, false, Record::new_monument_geo( - MonumentGeoRecord::new( + MonumentGeoRecord::new_igs( Epoch::from_gpst_seconds(61.25), - MonumentGeoMetadata::RNX2BIN, + "Great receiver", + "Fancy antenna", + "MARKERNAME", + "MARKERNUMBER", + "SITE", + "SITENAME", ) - .with_climatic_info("we need very lengthy climatic info") - .with_comment("and very long comments") - .with_comment("yet another comment") - .with_geophysical_info("interesting geophysical info") - .with_user_id("Custom ID#"), + .with_climatic_info("test") + .with_comment("super") + .with_geophysical_info("great") + .with_project_name("project"), ), ); let mut buf = [0; 128]; - assert!(msg.encode(&mut buf).is_err()); - - let mut buf = [0; 1024]; msg.encode(&mut buf).unwrap(); let parsed = Message::decode(&buf).unwrap(); From 545c89b8b43e69fe1665351d91348141249f9e16 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 12:34:39 +0100 Subject: [PATCH 14/28] Introduce ClosedSourceMeta Signed-off-by: Guillaume W. Bres --- binex/src/decoder.rs | 10 +--- binex/src/lib.rs | 63 ++++++++++++++--------- binex/src/message/mod.rs | 39 +++++++++----- binex/src/message/record/ephemeris/mod.rs | 2 +- binex/src/stream.rs | 24 +++++++++ binex/tests/decoder.rs | 8 +-- 6 files changed, 97 insertions(+), 49 deletions(-) diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index 1b0828ba6..1845cd3af 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -80,7 +80,7 @@ impl<'a, R: Read> Decoder<'a, R> { /// // do something /// }, /// Some(Err(e)) => match e { - /// Error::IoError(e) => { + /// Error::IoError => { /// // any I/O error should be handled /// // and user should react accordingly, /// break; @@ -90,9 +90,6 @@ impl<'a, R: Read> Decoder<'a, R> { /// // - reversed streams are not supported yet /// // - little endian streams are not supported yet /// }, - /// Error::InvalidStartofStream => { - /// // other errors give meaningful information - /// }, /// _ => {}, /// }, /// None => { @@ -136,7 +133,7 @@ impl<'a, R: Read> Decoder<'a, R> { /// // do something /// }, /// Some(Err(e)) => match e { - /// Error::IoError(e) => { + /// Error::IoError => { /// // any I/O error should be handled /// // and user should react accordingly, /// break; @@ -146,9 +143,6 @@ impl<'a, R: Read> Decoder<'a, R> { /// // - reversed streams are not supported yet /// // - little endian streams are not supported yet /// }, - /// Error::InvalidStartofStream => { - /// // other errors give meaningful information - /// }, /// _ => {}, /// }, /// None => { diff --git a/binex/src/lib.rs b/binex/src/lib.rs index a725134a2..5acb99a52 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -25,40 +25,55 @@ pub mod prelude { pub use hifitime::Epoch; } -#[derive(Error, Debug)] +use crate::stream::Provider; + +/// [ClosedSourceMeta] helps identify a closed source message we cannot interprate. +#[derive(Debug)] +pub struct ClosedSourceMeta { + // decoded MID (as is) + pub mid: u32, + // decoded MLEN (as is) + pub mlen: u32, + // payload offset in buffer + pub offset: usize, + // [Provider] of this message. Only this organization may continue the decoding process. + pub provider: Provider, +} + +#[derive(Debug)] pub enum Error { - #[error("not enough bytes available")] + /// Not enough bytes available to continue decoding process NotEnoughBytes, - #[error("i/o error")] - IoError(#[from] std::io::Error), - #[error("invalid start of stream")] - InvalidStartofStream, - #[error("no sync byte")] + /// I/O error + IoError, + /// Missing SYNC byte NoSyncByte, - #[error("reversed streams are not supported yet")] + // InvalidStartofStream, + /// Library limitation: reversed streams are not supported ReversedStream, - #[error("little endian encoded streams not supported yet")] + /// Library limitation: little endian streams are not verified yet LittleEndianStream, - #[error("enhanced crc is not supported yet")] + /// Library limitation: enhanced CRC is not supported yet EnhancedCrc, - #[error("non supported timescale")] + /// Found an unsupported timescale that we cannot interprate. NonSupportedTimescale, - // #[error("unknown message")] - // UnknownMessage, - #[error("unknown record field id")] - UnknownRecordFieldId, - #[error("utf8 error")] + /// Found unknown message ID + UnknownMessage, + /// Error while attempting to interprate UTF-8 (invalid ASCII) Utf8Error, - #[error("missing crc bytes")] + /// Message is missing CRC field and cannot be verified MissingCRC, - #[error("received invalid crc")] - BadCRC, - #[error("incomplete: need more data")] + /// Message corrupt: received CRC does not match expected CRC + CorrupctBadCRC, + /// Incomplete message: need more data to complete IncompleteMessage(usize), - #[error("non supported message: library limitation")] + /// Library limitation: not all open source [Message]s supported yet NonSupportedMesssage(usize), - #[error("message too large: library limitation")] - // This message should never happen: library is to be designed - // to support largest open source (fully disclosed) message frame + /// Library limtation: should never happen, because this library + /// will be designed to parse all open source [Message]s. + /// This may happen as either we're still in development (bad internal design) + /// or for format that we still do not support (temporarily "ok") TooLargeInternalLimitation, + /// Found closed source message + ClosedSourceMessage(ClosedSourceMeta), } diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 937c04941..2913bdd2c 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -14,7 +14,7 @@ pub(crate) use mid::MessageID; use checksum::Checksum; -use crate::{constants::Constants, Error}; +use crate::{constants::Constants, stream::Provider, ClosedSourceMeta, Error}; #[derive(Debug, Clone, PartialEq, Default)] pub struct Message { @@ -72,7 +72,11 @@ impl Message { total } - /// Decoding attempt from buffered content. + /// [Message] decoding attempt from buffered content. + /// Buffer must contain sync byte and the following frame must match + /// the specification if an open source BINEX [Message]. + /// For closed source [Message]s, we return [Error::ClosedSourceMessage] + /// with header information. pub fn decode(buf: &[u8]) -> Result { let sync_off; let buf_len = buf.len(); @@ -80,6 +84,7 @@ impl Message { let mut reversed = false; let mut enhanced_crc = false; let time_res = TimeResolution::QuarterSecond; + let mut closed_provider = Option::::None; // 1. locate SYNC byte if let Some(offset) = Self::locate(Constants::FWDSYNC_BE_STANDARD_CRC, buf) { @@ -170,8 +175,18 @@ impl Message { Record::new_ephemeris_frame(fr) }, id => { - println!("found unsupported msg id={:?}", id); - return Err(Error::NonSupportedMesssage(mlen)); + // verify this is not a closed source message + if let Some(provider) = Provider::match_any(mid.into()) { + return Err(Error::ClosedSourceMessage(ClosedSourceMeta { + mid: mid.into(), + mlen: mlen as u32, + offset: ptr, + provider, + })); + } else { + println!("found unsupported msg id={:?}", id); + return Err(Error::NonSupportedMesssage(mlen)); + } }, }; @@ -583,13 +598,13 @@ mod test { let buf = [0, 0, 0, 0, 0]; match Message::decode(&buf) { Err(Error::NoSyncByte) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } let buf = [0, 0, 0, 0, 0]; match Message::decode(&buf) { Err(Error::NoSyncByte) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } } @@ -599,7 +614,7 @@ mod test { let buf = [Constants::FWDSYNC_BE_ENHANCED_CRC, 0, 0, 0]; match Message::decode(&buf) { Err(Error::EnhancedCrc) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } } @@ -609,7 +624,7 @@ mod test { let buf = [Constants::FWDSYNC_LE_STANDARD_CRC, 0, 0, 0]; match Message::decode(&buf) { Err(Error::LittleEndianStream) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } } @@ -619,25 +634,25 @@ mod test { let buf = [Constants::REVSYNC_BE_STANDARD_CRC, 0, 0, 0]; match Message::decode(&buf) { Err(Error::ReversedStream) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } let buf = [Constants::REVSYNC_BE_ENHANCED_CRC, 0, 0, 0]; match Message::decode(&buf) { Err(Error::ReversedStream) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } let buf = [Constants::REVSYNC_LE_STANDARD_CRC, 0, 0, 0]; match Message::decode(&buf) { Err(Error::ReversedStream) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } let buf = [Constants::REVSYNC_LE_ENHANCED_CRC, 0, 0, 0]; match Message::decode(&buf) { Err(Error::ReversedStream) => {}, - Err(e) => panic!("returned unexpected error: {}", e), + Err(e) => panic!("returned unexpected error: {:?}", e), _ => panic!("should have paniced"), } } diff --git a/binex/src/message/record/ephemeris/mod.rs b/binex/src/message/record/ephemeris/mod.rs index d0cdf2bcf..5c7bc34d3 100644 --- a/binex/src/message/record/ephemeris/mod.rs +++ b/binex/src/message/record/ephemeris/mod.rs @@ -89,7 +89,7 @@ impl EphemerisFrame { let fr: GALEphemeris = GALEphemeris::decode(big_endian, &buf[size..])?; Ok(Self::GAL(fr)) }, - _ => Err(Error::UnknownRecordFieldId), + _ => Err(Error::NonSupportedMesssage(0)), } } diff --git a/binex/src/stream.rs b/binex/src/stream.rs index 9c9d45bad..ba5db854d 100644 --- a/binex/src/stream.rs +++ b/binex/src/stream.rs @@ -22,6 +22,30 @@ pub enum Provider { Topcon, } +impl Provider { + /// Identify potential closed source [Provider] + /// from parsed MID (u32) + pub(crate) fn match_any(mid: u32) -> Option { + if mid >= 0x80 && mid <= 0x87 { + Some(Self::UCAR) + } else if mid >= 0x88 && mid <= 0xa7 { + Some(Self::Ashtech) + } else if mid >= 0xa8 && mid <= 0xaf { + Some(Self::Topcon) + } else if mid >= 0xb0 && mid <= 0xb3 { + Some(Self::GPSSolutions) + } else if mid >= 0xb4 && mid <= 0xb7 { + Some(Self::NRCan) + } else if mid >= 0xb8 && mid <= 0xbf { + Some(Self::JPL) + } else if mid >= 0xc0 && mid <= 0xc3 { + Some(Self::ColoradoUnivBoulder) + } else { + None + } + } +} + /// Closed source frame that we can encode but not interprate. /// This particular [StreamElement] can be either a part of a continuous serie or self sustainable. pub struct ClosedSourceElement<'a> { diff --git a/binex/tests/decoder.rs b/binex/tests/decoder.rs index d3404e980..b1786d622 100644 --- a/binex/tests/decoder.rs +++ b/binex/tests/decoder.rs @@ -16,9 +16,9 @@ fn mfle20190130() { }, Some(Ok(StreamElement::ClosedSource(element))) => {}, Some(Err(e)) => match e { - Error::IoError(e) => panic!("i/o error: {}", e), + Error::IoError => panic!("i/o error"), e => { - println!("err={}", e); + println!("err={:?}", e); }, }, None => { @@ -46,9 +46,9 @@ fn gziped_files() { }, Some(Ok(StreamElement::ClosedSource(element))) => {}, Some(Err(e)) => match e { - Error::IoError(e) => panic!("i/o error: {}", e), + Error::IoError => panic!("i/o error"), e => { - println!("err={}", e); + println!("err={:?}", e); }, }, None => { From 9a914200e9ea8dfc720313498e3ef892585d6093 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 12:37:20 +0100 Subject: [PATCH 15/28] update readme Signed-off-by: Guillaume W. Bres --- binex/README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/binex/README.md b/binex/README.md index 4c9cd73b5..c1eb88432 100644 --- a/binex/README.md +++ b/binex/README.md @@ -7,15 +7,11 @@ BINEX is a simple library to decode and encode BINEX messages. BINEX stands for BINary EXchange and is the "real time" stream oriented -version of the RINEX format. +version of the RINEX format. It is to this day, the only open source protocol +to encode GNSS and navigation data. -RINEX is a readable text format which is based on line termination and allows describing -from the minimum requirement for GNSS navigation up to very precise navigation and -other side GNSS applications. - -BINEX is a binary stream (non readable) conversion to that, dedicated to GNSS receivers and hardware interfacing. -Like RINEX, it is an open source format, the specifications are described by -[UNAVCO here](https://www.unavco.org/data/gps-gnss/data-formats/binex). +While RINEX is readable and based on line termination, BINEX is real-time and +hardware orientated (at the GNSS receiver firmware level). This library allows easy message encoding and decoding, and aims at providing seamless convertion from RINEX back and forth. From 258afd5f08087c0d0ff02cb2a348962cbef2be1e Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 12:37:51 +0100 Subject: [PATCH 16/28] prepare for next release Signed-off-by: Guillaume W. Bres --- binex/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binex/Cargo.toml b/binex/Cargo.toml index f58dfcb4a..fd3e6e0ce 100644 --- a/binex/Cargo.toml +++ b/binex/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "binex" -version = "0.2.0" +version = "0.3.0" license = "MIT OR Apache-2.0" authors = ["Guillaume W. Bres "] description = "BINEX Binary RINEX encoder and decoder" From 6de09dc10cabf955e4060e9a855d8c3e88a0e9b5 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 13:00:30 +0100 Subject: [PATCH 17/28] Reflect uniqueness of some of the readable elements Signed-off-by: Guillaume W. Bres --- binex/src/message/record/monument/mod.rs | 97 +++++++++++++----------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/binex/src/message/record/monument/mod.rs b/binex/src/message/record/monument/mod.rs index a228547c8..a6834dcbf 100644 --- a/binex/src/message/record/monument/mod.rs +++ b/binex/src/message/record/monument/mod.rs @@ -24,10 +24,10 @@ pub use src::MonumentGeoMetadata; /// [GeoStringFrame] helps us encode / decode /// readable [MonumentGeoRecord] entries, which makes /// the vast majority of supported frames -struct GeoStringFrame { +pub struct GeoStringFrame { /// [FieldID] frame identifier - pub fid: FieldID, - /// readable string + pub(crate) fid: FieldID, + /// Readable string pub string: String, } @@ -38,13 +38,6 @@ impl GeoStringFrame { string: s.to_string(), } } - pub fn encoding_size(&self) -> usize { - let mut size = 2; // FID + FID_1_4 (will never exceed 1) - let s_len = self.string.len(); - size += Message::bnxi_encoding_size(s_len as u32); - size += s_len; - size - } } #[derive(Debug, Clone, Default, PartialEq)] @@ -56,7 +49,7 @@ pub struct MonumentGeoRecord { /// Readable comments (if any), repeat as needed. pub comments: Vec, /// Readable frames that we expose with high level methods - frames: Vec, + pub frames: Vec, } impl MonumentGeoRecord { @@ -276,7 +269,19 @@ impl MonumentGeoRecord { // bad ID: debug trace ? }, _ => { - ret.frames.push(GeoStringFrame::new(fid, s)); + // reflect uniqueness of this information + // if stream badly encodes many of these, we only lacth the latest one + if let Some(fr) = ret + .frames + .iter_mut() + .filter(|fr| fr.fid == fid) + .reduce(|k, _| k) + { + fr.string = s.to_string(); // overwrite with latest + } else { + // store new readable element + ret.frames.push(GeoStringFrame::new(fid, s)); + } }, } } @@ -383,97 +388,103 @@ impl MonumentGeoRecord { s } + // reflect uniqueness of this information + // if stream badly encodes many of these, we only lacth the latest one + fn push_or_update(&mut self, fid: FieldID, value: &str) { + if let Some(fr) = self + .frames + .iter_mut() + .filter(|fr| fr.fid == fid) + .reduce(|k, _| k) + { + fr.string = value.to_string(); // overwrite / update + } else { + // store new readable element + self.frames.push(GeoStringFrame::new(fid, value)); + } + } + /// Define receiver model. pub fn with_receiver_model(&self, model: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::ReceiverType, model)); + s.push_or_update(FieldID::ReceiverType, model); s } + /// Define receiver serial number (if known). pub fn with_receiver_serial_number(&self, sn: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::ReceiverNumber, sn)); + s.push_or_update(FieldID::ReceiverNumber, sn); s } + /// Define receiver firmware version (if known). pub fn with_receiver_firm_version(&self, version: &str) -> Self { let mut s = self.clone(); - s.frames.push(GeoStringFrame::new( - FieldID::ReceiverFirmwareVersion, - version, - )); + s.push_or_update(FieldID::ReceiverFirmwareVersion, version); s } /// Define name of observer pub fn with_observer(&self, observer: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::ObserverName, observer)); + s.push_or_update(FieldID::ObserverName, observer); s } /// Define observer's contact (email address) pub fn with_observer_contact(&self, contact: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::ObserverContact, contact)); + s.push_or_update(FieldID::ObserverContact, contact); s } + /// Define Geodetic marker name pub fn with_geodetic_marker_name(&self, name: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::MarkerName, name)); + s.push_or_update(FieldID::MarkerName, name); s } /// Define Geodetic marker number (DOMES) pub fn with_geodetic_marker_number(&self, domes: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::MarkerNumber, domes)); + s.push_or_update(FieldID::MarkerNumber, domes); s } /// Define Locatio of this geodetic site pub fn with_site_location(&self, location: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::SiteLocation, location)); + s.push_or_update(FieldID::SiteLocation, location); s } /// Define Agency (Organization) pub fn with_agency(&self, agency: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::AgencyName, agency)); + s.push_or_update(FieldID::AgencyName, agency); s } /// Define Antenna model (if known) pub fn with_antenna_model(&self, model: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::AntennaType, model)); + s.push_or_update(FieldID::AntennaType, model); s } /// Define Antenna serial number (if known) pub fn with_antenna_serial_number(&self, sn: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::AntennaNumber, sn)); + s.push_or_update(FieldID::AntennaNumber, sn); s } /// Define name of Geodetic site pub fn with_site_name(&self, name: &str) -> Self { let mut s = self.clone(); - s.frames.push(GeoStringFrame::new(FieldID::SiteName, name)); + s.push_or_update(FieldID::SiteName, name); s } @@ -482,8 +493,7 @@ impl MonumentGeoRecord { /// otherwise, the message will not respect the standard definitions. pub fn with_geophysical_info(&self, info: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::Geophysical, info)); + s.push_or_update(FieldID::Geophysical, info); s } @@ -492,7 +502,7 @@ impl MonumentGeoRecord { /// the message will not respect the standard definitions. pub fn with_climatic_info(&self, info: &str) -> Self { let mut s = self.clone(); - s.frames.push(GeoStringFrame::new(FieldID::Climatic, info)); + s.push_or_update(FieldID::Climatic, info); s } @@ -501,15 +511,14 @@ impl MonumentGeoRecord { /// the message will not respect the standard definitions. pub fn with_user_id(&self, userid: &str) -> Self { let mut s = self.clone(); - s.frames.push(GeoStringFrame::new(FieldID::UserID, userid)); + s.push_or_update(FieldID::UserID, userid); s } /// Provide the name of this Geodetic project (if any). pub fn with_project_name(&self, name: &str) -> Self { let mut s = self.clone(); - s.frames - .push(GeoStringFrame::new(FieldID::ProjectName, name)); + s.push_or_update(FieldID::ProjectName, name); s } @@ -517,7 +526,7 @@ impl MonumentGeoRecord { /// or experiment) pub fn with_extra_info(&self, extra: &str) -> Self { let mut s = self.clone(); - s.frames.push(GeoStringFrame::new(FieldID::Extra, extra)); + s.push_or_update(FieldID::Extra, extra); s } } From 44917d670b487ddae4477cfaad918f51933d6794 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 13:00:55 +0100 Subject: [PATCH 18/28] update readme Signed-off-by: Guillaume W. Bres --- binex/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/binex/README.md b/binex/README.md index c1eb88432..4b492dc12 100644 --- a/binex/README.md +++ b/binex/README.md @@ -16,6 +16,14 @@ hardware orientated (at the GNSS receiver firmware level). This library allows easy message encoding and decoding, and aims at providing seamless convertion from RINEX back and forth. +You have two scenarios to approach a BINEX stream: + +* use our Decoder object, which works on I/O interface directly +and can represent a stream of continuous of either [Message]s (open source) +or undisclosed elements. (private prototypes) + +* use Message::decode to work on your own buffer directly. + ## Message Decoding Use the BINEX `Decoder` to decode messages from a `Readable` interface: From d1f2df393af5675b79bfb4d5daf8e4219a9ec1d7 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 14:21:53 +0100 Subject: [PATCH 19/28] introduced closed source meta Signed-off-by: Guillaume W. Bres --- binex/src/decoder.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index 1845cd3af..5e73f1ac4 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -236,6 +236,25 @@ impl<'a, R: Read> Iterator for Decoder<'a, R> { return Some(Err(Error::IncompleteMessage(mlen))); } }, + Error::ClosedSourceMessage(meta) => { + // determine whether + // - this element is self sustained (ie., fully described by this meta) + // - the followup of previous elements + // - or the last element of a serie + if self.rd_ptr + meta.mlen < 4096 { + // content is fully wrapped in buffer: expose as is + // self.past_element = Some(ClosedSourceElement { + // provider: meta.provider, + // size: meta.mlen, + // total: meta.mlen, + // raw: self.buf[self.rd_ptr..self.rd_ptr +meta.mlen], + // }); + } else { + // content is not fully wrapped up here; + // initiate or continue a serie of undisclosed element + } + return Some(Err(Error::IncompleteMessage(meta.mlen))); + }, _ => { // bad content that does not look like valid BINEX. // This is very inefficient. If returned error would increment From aa87626ed16ce7075e903082a3594928d2c4d3b4 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 14:22:42 +0100 Subject: [PATCH 20/28] introduced closed source meta Signed-off-by: Guillaume W. Bres --- binex/src/lib.rs | 2 +- binex/src/stream.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/binex/src/lib.rs b/binex/src/lib.rs index 5acb99a52..a43e14550 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -33,7 +33,7 @@ pub struct ClosedSourceMeta { // decoded MID (as is) pub mid: u32, // decoded MLEN (as is) - pub mlen: u32, + pub mlen: usize, // payload offset in buffer pub offset: usize, // [Provider] of this message. Only this organization may continue the decoding process. diff --git a/binex/src/stream.rs b/binex/src/stream.rs index ba5db854d..150da2e6f 100644 --- a/binex/src/stream.rs +++ b/binex/src/stream.rs @@ -59,7 +59,7 @@ pub struct ClosedSourceElement<'a> { /// in a continuous stream of undisclosed [StreamElement]s. pub total: usize, /// Raw data content that we can encode, decode but not interprate. - raw: &'a [u8], + pub raw: &'a [u8], } impl<'a> ClosedSourceElement<'a> { From 22e1a06bc0ff75e3963cce7061579ada4ce35d3f Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 14:22:50 +0100 Subject: [PATCH 21/28] introduced closed source meta Signed-off-by: Guillaume W. Bres --- binex/src/message/mod.rs | 45 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 2913bdd2c..43331953f 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -84,7 +84,6 @@ impl Message { let mut reversed = false; let mut enhanced_crc = false; let time_res = TimeResolution::QuarterSecond; - let mut closed_provider = Option::::None; // 1. locate SYNC byte if let Some(offset) = Self::locate(Constants::FWDSYNC_BE_STANDARD_CRC, buf) { @@ -174,17 +173,17 @@ impl Message { let fr = EphemerisFrame::decode(big_endian, &buf[ptr..])?; Record::new_ephemeris_frame(fr) }, - id => { + _ => { // verify this is not a closed source message if let Some(provider) = Provider::match_any(mid.into()) { return Err(Error::ClosedSourceMessage(ClosedSourceMeta { - mid: mid.into(), - mlen: mlen as u32, - offset: ptr, + mlen, provider, + offset: ptr, + mid: mid.into(), })); } else { - println!("found unsupported msg id={:?}", id); + // println!("found unsupported msg id={:?}", id); return Err(Error::NonSupportedMesssage(mlen)); } }, @@ -204,18 +203,18 @@ impl Message { // verify let expected = checksum.calc(&buf[sync_off + 1..], mlen + 2); - // if expected != ck { - // Err(Error::BadCRC) - // } else { - Ok(Self { - mid, - record, - reversed, - time_res, - big_endian, - enhanced_crc, - }) - // } + if expected != ck { + Err(Error::CorrupctBadCRC) + } else { + Ok(Self { + mid, + record, + reversed, + time_res, + big_endian, + enhanced_crc, + }) + } } /// [Message] encoding attempt into buffer. @@ -255,11 +254,11 @@ impl Message { let crc_u128 = ck.calc(&buf[1..], mlen + 2); let crc_bytes = crc_u128.to_le_bytes(); - // if ck_len == 1 { - // buf[ptr] = crc_u128 as u8; - // } else { - // buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); - // } + if ck_len == 1 { + buf[ptr] = crc_u128 as u8; + } else { + buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); + } Ok(ptr + ck_len) } From 15918444252240356bbb5a07d7c889a29c59da71 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 14:23:03 +0100 Subject: [PATCH 22/28] working on CRC verification Signed-off-by: Guillaume W. Bres --- binex/src/message/checksum.rs | 77 ++++++++++++++++++++++++----------- binex/tools/crc16.py | 4 +- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/binex/src/message/checksum.rs b/binex/src/message/checksum.rs index 79eabef66..dd3146163 100644 --- a/binex/src/message/checksum.rs +++ b/binex/src/message/checksum.rs @@ -13,30 +13,30 @@ pub enum Checksum { lazy_static! { static ref CRC16_TABLE: [u16; 256] = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, - 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, - 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, - 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, - 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, - 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, - 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, - 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, - 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, - 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, - 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, - 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, - 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, - 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, - 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, - 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, - 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, - 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, - 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, - 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, - 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, - 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, - 0x3EB2, 0x0ED1, 0x1EF0, + 0x0000, 0x8161, 0x83A3, 0x02C2, 0x8627, 0x0746, 0x0584, 0x84E5, 0x8D2F, 0x0C4E, 0x0E8C, + 0x8FED, 0x0B08, 0x8A69, 0x88AB, 0x09CA, 0x9B3F, 0x1A5E, 0x189C, 0x99FD, 0x1D18, 0x9C79, + 0x9EBB, 0x1FDA, 0x1610, 0x9771, 0x95B3, 0x14D2, 0x9037, 0x1156, 0x1394, 0x92F5, 0xB71F, + 0x367E, 0x34BC, 0xB5DD, 0x3138, 0xB059, 0xB29B, 0x33FA, 0x3A30, 0xBB51, 0xB993, 0x38F2, + 0xBC17, 0x3D76, 0x3FB4, 0xBED5, 0x2C20, 0xAD41, 0xAF83, 0x2EE2, 0xAA07, 0x2B66, 0x29A4, + 0xA8C5, 0xA10F, 0x206E, 0x22AC, 0xA3CD, 0x2728, 0xA649, 0xA48B, 0x25EA, 0xEF5F, 0x6E3E, + 0x6CFC, 0xED9D, 0x6978, 0xE819, 0xEADB, 0x6BBA, 0x6270, 0xE311, 0xE1D3, 0x60B2, 0xE457, + 0x6536, 0x67F4, 0xE695, 0x7460, 0xF501, 0xF7C3, 0x76A2, 0xF247, 0x7326, 0x71E4, 0xF085, + 0xF94F, 0x782E, 0x7AEC, 0xFB8D, 0x7F68, 0xFE09, 0xFCCB, 0x7DAA, 0x5840, 0xD921, 0xDBE3, + 0x5A82, 0xDE67, 0x5F06, 0x5DC4, 0xDCA5, 0xD56F, 0x540E, 0x56CC, 0xD7AD, 0x5348, 0xD229, + 0xD0EB, 0x518A, 0xC37F, 0x421E, 0x40DC, 0xC1BD, 0x4558, 0xC439, 0xC6FB, 0x479A, 0x4E50, + 0xCF31, 0xCDF3, 0x4C92, 0xC877, 0x4916, 0x4BD4, 0xCAB5, 0x5FDF, 0xDEBE, 0xDC7C, 0x5D1D, + 0xD9F8, 0x5899, 0x5A5B, 0xDB3A, 0xD2F0, 0x5391, 0x5153, 0xD032, 0x54D7, 0xD5B6, 0xD774, + 0x5615, 0xC4E0, 0x4581, 0x4743, 0xC622, 0x42C7, 0xC3A6, 0xC164, 0x4005, 0x49CF, 0xC8AE, + 0xCA6C, 0x4B0D, 0xCFE8, 0x4E89, 0x4C4B, 0xCD2A, 0xE8C0, 0x69A1, 0x6B63, 0xEA02, 0x6EE7, + 0xEF86, 0xED44, 0x6C25, 0x65EF, 0xE48E, 0xE64C, 0x672D, 0xE3C8, 0x62A9, 0x606B, 0xE10A, + 0x73FF, 0xF29E, 0xF05C, 0x713D, 0xF5D8, 0x74B9, 0x767B, 0xF71A, 0xFED0, 0x7FB1, 0x7D73, + 0xFC12, 0x78F7, 0xF996, 0xFB54, 0x7A35, 0xB080, 0x31E1, 0x3323, 0xB242, 0x36A7, 0xB7C6, + 0xB504, 0x3465, 0x3DAF, 0xBCCE, 0xBE0C, 0x3F6D, 0xBB88, 0x3AE9, 0x382B, 0xB94A, 0x2BBF, + 0xAADE, 0xA81C, 0x297D, 0xAD98, 0x2CF9, 0x2E3B, 0xAF5A, 0xA690, 0x27F1, 0x2533, 0xA452, + 0x20B7, 0xA1D6, 0xA314, 0x2275, 0x079F, 0x86FE, 0x843C, 0x055D, 0x81B8, 0x00D9, 0x021B, + 0x837A, 0x8AB0, 0x0BD1, 0x0913, 0x8872, 0x0C97, 0x8DF6, 0x8F34, 0x0E55, 0x9CA0, 0x1DC1, + 0x1F03, 0x9E62, 0x1A87, 0x9BE6, 0x9924, 0x1845, 0x118F, 0x90EE, 0x922C, 0x134D, 0x97A8, + 0x16C9, 0x140B, 0x956A, ]; static ref CRC32_TABLE: [u32; 256] = [ 0x0000, 0x4C11B7, 0x98236E, 0xD432D9, 0x13046DC, 0x17C576B, 0x1A865B2, 0x1E47405, @@ -154,6 +154,9 @@ impl Checksum { fn xor16_calc(bytes: &[u8]) -> u128 { let mut crc = 0xffff_u16; for byte in bytes.iter() { + // let tmp = (crc >> 8) ^ *byte as u16; + // crc = (crc << 8) ^ CRC16_TABLE[tmp as usize]; + let tmp = (*byte as u16) ^ crc; crc >>= 8; crc ^= CRC16_TABLE[(tmp as usize) % 256]; @@ -186,5 +189,31 @@ mod test { fn test_xor8() { let buf = [0, 1, 2, 3, 4]; assert_eq!(Checksum::XOR8.calc(&buf, 5), 4); + + let buf = [ + 0x00, 0x1f, 0x01, 0x39, 0x87, 0x20, 0x00, 0x00, 0x00, 0x17, 0x42, 0x49, 0x4e, 0x45, + 0x58, 0x20, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x52, 0x65, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x65, 0x64, 0x21, + ]; + assert_eq!(Checksum::XOR8.calc(&buf, buf.len()), 0x84); + } + + #[test] + fn test_xor16() { + // 0xe2, 0x01=MID, 0x81=MLEN, + let buf = [ + 0x01, 0x81, 0x00, 0x01, 0x1d, 0x07, 0xf6, 0x00, 0x03, 0xd8, 0x72, 0x00, 0x03, 0xf4, + 0x80, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xac, + 0xdc, 0x00, 0x00, 0xb8, 0x38, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x20, 0x30, 0xd5, 0x5c, + 0x00, 0xbf, 0xf8, 0x96, 0x4c, 0x65, 0x6e, 0xda, 0x41, 0x3f, 0x6d, 0x97, 0xd5, 0xd0, + 0x00, 0x00, 0x00, 0x40, 0xb4, 0x21, 0xa2, 0x39, 0x40, 0x00, 0x00, 0x32, 0x20, 0x00, + 0x00, 0x43, 0x44, 0xf8, 0x00, 0xb3, 0x18, 0x00, 0x00, 0x42, 0x78, 0x60, 0x00, 0x36, + 0x49, 0xa0, 0x00, 0x37, 0x16, 0x60, 0x00, 0x40, 0x02, 0xa8, 0x2c, 0x0b, 0x2a, 0x18, + 0x0c, 0xc0, 0x08, 0x23, 0xb8, 0x97, 0xbd, 0xf9, 0x99, 0x3f, 0xee, 0x23, 0x55, 0xce, + 0x2e, 0x11, 0x70, 0xb1, 0x31, 0xa4, 0x00, 0xad, 0xac, 0x00, 0x00, 0x41, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x04, + ]; + + assert_eq!(Checksum::XOR16.calc(&buf, buf.len()), 0x7d49); } } diff --git a/binex/tools/crc16.py b/binex/tools/crc16.py index 43fa011f5..cd48e3e3e 100755 --- a/binex/tools/crc16.py +++ b/binex/tools/crc16.py @@ -2,7 +2,7 @@ def generate_crc16_look_up_table(): with open("output.txt", "w") as fd: - polynomial = 0x1021 + polynomial = 0x8161 for i in range(256): crc = i << 8 for _ in range(8): @@ -13,7 +13,7 @@ def generate_crc16_look_up_table(): crc &= 0xffff fd.write("0x{:04X}, ".format(crc)) - if (i+1) % 4 == 0 : + if (i+1) % 8 == 0 : fd.write("\n") if __name__ == "__main__": From eafc347c990f54b57e8613f1631ca27137f7866a Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 14:28:42 +0100 Subject: [PATCH 23/28] working on CRC verification Signed-off-by: Guillaume W. Bres --- binex/src/message/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 43331953f..dc02f40d3 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -684,7 +684,7 @@ mod test { let mut encoded = [0; 256]; msg.encode(&mut encoded).unwrap(); - assert_eq!(encoded[17], 0); + assert_eq!(encoded[17], 3); // parse back let parsed = Message::decode(&encoded).unwrap(); From fbf9d3218cbb509ee6be28ba5a75b36f2c926984 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 14:48:03 +0100 Subject: [PATCH 24/28] closed source meta Signed-off-by: Guillaume W. Bres --- binex/README.md | 83 +++++++++++++++++++++++++++++++++++++++++++-- binex/src/lib.rs | 2 +- binex/src/stream.rs | 47 +++++++++++++------------ 3 files changed, 108 insertions(+), 24 deletions(-) diff --git a/binex/README.md b/binex/README.md index 4b492dc12..9ec1274f7 100644 --- a/binex/README.md +++ b/binex/README.md @@ -22,13 +22,92 @@ You have two scenarios to approach a BINEX stream: and can represent a stream of continuous of either [Message]s (open source) or undisclosed elements. (private prototypes) -* use Message::decode to work on your own buffer directly. +* or use Message::decode to work on your own buffer directly. ## Message Decoding -Use the BINEX `Decoder` to decode messages from a `Readable` interface: +Use the BINEX `Decoder` to decode a `Readable` interface streaming +BINEX messages. [Decoder] exposes open source [Message] that +it fully interprated: ```rust +use std::fs::File; +use binex::prelude::{OpenSourceDecoder, Error}; + +let fd = File::open("../test_resources/BIN/mfle20190130.bnx") + .unwrap(); + +let mut decoder = OpenSourceDecoder::new(fd); + +loop { + match decoder.next() { + Some(Ok(msg)) => { + // process decoded [Message] + }, + Some(Err(e)) => { + // it is possible that some frames may not + // be supported yet. + // Any I/O error should not happen. + }, + None => { + // end of stream + break; + }, + } +} +``` + +The [OpenSourceDecoder] will solely decode documented +open source [Message]s that the stream contains. +But BINEX is flexiblea and allows the description of closed +source messages, not realesed to the general public yet. + +You can use the [Decoder] object that will decode +both disclosed [Message] and undisclosed frames in form of +[ClosedSourceElement]s of the data stream. It is then up to you +to complete the stream interpretation: + +```rust +use std::fs::File; +use binex::prelude::{Decoder, Provider, StreamElement, Error}; + +let fd = File::open("../test_resources/BIN/mfle20190130.bnx") + .unwrap(); + +let mut decoder = Decoder::new(fd); + +loop { + match decoder.next() { + Some(Ok(StreamElement::OpenSource(msg))) => { + // fully interprated element + }, + Some(Ok(StreamElement::ClosedSource(element))) => { + // verify this is your organization + if element.provider == Provider::JPL { + // MID and MLEN should follow the BINEX specs + // and are provided as decoded + let mid = element.mid; + let mlen = element.mlen; + // now proceed to custom interpratetion + element.interprate(|data| { + match mid { + // process custom mid and custom data + // using undisclosed method + } + }); + } + }, + Some(Err(e)) => { + // it is possible that some frames may not + // be supported yet. + // Any I/O error should not happen. + }, + None => { + // end of stream + break; + }, + } +} ``` ## Message forging diff --git a/binex/src/lib.rs b/binex/src/lib.rs index a43e14550..2f2b107b1 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -19,7 +19,7 @@ pub mod prelude { MonumentGeoMetadata, MonumentGeoRecord, Record, SBASEphemeris, TimeResolution, }, stream::{ClosedSourceElement, Provider, StreamElement}, - Error, + ClosedSourceMeta, Error, }; // re-export pub use hifitime::Epoch; diff --git a/binex/src/stream.rs b/binex/src/stream.rs index 150da2e6f..56b076053 100644 --- a/binex/src/stream.rs +++ b/binex/src/stream.rs @@ -49,24 +49,22 @@ impl Provider { /// Closed source frame that we can encode but not interprate. /// This particular [StreamElement] can be either a part of a continuous serie or self sustainable. pub struct ClosedSourceElement<'a> { + /// Message ID as decoded + pub mid: u32, /// Provider of this frame. /// Only this organization may have capabilities to interprate this frame. pub provider: Provider, - /// Size of this element. Use this to determine the packet index - /// in a continuous stream of undisclosed [StreamElement]s. + /// Total size of this [StreamElement] as it may be part of a continuous + /// serie of [StreamElement]s. pub size: usize, - /// Total size of this undisclosed message. Use this to determine the packet index - /// in a continuous stream of undisclosed [StreamElement]s. - pub total: usize, + /// Total message size (not the size of this element) + pub mlen: usize, /// Raw data content that we can encode, decode but not interprate. pub raw: &'a [u8], } impl<'a> ClosedSourceElement<'a> { /// Interprate this [ClosedSourceElement] using custom undisclosed method. - /// ``` - /// - /// ``` pub fn interprate(&self, f: &dyn Fn(&[u8])) { f(&self.raw[..self.size]) } @@ -104,12 +102,19 @@ impl<'a> StreamElement<'a> { /// - provider: specific [Provider] /// - raw: content we can encode, decode but not interprate /// - size: size of this [StreamElement] - /// - total: total size of the [StreamElement] serie - pub fn new_prototype(provider: Provider, raw: &'a [u8], size: usize, total: usize) -> Self { + /// - mlen: total message lenth + pub fn new_prototype( + provider: Provider, + mid: u32, + raw: &'a [u8], + size: usize, + mlen: usize, + ) -> Self { Self::ClosedSource(ClosedSourceElement { raw, - total, + mlen, size, + mid, provider, }) } @@ -120,8 +125,8 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn jpl_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { - Self::new_prototype(Provider::JPL, raw, size, total) + pub fn jpl_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { + Self::new_prototype(Provider::JPL, mid, raw, size, total) } /// Add one closed source [StreamElement]s provided by desired [Provider::JPL]. @@ -130,8 +135,8 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn igs_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { - Self::new_prototype(Provider::IGS, raw, size, total) + pub fn igs_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { + Self::new_prototype(Provider::IGS, mid, raw, size, total) } /// Add one closed source [StreamElement]s provided by desired [Provider::ColoradoUnivBoulder]. @@ -140,8 +145,8 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn cuboulder_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { - Self::new_prototype(Provider::ColoradoUnivBoulder, raw, size, total) + pub fn cuboulder_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { + Self::new_prototype(Provider::ColoradoUnivBoulder, mid, raw, size, total) } /// Add one closed source [StreamElement]s provided by desired [Provider::NRCan]. @@ -150,8 +155,8 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn nrcan_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { - Self::new_prototype(Provider::NRCan, raw, size, total) + pub fn nrcan_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { + Self::new_prototype(Provider::NRCan, mid, raw, size, total) } /// Add one closed source [StreamElement]s provided by desired [Provider::UCAR]. @@ -160,7 +165,7 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn ucar_prototype(raw: &'a [u8], size: usize, total: usize) -> Self { - Self::new_prototype(Provider::UCAR, raw, size, total) + pub fn ucar_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { + Self::new_prototype(Provider::UCAR, mid, raw, size, total) } } From 7fd2c8bff382a1c5bd2413e7ade4c4deacc698c3 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 14:55:40 +0100 Subject: [PATCH 25/28] docs Signed-off-by: Guillaume W. Bres --- binex/README.md | 14 +++++++------- binex/src/decoder.rs | 3 +-- binex/src/lib.rs | 2 +- binex/src/message/record/monument/mod.rs | 4 ++-- binex/src/stream.rs | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/binex/README.md b/binex/README.md index 9ec1274f7..ff0b1bcd7 100644 --- a/binex/README.md +++ b/binex/README.md @@ -1,9 +1,7 @@ # BINEX [![Rust](https://github.com/georust/rinex/actions/workflows/rust.yml/badge.svg)](https://github.com/georust/rinex/actions/workflows/rust.yml) -[![Rust](https://github.com/georust/rinex/actions/workflows/daily.yml/badge.svg)](https://github.com/georust/rinex/actions/workflows/daily.yml) -[![crates.io](https://img.shields.io/crates/v/binex.svg)](https://crates.io/crates/binex) -[![crates.io](https://docs.rs/binex/badge.svg)](https://docs.rs/binex/badge.svg) +[![Rust](https://github.com/georust/rinex/actions/workflows/daily.yml/badge.svg)](https://github.com/georust/rinex/actions/workflows/daily.yml) [![crates.io](https://img.shields.io/crates/v/binex.svg)](https://crates.io/crates/binex) [![crates.io](https://docs.rs/binex/badge.svg)](https://docs.rs/binex/badge.svg) BINEX is a simple library to decode and encode BINEX messages. BINEX stands for BINary EXchange and is the "real time" stream oriented @@ -19,15 +17,16 @@ convertion from RINEX back and forth. You have two scenarios to approach a BINEX stream: * use our Decoder object, which works on I/O interface directly -and can represent a stream of continuous of either [Message]s (open source) +and can represent a stream of continuous of either Messages (open source) or undisclosed elements. (private prototypes) * or use Message::decode to work on your own buffer directly. -## Message Decoding +Message Decoding +================ Use the BINEX `Decoder` to decode a `Readable` interface streaming -BINEX messages. [Decoder] exposes open source [Message] that +BINEX messages. Decoder exposes open source Message that it fully interprated: ```rust @@ -110,7 +109,8 @@ loop { } ``` -## Message forging +Message Forging +=============== The BINEX library allows easy message forging. Each message can be easily encoded and then streamed into a `Writable` interface: diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index 5e73f1ac4..45a5acb6f 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -8,7 +8,7 @@ use flate2::read::GzDecoder; use crate::prelude::{ClosedSourceElement, Error, Message, StreamElement}; -/// Abstraction for Plain or Compressed [R] +/// Abstraction for Plain or Compressed [Read]able I/O enum Reader { Plain(R), #[cfg(feature = "flate2")] @@ -70,7 +70,6 @@ impl<'a, R: Read> Decoder<'a, R> { /// let mut fd = File::open("../test_resources/BIN/mfle20190130.bnx") /// .unwrap(); /// - /// // Two generics: with M the internal buffer depth /// let mut decoder = Decoder::new(fd); /// /// // Consume data stream diff --git a/binex/src/lib.rs b/binex/src/lib.rs index 2f2b107b1..73b15b123 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -67,7 +67,7 @@ pub enum Error { CorrupctBadCRC, /// Incomplete message: need more data to complete IncompleteMessage(usize), - /// Library limitation: not all open source [Message]s supported yet + /// Library limitation: not all open source Messages supported yet NonSupportedMesssage(usize), /// Library limtation: should never happen, because this library /// will be designed to parse all open source [Message]s. diff --git a/binex/src/message/record/monument/mod.rs b/binex/src/message/record/monument/mod.rs index a6834dcbf..09550e1f0 100644 --- a/binex/src/message/record/monument/mod.rs +++ b/binex/src/message/record/monument/mod.rs @@ -83,8 +83,8 @@ impl MonumentGeoRecord { /// ); /// /// // customize as you need - /// let record = record. - /// with_comment("you can add") + /// let record = record + /// .with_comment("you can add") /// .with_comment("as many as you need") /// .with_extra_info("Experiment or setup context") /// .with_geophysical_info("Eurasian plate") diff --git a/binex/src/stream.rs b/binex/src/stream.rs index 56b076053..fb4afb1de 100644 --- a/binex/src/stream.rs +++ b/binex/src/stream.rs @@ -12,7 +12,7 @@ pub enum Provider { ColoradoUnivBoulder, /// NRCan for internal needs or prototyping. NRCan, - /// UCAR COSMIC [https://www.cosmic.ucar.edu] + /// UCAR COSMIC UCAR, /// GPS Solutions Inc. GPSSolutions, From adb2d1bca707d7b60b25112474aea117f2668608 Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 21:24:56 +0100 Subject: [PATCH 26/28] Fix CRC16 calc Signed-off-by: Guillaume W. Bres --- binex/src/decoder.rs | 3 +- binex/src/message/checksum.rs | 159 ++++++++++++++------- binex/src/message/mod.rs | 54 +++++-- binex/src/message/record/ephemeris/mod.rs | 24 ++-- binex/src/message/record/monument/frame.rs | 51 +++++++ binex/tests/decoder.rs | 4 +- binex/tests/message.rs | 20 ++- binex/tools/crc16.py | 2 +- binex/tools/crc32.py | 2 +- 9 files changed, 230 insertions(+), 89 deletions(-) create mode 100644 binex/src/message/record/monument/frame.rs diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index 45a5acb6f..5e73f1ac4 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -8,7 +8,7 @@ use flate2::read::GzDecoder; use crate::prelude::{ClosedSourceElement, Error, Message, StreamElement}; -/// Abstraction for Plain or Compressed [Read]able I/O +/// Abstraction for Plain or Compressed [R] enum Reader { Plain(R), #[cfg(feature = "flate2")] @@ -70,6 +70,7 @@ impl<'a, R: Read> Decoder<'a, R> { /// let mut fd = File::open("../test_resources/BIN/mfle20190130.bnx") /// .unwrap(); /// + /// // Two generics: with M the internal buffer depth /// let mut decoder = Decoder::new(fd); /// /// // Consume data stream diff --git a/binex/src/message/checksum.rs b/binex/src/message/checksum.rs index dd3146163..c5906f74e 100644 --- a/binex/src/message/checksum.rs +++ b/binex/src/message/checksum.rs @@ -13,30 +13,30 @@ pub enum Checksum { lazy_static! { static ref CRC16_TABLE: [u16; 256] = [ - 0x0000, 0x8161, 0x83A3, 0x02C2, 0x8627, 0x0746, 0x0584, 0x84E5, 0x8D2F, 0x0C4E, 0x0E8C, - 0x8FED, 0x0B08, 0x8A69, 0x88AB, 0x09CA, 0x9B3F, 0x1A5E, 0x189C, 0x99FD, 0x1D18, 0x9C79, - 0x9EBB, 0x1FDA, 0x1610, 0x9771, 0x95B3, 0x14D2, 0x9037, 0x1156, 0x1394, 0x92F5, 0xB71F, - 0x367E, 0x34BC, 0xB5DD, 0x3138, 0xB059, 0xB29B, 0x33FA, 0x3A30, 0xBB51, 0xB993, 0x38F2, - 0xBC17, 0x3D76, 0x3FB4, 0xBED5, 0x2C20, 0xAD41, 0xAF83, 0x2EE2, 0xAA07, 0x2B66, 0x29A4, - 0xA8C5, 0xA10F, 0x206E, 0x22AC, 0xA3CD, 0x2728, 0xA649, 0xA48B, 0x25EA, 0xEF5F, 0x6E3E, - 0x6CFC, 0xED9D, 0x6978, 0xE819, 0xEADB, 0x6BBA, 0x6270, 0xE311, 0xE1D3, 0x60B2, 0xE457, - 0x6536, 0x67F4, 0xE695, 0x7460, 0xF501, 0xF7C3, 0x76A2, 0xF247, 0x7326, 0x71E4, 0xF085, - 0xF94F, 0x782E, 0x7AEC, 0xFB8D, 0x7F68, 0xFE09, 0xFCCB, 0x7DAA, 0x5840, 0xD921, 0xDBE3, - 0x5A82, 0xDE67, 0x5F06, 0x5DC4, 0xDCA5, 0xD56F, 0x540E, 0x56CC, 0xD7AD, 0x5348, 0xD229, - 0xD0EB, 0x518A, 0xC37F, 0x421E, 0x40DC, 0xC1BD, 0x4558, 0xC439, 0xC6FB, 0x479A, 0x4E50, - 0xCF31, 0xCDF3, 0x4C92, 0xC877, 0x4916, 0x4BD4, 0xCAB5, 0x5FDF, 0xDEBE, 0xDC7C, 0x5D1D, - 0xD9F8, 0x5899, 0x5A5B, 0xDB3A, 0xD2F0, 0x5391, 0x5153, 0xD032, 0x54D7, 0xD5B6, 0xD774, - 0x5615, 0xC4E0, 0x4581, 0x4743, 0xC622, 0x42C7, 0xC3A6, 0xC164, 0x4005, 0x49CF, 0xC8AE, - 0xCA6C, 0x4B0D, 0xCFE8, 0x4E89, 0x4C4B, 0xCD2A, 0xE8C0, 0x69A1, 0x6B63, 0xEA02, 0x6EE7, - 0xEF86, 0xED44, 0x6C25, 0x65EF, 0xE48E, 0xE64C, 0x672D, 0xE3C8, 0x62A9, 0x606B, 0xE10A, - 0x73FF, 0xF29E, 0xF05C, 0x713D, 0xF5D8, 0x74B9, 0x767B, 0xF71A, 0xFED0, 0x7FB1, 0x7D73, - 0xFC12, 0x78F7, 0xF996, 0xFB54, 0x7A35, 0xB080, 0x31E1, 0x3323, 0xB242, 0x36A7, 0xB7C6, - 0xB504, 0x3465, 0x3DAF, 0xBCCE, 0xBE0C, 0x3F6D, 0xBB88, 0x3AE9, 0x382B, 0xB94A, 0x2BBF, - 0xAADE, 0xA81C, 0x297D, 0xAD98, 0x2CF9, 0x2E3B, 0xAF5A, 0xA690, 0x27F1, 0x2533, 0xA452, - 0x20B7, 0xA1D6, 0xA314, 0x2275, 0x079F, 0x86FE, 0x843C, 0x055D, 0x81B8, 0x00D9, 0x021B, - 0x837A, 0x8AB0, 0x0BD1, 0x0913, 0x8872, 0x0C97, 0x8DF6, 0x8F34, 0x0E55, 0x9CA0, 0x1DC1, - 0x1F03, 0x9E62, 0x1A87, 0x9BE6, 0x9924, 0x1845, 0x118F, 0x90EE, 0x922C, 0x134D, 0x97A8, - 0x16C9, 0x140B, 0x956A, + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, + 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, + 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, + 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, + 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, + 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, + 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, + 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, + 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, + 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, + 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, + 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, + 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, + 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, + 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, + 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, + 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, + 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, + 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, + 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, + 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, + 0x3EB2, 0x0ED1, 0x1EF0, ]; static ref CRC32_TABLE: [u32; 256] = [ 0x0000, 0x4C11B7, 0x98236E, 0xD432D9, 0x13046DC, 0x17C576B, 0x1A865B2, 0x1E47405, @@ -112,17 +112,17 @@ impl Checksum { } /// Helper to decode checksum value as unsigned 128, /// which covers all scenarios - pub fn decode(&self, slice: &[u8], len: usize, big_endian: bool) -> u128 { - if len == 1 { + pub fn decode(&self, slice: &[u8], ck_len: usize, big_endian: bool) -> u128 { + if ck_len == 1 { slice[0] as u128 - } else if len == 2 { + } else if ck_len == 2 { let val_u16 = if big_endian { u16::from_be_bytes([slice[0], slice[1]]) } else { u16::from_le_bytes([slice[0], slice[1]]) }; val_u16 as u128 - } else if len == 4 { + } else if ck_len == 4 { let val_u32 = if big_endian { u32::from_be_bytes([slice[0], slice[1], slice[2], slice[3]]) } else { @@ -134,12 +134,12 @@ impl Checksum { } } /// Calculates expected Checksum for this msg - pub fn calc(&self, bytes: &[u8], mlen: usize) -> u128 { + pub fn calc(&self, bytes: &[u8], size: usize) -> u128 { match self { - Self::XOR8 => Self::xor8_calc(bytes, mlen), - Self::XOR16 => Self::xor16_calc(bytes), - Self::XOR32 => Self::xor32_calc(bytes), - Self::MD5 => Self::md5_calc(bytes), + Self::XOR8 => Self::xor8_calc(bytes, size), + Self::XOR16 => Self::xor16_calc(bytes, size), + Self::XOR32 => Self::xor32_calc(bytes, size), + Self::MD5 => Self::md5_calc(bytes, size), } } /// Calculates expected Checksum using XOR8 algorithm @@ -151,32 +151,29 @@ impl Checksum { xor as u128 } /// Calculates expected Checksum using XOR16 algorithm - fn xor16_calc(bytes: &[u8]) -> u128 { - let mut crc = 0xffff_u16; - for byte in bytes.iter() { - // let tmp = (crc >> 8) ^ *byte as u16; - // crc = (crc << 8) ^ CRC16_TABLE[tmp as usize]; - - let tmp = (*byte as u16) ^ crc; - crc >>= 8; - crc ^= CRC16_TABLE[(tmp as usize) % 256]; + fn xor16_calc(bytes: &[u8], size: usize) -> u128 { + let mut crc = 0_u16; + for i in 0..size { + let index = (((crc >> 8) ^ bytes[i] as u16) & 0xff) as usize; + crc = (crc << 8) ^ CRC16_TABLE[index]; + crc &= 0xffff; } crc as u128 } /// Calculates expected Checksum using XO32 algorithm - fn xor32_calc(bytes: &[u8]) -> u128 { - let mut crc = 0xffffffff_u32; - for byte in bytes.iter() { - let tmp = (*byte as u32) ^ crc; - crc >>= 8; - crc ^= CRC32_TABLE[(tmp as usize) % 256]; + fn xor32_calc(bytes: &[u8], size: usize) -> u128 { + let mut crc = 0_u32; + for i in 0..size { + let index = (((crc >> 24) ^ bytes[i] as u32) & 0xff) as usize; + crc = (crc << 8) ^ CRC32_TABLE[index]; + crc &= 0xffffffff; } crc as u128 } /// Calculates expected Checksum using MD5 algorithm - fn md5_calc(bytes: &[u8]) -> u128 { + fn md5_calc(bytes: &[u8], size: usize) -> u128 { let mut hasher = Md5::new(); - hasher.update(bytes); + hasher.update(&bytes[..size]); let md5 = hasher.finalize(); u128::from_le_bytes(md5.into()) } @@ -200,7 +197,7 @@ mod test { #[test] fn test_xor16() { - // 0xe2, 0x01=MID, 0x81=MLEN, + // e2, 01 81 let buf = [ 0x01, 0x81, 0x00, 0x01, 0x1d, 0x07, 0xf6, 0x00, 0x03, 0xd8, 0x72, 0x00, 0x03, 0xf4, 0x80, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xac, @@ -215,5 +212,65 @@ mod test { ]; assert_eq!(Checksum::XOR16.calc(&buf, buf.len()), 0x7d49); + + let buf = [ + 0x01, 0x81, 0x00, 0x01, 0x07, 0x07, 0xf6, 0x00, 0x03, 0xd8, 0x72, 0x00, 0x03, 0xf4, + 0x80, 0x31, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0xab, + 0xc0, 0x00, 0x00, 0xb9, 0x09, 0x3b, 0x60, 0x00, 0x00, 0x00, 0x1d, 0x30, 0xc3, 0x30, + 0x00, 0x3f, 0xf9, 0xa2, 0xc9, 0x26, 0x53, 0xc2, 0x7b, 0x3f, 0x71, 0x2c, 0xe0, 0xd8, + 0x00, 0x00, 0x00, 0x40, 0xb4, 0x21, 0xb0, 0xf1, 0x60, 0x00, 0x00, 0x33, 0xa0, 0x00, + 0x00, 0x43, 0x98, 0x64, 0x00, 0xb2, 0x60, 0x00, 0x00, 0xc2, 0x2f, 0xa0, 0x00, 0xb6, + 0x0c, 0xe0, 0x00, 0x36, 0x83, 0xc0, 0x00, 0xbf, 0xfe, 0xa8, 0x9a, 0xfb, 0x49, 0x69, + 0xb2, 0xbf, 0xd7, 0x73, 0x3f, 0x12, 0x4a, 0xa8, 0x69, 0x3f, 0xef, 0x09, 0xab, 0xae, + 0x21, 0x65, 0xd4, 0xb1, 0x33, 0xe8, 0x00, 0xae, 0xa1, 0xc0, 0x00, 0x41, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x04, + ]; + + assert_eq!(Checksum::XOR16.calc(&buf, buf.len()), 0x6c23); + + let buf = [ + 0x01, 0x81, 0x00, 0x01, 0x06, 0x07, 0xf6, 0x00, 0x03, 0xd8, 0x72, 0x00, 0x03, 0xf4, + 0x80, 0xb2, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0xac, + 0xfc, 0x00, 0x00, 0x38, 0x31, 0xab, 0x80, 0x00, 0x00, 0x00, 0x53, 0x30, 0xc5, 0x24, + 0x00, 0xbf, 0xf8, 0xbb, 0x57, 0x3d, 0x09, 0x5f, 0x9d, 0x3f, 0x88, 0xee, 0x38, 0x68, + 0x00, 0x00, 0x00, 0x40, 0xb4, 0x21, 0x9d, 0xac, 0xc0, 0x00, 0x00, 0x34, 0x5a, 0x00, + 0x00, 0x43, 0x48, 0x50, 0x00, 0xb3, 0xe4, 0x00, 0x00, 0x42, 0x67, 0x40, 0x00, 0x36, + 0x42, 0x80, 0x00, 0x37, 0x16, 0xe8, 0x00, 0x40, 0x02, 0x6a, 0xdc, 0xf8, 0x6b, 0x10, + 0x56, 0xc0, 0x03, 0xd3, 0x59, 0xfa, 0x22, 0x6d, 0x54, 0x3f, 0xee, 0x99, 0xf5, 0x34, + 0x32, 0xf3, 0xdd, 0xb1, 0x2b, 0xf6, 0x00, 0xac, 0xe8, 0x00, 0x00, 0x41, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x04, + ]; + + assert_eq!(Checksum::XOR16.calc(&buf, buf.len()), 0x1919); + + let buf = [ + 0x01, 0x81, 0x00, 0x01, 0x11, 0x07, 0xf6, 0x00, 0x03, 0xe5, 0x74, 0x00, 0x03, 0xf4, + 0x80, 0xb1, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x2c, + 0x84, 0x00, 0x00, 0x37, 0xae, 0x23, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0xc9, 0xa4, + 0x00, 0xbf, 0xf2, 0x1b, 0xb8, 0xd2, 0xf3, 0x07, 0xe2, 0x3f, 0x8e, 0xbe, 0xca, 0x08, + 0x00, 0x00, 0x00, 0x40, 0xb4, 0x21, 0xbe, 0x8a, 0x80, 0x00, 0x00, 0xb3, 0xc0, 0x00, + 0x00, 0x43, 0x53, 0x30, 0x00, 0x34, 0xb4, 0x00, 0x00, 0xc2, 0x64, 0xa0, 0x00, 0xb6, + 0x3f, 0xa0, 0x00, 0x37, 0x0e, 0xf8, 0x00, 0xbf, 0xec, 0xde, 0x8f, 0x8b, 0x25, 0x86, + 0x86, 0x3f, 0xf5, 0xf8, 0xf4, 0xf0, 0x6d, 0x38, 0x8e, 0x3f, 0xee, 0x7c, 0xb1, 0xdf, + 0xad, 0x10, 0xdf, 0xb1, 0x34, 0x46, 0x00, 0xaa, 0x80, 0x00, 0x00, 0x41, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x04, + ]; + + assert_eq!(Checksum::XOR16.calc(&buf, buf.len()), 0x72d8); + + let buf = [ + 0x01, 0x81, 0x00, 0x01, 0x00, 0x07, 0xf6, 0x00, 0x03, 0xf2, 0x6a, 0x00, 0x03, 0xf4, + 0x80, 0x31, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0xac, + 0xec, 0x00, 0x00, 0xb9, 0x21, 0x70, 0x60, 0x00, 0x00, 0x00, 0x5b, 0x30, 0xb1, 0xd4, + 0x00, 0xbf, 0xed, 0x6f, 0x04, 0x75, 0x35, 0xbf, 0x7c, 0x3f, 0x81, 0x09, 0xb5, 0x7c, + 0x00, 0x00, 0x00, 0x40, 0xb4, 0x21, 0xa9, 0xec, 0xa0, 0x00, 0x00, 0x34, 0x3c, 0x00, + 0x00, 0x43, 0x55, 0xd8, 0x00, 0x33, 0xec, 0x00, 0x00, 0xc2, 0x6f, 0xc0, 0x00, 0xb6, + 0x55, 0x80, 0x00, 0x37, 0x16, 0x80, 0x00, 0xbf, 0xeb, 0x2f, 0xea, 0x1a, 0x2d, 0x94, + 0x4f, 0x3f, 0xe5, 0xf2, 0x7f, 0x44, 0xff, 0xc4, 0xc1, 0x3f, 0xef, 0x2d, 0x2d, 0x85, + 0x03, 0xc4, 0xfb, 0xb1, 0x2c, 0x40, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x41, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x04, + ]; + + assert_eq!(Checksum::XOR16.calc(&buf, buf.len()), 0x5376); } } diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index dc02f40d3..9aad2416d 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -142,9 +142,9 @@ impl Message { let mut ptr = sync_off + 1; // 2. parse MID - let (bnxi, size) = Self::decode_bnxi(&buf[ptr..], big_endian); + let (bnxi, mid_1_4) = Self::decode_bnxi(&buf[ptr..], big_endian); let mid = MessageID::from(bnxi); - ptr += size; + ptr += mid_1_4; // make sure we can parse up to 4 byte MLEN if buf_len - ptr < 4 { @@ -152,7 +152,7 @@ impl Message { } // 3. parse MLEN - let (mlen, size) = Self::decode_bnxi(&buf[ptr..], big_endian); + let (mlen, mlen_1_4) = Self::decode_bnxi(&buf[ptr..], big_endian); let mlen = mlen as usize; // println!("mlen={}", mlen); @@ -160,7 +160,8 @@ impl Message { // buffer does not contain complete message! return Err(Error::IncompleteMessage(mlen)); } - ptr += size; + + ptr += mlen_1_4; // 4. parse RECORD let record = match mid { @@ -174,7 +175,7 @@ impl Message { Record::new_ephemeris_frame(fr) }, _ => { - // verify this is not a closed source message + // check whether this message is undisclosed or not if let Some(provider) = Provider::match_any(mid.into()) { return Err(Error::ClosedSourceMessage(ClosedSourceMeta { mlen, @@ -201,7 +202,7 @@ impl Message { let ck = checksum.decode(&buf[ptr + mlen..], ck_len, big_endian); // verify - let expected = checksum.calc(&buf[sync_off + 1..], mlen + 2); + let expected = checksum.calc(&buf[sync_off + 1..], mlen + mid_1_4 + mlen_1_4); if expected != ck { Err(Error::CorrupctBadCRC) @@ -232,11 +233,13 @@ impl Message { // Encode MID let mid = self.record.to_message_id() as u32; - ptr += Self::encode_bnxi(mid, self.big_endian, &mut buf[ptr..])?; + let mid_1_4 = Self::encode_bnxi(mid, self.big_endian, &mut buf[ptr..])?; + ptr += mid_1_4; // Encode MLEN let mlen = self.record.encoding_size(); - ptr += Self::encode_bnxi(mlen as u32, self.big_endian, &mut buf[ptr..])?; + let mlen_1_4 = Self::encode_bnxi(mlen as u32, self.big_endian, &mut buf[ptr..])?; + ptr += mlen_1_4; // Encode message match &self.record { @@ -251,13 +254,38 @@ impl Message { // encode CRC let ck = Checksum::from_len(mlen, self.enhanced_crc); let ck_len = ck.len(); - let crc_u128 = ck.calc(&buf[1..], mlen + 2); - let crc_bytes = crc_u128.to_le_bytes(); + let crc_u128 = ck.calc(&buf[1..], mlen + mid_1_4 + mlen_1_4); if ck_len == 1 { buf[ptr] = crc_u128 as u8; + } else if ck_len == 2 { + let crc_bytes = if self.big_endian { + (crc_u128 as u16).to_be_bytes() + } else { + (crc_u128 as u16).to_le_bytes() + }; + + for i in 0..ck_len { + buf[ptr + i] = crc_bytes[i]; + } + } else if ck_len == 4 { + let crc_bytes = if self.big_endian { + (crc_u128 as u32).to_be_bytes() + } else { + (crc_u128 as u32).to_le_bytes() + }; + for i in 0..ck_len { + buf[ptr + i] = crc_bytes[i]; + } } else { - buf[ptr..ptr + ck_len].copy_from_slice(&crc_bytes[..ck_len]); + let crc_bytes = if self.big_endian { + crc_u128.to_be_bytes() + } else { + crc_u128.to_le_bytes() + }; + for i in 0..ck_len { + buf[ptr + i] = crc_bytes[i]; + } } Ok(ptr + ck_len) @@ -732,7 +760,7 @@ mod test { let gps_eph_len = gps_eph.encoding_size(); let record = Record::new_ephemeris_frame(gps_eph); - assert_eq!(gps_eph_len, 128); + assert_eq!(gps_eph_len, 129); let msg = Message::new( big_endian, @@ -763,7 +791,7 @@ mod test { let eph_len = eph.encoding_size(); let record = Record::new_ephemeris_frame(eph); - assert_eq!(eph_len, 128); + assert_eq!(eph_len, 129); let msg = Message::new( big_endian, diff --git a/binex/src/message/record/ephemeris/mod.rs b/binex/src/message/record/ephemeris/mod.rs index 5c7bc34d3..8ce8d94f2 100644 --- a/binex/src/message/record/ephemeris/mod.rs +++ b/binex/src/message/record/ephemeris/mod.rs @@ -37,13 +37,17 @@ impl EphemerisFrame { /// Returns total length (bytewise) required to fully encode [Self]. /// Use this to fulfill [Self::encode] requirements. pub fn encoding_size(&self) -> usize { - match self { + let fid_1_4 = Message::bnxi_encoding_size(self.to_field_id() as u32); + + let size = match self { Self::GPSRaw(_) => GPSRaw::encoding_size(), Self::GPS(_) => GPSEphemeris::encoding_size(), Self::GLO(_) => GLOEphemeris::encoding_size(), Self::SBAS(_) => SBASEphemeris::encoding_size(), Self::GAL(_) => GALEphemeris::encoding_size(), - } + }; + + size + fid_1_4 } /// Returns expected [FieldID] for [Self] @@ -104,13 +108,15 @@ impl EphemerisFrame { let fid = self.to_field_id() as u32; let offset = Message::encode_bnxi(fid, big_endian, buf)?; - match self { - Self::GPSRaw(r) => r.encode(big_endian, &mut buf[offset..]), - Self::GPS(r) => r.encode(big_endian, &mut buf[offset..]), - Self::GLO(r) => r.encode(big_endian, &mut buf[offset..]), - Self::GAL(r) => r.encode(big_endian, &mut buf[offset..]), - Self::SBAS(r) => r.encode(big_endian, &mut buf[offset..]), - } + let size = match self { + Self::GPSRaw(r) => r.encode(big_endian, &mut buf[offset..])?, + Self::GPS(r) => r.encode(big_endian, &mut buf[offset..])?, + Self::GLO(r) => r.encode(big_endian, &mut buf[offset..])?, + Self::GAL(r) => r.encode(big_endian, &mut buf[offset..])?, + Self::SBAS(r) => r.encode(big_endian, &mut buf[offset..])?, + }; + + Ok(size + offset) } /// Creates new [GPSRaw] frame diff --git a/binex/src/message/record/monument/frame.rs b/binex/src/message/record/monument/frame.rs new file mode 100644 index 000000000..a2171a872 --- /dev/null +++ b/binex/src/message/record/monument/frame.rs @@ -0,0 +1,51 @@ +//! Monument Geodetic marker specific frames + +use crate::{ + message::{record::monument::FieldID, Message}, + Error, +}; + +// use log::error; + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn geo_comments() { + let frame = MonumentGeoFrame::Comment("Hello".to_string()); + assert_eq!(frame.encoding_size(), 5 + 2); + + let big_endian = true; + let mut buf = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let size = frame.encode(big_endian, &mut buf).unwrap(); + + assert_eq!(size, frame.encoding_size()); + assert_eq!( + buf, + [0, 5, 'H' as u8, 'e' as u8, 'l' as u8, 'l' as u8, 'o' as u8, 0, 0, 0, 0, 0, 0] + ); + + let decoded = MonumentGeoFrame::decode(big_endian, &buf).unwrap(); + + assert_eq!(decoded, frame); + } + #[test] + fn geo_climatic() { + let frame = MonumentGeoFrame::Climatic("ABC".to_string()); + assert_eq!(frame.encoding_size(), 3 + 2); + + let big_endian = true; + let mut buf = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let size = frame.encode(big_endian, &mut buf).unwrap(); + + assert_eq!(size, frame.encoding_size()); + assert_eq!( + buf, + [14, 3, 'A' as u8, 'B' as u8, 'C' as u8, 0, 0, 0, 0, 0, 0] + ); + + let decoded = MonumentGeoFrame::decode(big_endian, &buf).unwrap(); + + assert_eq!(decoded, frame); + } +} diff --git a/binex/tests/decoder.rs b/binex/tests/decoder.rs index b1786d622..70bb652bc 100644 --- a/binex/tests/decoder.rs +++ b/binex/tests/decoder.rs @@ -14,7 +14,7 @@ fn mfle20190130() { found += 1; println!("parsed: {:?}", msg); }, - Some(Ok(StreamElement::ClosedSource(element))) => {}, + Some(Ok(StreamElement::ClosedSource(_))) => {}, Some(Err(e)) => match e { Error::IoError => panic!("i/o error"), e => { @@ -44,7 +44,7 @@ fn gziped_files() { found += 1; println!("parsed: {:?}", msg); }, - Some(Ok(StreamElement::ClosedSource(element))) => {}, + Some(Ok(StreamElement::ClosedSource(_))) => {}, Some(Err(e)) => match e { Error::IoError => panic!("i/o error"), e => { diff --git a/binex/tests/message.rs b/binex/tests/message.rs index 37df33a00..920c30f6a 100644 --- a/binex/tests/message.rs +++ b/binex/tests/message.rs @@ -1,6 +1,5 @@ use binex::prelude::{ - EphemerisFrame, Epoch, GPSEphemeris, GPSRaw, Message, MonumentGeoMetadata, MonumentGeoRecord, - Record, TimeResolution, + EphemerisFrame, Epoch, GPSEphemeris, GPSRaw, Message, MonumentGeoRecord, Record, TimeResolution, }; #[test] @@ -60,7 +59,7 @@ fn test_crc16_geo() { } #[test] -fn test_crc8_eph() { +fn test_crc8_gps() { let msg = Message::new( true, TimeResolution::QuarterSecond, @@ -74,15 +73,14 @@ fn test_crc8_eph() { assert_eq!(buf[0], 226); // SYNC assert_eq!(buf[1], 1); // MID - assert_eq!(buf[2], 78); // RLEN - // assert_eq!(buf[3 + 78], 79); // CRC TODO + assert_eq!(buf[2], 79); // RLEN let parsed = Message::decode(&buf).unwrap(); assert_eq!(msg, parsed); } #[test] -fn test_crc16_eph() { +fn test_crc16_gps() { let msg = Message::new( true, TimeResolution::QuarterSecond, @@ -91,12 +89,12 @@ fn test_crc16_eph() { Record::new_ephemeris_frame(EphemerisFrame::new_gps(GPSEphemeris::default())), ); - let mut buf = [0; 128]; - assert!(msg.encode(&mut buf).is_err()); + let mut encoded = [0; 128]; + assert!(msg.encode(&mut encoded).is_err()); - let mut buf = [0; 256]; - msg.encode(&mut buf).unwrap(); + let mut encoded = [0; 256]; + msg.encode(&mut encoded).unwrap(); - let parsed = Message::decode(&buf).unwrap(); + let parsed = Message::decode(&encoded).unwrap(); assert_eq!(msg, parsed); } diff --git a/binex/tools/crc16.py b/binex/tools/crc16.py index cd48e3e3e..65be05d92 100755 --- a/binex/tools/crc16.py +++ b/binex/tools/crc16.py @@ -2,7 +2,7 @@ def generate_crc16_look_up_table(): with open("output.txt", "w") as fd: - polynomial = 0x8161 + polynomial = 0x1021 for i in range(256): crc = i << 8 for _ in range(8): diff --git a/binex/tools/crc32.py b/binex/tools/crc32.py index daf32ba46..e72cf8a20 100755 --- a/binex/tools/crc32.py +++ b/binex/tools/crc32.py @@ -10,8 +10,8 @@ def generate_crc32_look_up_table(): crc = (crc << 1) ^ polynomial else: crc = crc << 1 - crc &= 0xffffffff + fd.write("0x{:04X}, ".format(crc)) if (i+1) % 8 == 0 : fd.write("\n") From 5a40e6c8ea66cb016d13e31b6700308a7eddeb6c Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 21:50:53 +0100 Subject: [PATCH 27/28] Update docs Signed-off-by: Guillaume W. Bres --- binex/README.md | 49 ++++++---------------------------------- binex/src/decoder.rs | 4 ++++ binex/src/message/mod.rs | 27 +++++++++++++--------- 3 files changed, 27 insertions(+), 53 deletions(-) diff --git a/binex/README.md b/binex/README.md index ff0b1bcd7..c1a9c2366 100644 --- a/binex/README.md +++ b/binex/README.md @@ -26,49 +26,13 @@ Message Decoding ================ Use the BINEX `Decoder` to decode a `Readable` interface streaming -BINEX messages. Decoder exposes open source Message that -it fully interprated: +BINEX messages. Decoder exposes both open source Messages that +were fully interprated and closed source Messages (undisclosed prototypes) +that it cannot interprate: ```rust use std::fs::File; -use binex::prelude::{OpenSourceDecoder, Error}; - -let fd = File::open("../test_resources/BIN/mfle20190130.bnx") - .unwrap(); - -let mut decoder = OpenSourceDecoder::new(fd); - -loop { - match decoder.next() { - Some(Ok(msg)) => { - // process decoded [Message] - }, - Some(Err(e)) => { - // it is possible that some frames may not - // be supported yet. - // Any I/O error should not happen. - }, - None => { - // end of stream - break; - }, - } -} -``` - -The [OpenSourceDecoder] will solely decode documented -open source [Message]s that the stream contains. -But BINEX is flexiblea and allows the description of closed -source messages, not realesed to the general public yet. - -You can use the [Decoder] object that will decode -both disclosed [Message] and undisclosed frames in form of -[ClosedSourceElement]s of the data stream. It is then up to you -to complete the stream interpretation: - -```rust -use std::fs::File; -use binex::prelude::{Decoder, Provider, StreamElement, Error}; +use binex::prelude::{Decoder, StreamElement, Provider, Error}; let fd = File::open("../test_resources/BIN/mfle20190130.bnx") .unwrap(); @@ -88,15 +52,16 @@ loop { let mid = element.mid; let mlen = element.mlen; // now proceed to custom interpratetion - element.interprate(|data| { + element.interprate(&|data| { match mid { // process custom mid and custom data // using undisclosed method + _ => {}, } }); } }, - Some(Err(e)) => { + Some(Err(e)) => { // it is possible that some frames may not // be supported yet. // Any I/O error should not happen. diff --git a/binex/src/decoder.rs b/binex/src/decoder.rs index 5e73f1ac4..0e994293e 100644 --- a/binex/src/decoder.rs +++ b/binex/src/decoder.rs @@ -255,6 +255,10 @@ impl<'a, R: Read> Iterator for Decoder<'a, R> { } return Some(Err(Error::IncompleteMessage(meta.mlen))); }, + Error::UnknownMessage => { + // panic!("unknown message\nrd_ptr={}\nbuf={:?}", self.rd_ptr, &self.buf[self.rd_ptr-1..self.rd_ptr+4]); + self.rd_ptr += 1; + }, _ => { // bad content that does not look like valid BINEX. // This is very inefficient. If returned error would increment diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 9aad2416d..70f287622 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -86,32 +86,32 @@ impl Message { let time_res = TimeResolution::QuarterSecond; // 1. locate SYNC byte - if let Some(offset) = Self::locate(Constants::FWDSYNC_BE_STANDARD_CRC, buf) { + if let Some(offset) = Self::find(Constants::FWDSYNC_BE_STANDARD_CRC, buf) { big_endian = true; sync_off = offset; - } else if let Some(offset) = Self::locate(Constants::FWDSYNC_LE_STANDARD_CRC, buf) { + } else if let Some(offset) = Self::find(Constants::FWDSYNC_LE_STANDARD_CRC, buf) { sync_off = offset; big_endian = false; - } else if let Some(offset) = Self::locate(Constants::FWDSYNC_BE_ENHANCED_CRC, buf) { + } else if let Some(offset) = Self::find(Constants::FWDSYNC_BE_ENHANCED_CRC, buf) { big_endian = true; enhanced_crc = true; sync_off = offset; - } else if let Some(offset) = Self::locate(Constants::FWDSYNC_LE_ENHANCED_CRC, buf) { + } else if let Some(offset) = Self::find(Constants::FWDSYNC_LE_ENHANCED_CRC, buf) { enhanced_crc = true; sync_off = offset; - } else if let Some(offset) = Self::locate(Constants::REVSYNC_LE_STANDARD_CRC, buf) { + } else if let Some(offset) = Self::find(Constants::REVSYNC_LE_STANDARD_CRC, buf) { reversed = true; sync_off = offset; - } else if let Some(offset) = Self::locate(Constants::REVSYNC_BE_STANDARD_CRC, buf) { + } else if let Some(offset) = Self::find(Constants::REVSYNC_BE_STANDARD_CRC, buf) { reversed = true; big_endian = true; sync_off = offset; - } else if let Some(offset) = Self::locate(Constants::REVSYNC_BE_ENHANCED_CRC, buf) { + } else if let Some(offset) = Self::find(Constants::REVSYNC_BE_ENHANCED_CRC, buf) { reversed = true; big_endian = true; enhanced_crc = true; sync_off = offset; - } else if let Some(offset) = Self::locate(Constants::REVSYNC_LE_ENHANCED_CRC, buf) { + } else if let Some(offset) = Self::find(Constants::REVSYNC_LE_ENHANCED_CRC, buf) { reversed = true; enhanced_crc = true; sync_off = offset; @@ -144,6 +144,11 @@ impl Message { // 2. parse MID let (bnxi, mid_1_4) = Self::decode_bnxi(&buf[ptr..], big_endian); let mid = MessageID::from(bnxi); + + if mid == MessageID::Unknown { + return Err(Error::UnknownMessage); + } + ptr += mid_1_4; // make sure we can parse up to 4 byte MLEN @@ -154,9 +159,9 @@ impl Message { // 3. parse MLEN let (mlen, mlen_1_4) = Self::decode_bnxi(&buf[ptr..], big_endian); let mlen = mlen as usize; - // println!("mlen={}", mlen); + //println!("mid={:?}/mlen={}/ptr={}", mid, mlen, ptr); - if buf_len - ptr < mlen { + if ptr + mlen > buf_len { // buffer does not contain complete message! return Err(Error::IncompleteMessage(mlen)); } @@ -325,7 +330,7 @@ impl Message { } /// Tries to locate desired byte within buffer - fn locate(to_find: u8, buf: &[u8]) -> Option { + fn find(to_find: u8, buf: &[u8]) -> Option { buf.iter().position(|b| *b == to_find) } From cee4b6619a99e0dc2a8571f4e0f43944c32345ae Mon Sep 17 00:00:00 2001 From: "Guillaume W. Bres" Date: Sat, 2 Nov 2024 22:13:59 +0100 Subject: [PATCH 28/28] ClosedSourceMeta: * the decoding process will need it Signed-off-by: Guillaume W. Bres --- binex/README.md | 18 +++-- binex/src/lib.rs | 14 +++- binex/src/message/mod.rs | 3 + binex/src/stream.rs | 156 +++++++++++++++++++++++++++++++++------ 4 files changed, 155 insertions(+), 36 deletions(-) diff --git a/binex/README.md b/binex/README.md index c1a9c2366..bb6cbd624 100644 --- a/binex/README.md +++ b/binex/README.md @@ -46,16 +46,18 @@ loop { }, Some(Ok(StreamElement::ClosedSource(element))) => { // verify this is your organization - if element.provider == Provider::JPL { - // MID and MLEN should follow the BINEX specs - // and are provided as decoded - let mid = element.mid; - let mlen = element.mlen; - // now proceed to custom interpratetion + if element.meta.provider == Provider::JPL { + // grab fields that you probably need to decode + let mid = element.meta.mid; + let mlen = element.meta.mlen; + let big_endian = element.meta.big_endian; + let is_reversed = element.meta.reversed; + let enhanced_crc = element.meta.enhanced_crc; + + // now, proceed to interpretation of this element, + // using undisclosed method element.interprate(&|data| { match mid { - // process custom mid and custom data - // using undisclosed method _ => {}, } }); diff --git a/binex/src/lib.rs b/binex/src/lib.rs index 73b15b123..2df4a82ec 100644 --- a/binex/src/lib.rs +++ b/binex/src/lib.rs @@ -32,12 +32,18 @@ use crate::stream::Provider; pub struct ClosedSourceMeta { // decoded MID (as is) pub mid: u32, - // decoded MLEN (as is) + /// decoded MLEN (as is) pub mlen: usize, - // payload offset in buffer - pub offset: usize, - // [Provider] of this message. Only this organization may continue the decoding process. + /// Whether this item is reversed or not + pub reversed: bool, + /// Whether this item uses enhanced CRC or not + pub enhanced_crc: bool, + /// Whether this is big endian encoded or not + pub big_endian: bool, + /// [Provider] of this message. Only this organization may continue the decoding process. pub provider: Provider, + // payload offset in buffer + offset: usize, } #[derive(Debug)] diff --git a/binex/src/message/mod.rs b/binex/src/message/mod.rs index 70f287622..e633892ec 100644 --- a/binex/src/message/mod.rs +++ b/binex/src/message/mod.rs @@ -186,6 +186,9 @@ impl Message { mlen, provider, offset: ptr, + big_endian, + reversed, + enhanced_crc, mid: mid.into(), })); } else { diff --git a/binex/src/stream.rs b/binex/src/stream.rs index fb4afb1de..19eb26b0f 100644 --- a/binex/src/stream.rs +++ b/binex/src/stream.rs @@ -1,5 +1,5 @@ //! BINEX Stream representation -use crate::prelude::Message; +use crate::prelude::{ClosedSourceMeta, Message}; /// [Message] [Provider] #[derive(Debug, Copy, Clone, PartialEq)] @@ -49,17 +49,13 @@ impl Provider { /// Closed source frame that we can encode but not interprate. /// This particular [StreamElement] can be either a part of a continuous serie or self sustainable. pub struct ClosedSourceElement<'a> { - /// Message ID as decoded - pub mid: u32, - /// Provider of this frame. - /// Only this organization may have capabilities to interprate this frame. - pub provider: Provider, - /// Total size of this [StreamElement] as it may be part of a continuous - /// serie of [StreamElement]s. + /// [ClosedSourceMeta] + pub meta: ClosedSourceMeta, + /// Size of this [StreamElement]: this is not + /// the size of the complete message, in case this is part + /// of a serie of [StreamElement]s. pub size: usize, - /// Total message size (not the size of this element) - pub mlen: usize, - /// Raw data content that we can encode, decode but not interprate. + /// Raw data starting at first byte of undisclosed payload. pub raw: &'a [u8], } @@ -103,19 +99,31 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of this [StreamElement] /// - mlen: total message lenth + /// - reversed: whether this uses the reversed stream algorithm or not + /// - enhanced_crc: whether this uses the enhanced CRC or not + /// - big_endian: whether we'll use "big" endianess when encoding, or not. pub fn new_prototype( provider: Provider, mid: u32, raw: &'a [u8], size: usize, mlen: usize, + reversed: bool, + enhanced_crc: bool, + big_endian: bool, ) -> Self { Self::ClosedSource(ClosedSourceElement { raw, - mlen, size, - mid, - provider, + meta: ClosedSourceMeta { + mid, + mlen, + reversed, + enhanced_crc, + big_endian, + provider, + offset: 0, + }, }) } @@ -125,8 +133,28 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn jpl_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { - Self::new_prototype(Provider::JPL, mid, raw, size, total) + /// - reversed: whether this uses the reversed stream algorithm or not + /// - enhanced_crc: whether this uses the enhanced CRC or not + /// - big_endian: whether we'll use "big" endianess when encoding, or not. + pub fn jpl_prototype( + raw: &'a [u8], + mid: u32, + size: usize, + total: usize, + reversed: bool, + enhanced_crc: bool, + big_endian: bool, + ) -> Self { + Self::new_prototype( + Provider::JPL, + mid, + raw, + size, + total, + reversed, + enhanced_crc, + big_endian, + ) } /// Add one closed source [StreamElement]s provided by desired [Provider::JPL]. @@ -135,8 +163,28 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn igs_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { - Self::new_prototype(Provider::IGS, mid, raw, size, total) + /// - reversed: whether this uses the reversed stream algorithm or not + /// - enhanced_crc: whether this uses the enhanced CRC or not + /// - big_endian: whether we'll use "big" endianess when encoding, or not. + pub fn igs_prototype( + raw: &'a [u8], + mid: u32, + size: usize, + total: usize, + reversed: bool, + enhanced_crc: bool, + big_endian: bool, + ) -> Self { + Self::new_prototype( + Provider::IGS, + mid, + raw, + size, + total, + reversed, + enhanced_crc, + big_endian, + ) } /// Add one closed source [StreamElement]s provided by desired [Provider::ColoradoUnivBoulder]. @@ -145,8 +193,28 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn cuboulder_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { - Self::new_prototype(Provider::ColoradoUnivBoulder, mid, raw, size, total) + /// - reversed: whether this uses the reversed stream algorithm or not + /// - enhanced_crc: whether this uses the enhanced CRC or not + /// - big_endian: whether we'll use "big" endianess when encoding, or not. + pub fn cuboulder_prototype( + raw: &'a [u8], + mid: u32, + size: usize, + total: usize, + reversed: bool, + enhanced_crc: bool, + big_endian: bool, + ) -> Self { + Self::new_prototype( + Provider::ColoradoUnivBoulder, + mid, + raw, + size, + total, + reversed, + enhanced_crc, + big_endian, + ) } /// Add one closed source [StreamElement]s provided by desired [Provider::NRCan]. @@ -155,8 +223,28 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn nrcan_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { - Self::new_prototype(Provider::NRCan, mid, raw, size, total) + /// - reversed: whether this uses the reversed stream algorithm or not + /// - enhanced_crc: whether this uses the enhanced CRC or not + /// - big_endian: whether we'll use "big" endianess when encoding, or not. + pub fn nrcan_prototype( + raw: &'a [u8], + mid: u32, + size: usize, + total: usize, + reversed: bool, + enhanced_crc: bool, + big_endian: bool, + ) -> Self { + Self::new_prototype( + Provider::NRCan, + mid, + raw, + size, + total, + reversed, + enhanced_crc, + big_endian, + ) } /// Add one closed source [StreamElement]s provided by desired [Provider::UCAR]. @@ -165,7 +253,27 @@ impl<'a> StreamElement<'a> { /// - raw: content we can encode, decode but not interprate /// - size: size of the provided buffer (bytewise) /// - total: total size of the closed source Message (bytewise) - pub fn ucar_prototype(raw: &'a [u8], mid: u32, size: usize, total: usize) -> Self { - Self::new_prototype(Provider::UCAR, mid, raw, size, total) + /// - reversed: whether this uses the reversed stream algorithm or not + /// - enhanced_crc: whether this uses the enhanced CRC or not + /// - big_endian: whether we'll use "big" endianess when encoding, or not. + pub fn ucar_prototype( + raw: &'a [u8], + mid: u32, + size: usize, + total: usize, + reversed: bool, + enhanced_crc: bool, + big_endian: bool, + ) -> Self { + Self::new_prototype( + Provider::UCAR, + mid, + raw, + size, + total, + reversed, + enhanced_crc, + big_endian, + ) } }