Initial commit
This commit is contained in:
		
						commit
						eef7484866
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					/target
 | 
				
			||||||
 | 
					Cargo.lock
 | 
				
			||||||
							
								
								
									
										23
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "rsbag"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2018"
 | 
				
			||||||
 | 
					build = "build.rs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					bytes = "1.1.0"
 | 
				
			||||||
 | 
					lalrpop-util = "0.19.6"
 | 
				
			||||||
 | 
					log = "0.4.14"
 | 
				
			||||||
 | 
					nom = "7.0.0"
 | 
				
			||||||
 | 
					num_enum = "0.5.4"
 | 
				
			||||||
 | 
					smallvec = "1.6.1"
 | 
				
			||||||
 | 
					thiserror = "1.0.28"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dev-dependencies]
 | 
				
			||||||
 | 
					env_logger = "0.9.0"
 | 
				
			||||||
 | 
					eyre = "0.6.5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[build-dependencies]
 | 
				
			||||||
 | 
					lalrpop = "0.19.6"
 | 
				
			||||||
							
								
								
									
										6
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    lalrpop::Configuration::new()
 | 
				
			||||||
 | 
					        .generate_in_source_tree()
 | 
				
			||||||
 | 
					        .process()
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								examples/bag_info.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								examples/bag_info.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					use std::{env::args, fs::File};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use log::{error, info, trace};
 | 
				
			||||||
 | 
					use nom::Parser;
 | 
				
			||||||
 | 
					use rsbag::{index::BagIndex, reader::IoReader};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    env_logger::init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let args: Vec<_> = args().collect();
 | 
				
			||||||
 | 
					    if args.len() != 2 {
 | 
				
			||||||
 | 
					        eprintln!("Usage: {} <bag path>", args[0]);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let bag_path = &args[1];
 | 
				
			||||||
 | 
					    let bag_file = File::open(bag_path).expect("Could not open bag file");
 | 
				
			||||||
 | 
					    let mut bag_reader = IoReader::new(bag_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match BagIndex::read_all(&mut bag_reader) {
 | 
				
			||||||
 | 
					        Ok(index) => {
 | 
				
			||||||
 | 
					            for conn in &index.connections {
 | 
				
			||||||
 | 
					                let msgdef = conn.message_definition().unwrap();
 | 
				
			||||||
 | 
					                trace!("message definition: {}", msgdef);
 | 
				
			||||||
 | 
					                let parser = rsbag::message_definition::grammar::MessageDefinitionParser::new();
 | 
				
			||||||
 | 
					                match parser.parse(&msgdef) {
 | 
				
			||||||
 | 
					                    Ok(def) => info!("message definition parsed: {}", def),
 | 
				
			||||||
 | 
					                    Err(err) => error!("message definition parse error: {}", err),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Err(err) => error!("bag parse error: {}", err),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					use num_enum::TryFromPrimitiveError;
 | 
				
			||||||
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::parse::{self, Op};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Error)]
 | 
				
			||||||
 | 
					pub enum Error {
 | 
				
			||||||
 | 
					    #[error("i/o error: {0}")]
 | 
				
			||||||
 | 
					    Io(#[from] std::io::Error),
 | 
				
			||||||
 | 
					    #[error("{0}")]
 | 
				
			||||||
 | 
					    Parse(parse::Error<parse::InputOwned>),
 | 
				
			||||||
 | 
					    #[error("unsupported version: {0}")]
 | 
				
			||||||
 | 
					    UnsupportedVersion(parse::Version),
 | 
				
			||||||
 | 
					    #[error("unsupported encryptor: {0}")]
 | 
				
			||||||
 | 
					    UnsupportedEncryptor(String),
 | 
				
			||||||
 | 
					    #[error("unexpected EOF")]
 | 
				
			||||||
 | 
					    Eof,
 | 
				
			||||||
 | 
					    #[error("invalid header op: {0}")]
 | 
				
			||||||
 | 
					    InvalidOp(#[from] TryFromPrimitiveError<Op>),
 | 
				
			||||||
 | 
					    #[error("missing field: {0:?}")]
 | 
				
			||||||
 | 
					    MissingField(String),
 | 
				
			||||||
 | 
					    #[error("bag is unindexed")]
 | 
				
			||||||
 | 
						Unindexed,
 | 
				
			||||||
 | 
					    #[error("{0}")]
 | 
				
			||||||
 | 
					    Other(String),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> From<parse::Error<parse::Input<'a>>> for Error {
 | 
				
			||||||
 | 
					    fn from(err: parse::Error<parse::Input>) -> Self {
 | 
				
			||||||
 | 
					        Error::Parse(err.into_owned())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> From<nom::Err<parse::Error<parse::Input<'a>>>> for Error {
 | 
				
			||||||
 | 
					    fn from(err: nom::Err<parse::Error<parse::Input<'a>>>) -> Self {
 | 
				
			||||||
 | 
					        match err {
 | 
				
			||||||
 | 
					            nom::Err::Error(e) | nom::Err::Failure(e) => e.into(),
 | 
				
			||||||
 | 
					            nom::Err::Incomplete(_) => panic!("incomplete error"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Error {
 | 
				
			||||||
 | 
					    pub fn other<S: Into<String>>(message: S) -> Self {
 | 
				
			||||||
 | 
					        Error::Other(message.into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type Result<T, E = Error> = core::result::Result<T, E>;
 | 
				
			||||||
							
								
								
									
										124
									
								
								src/index.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/index.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					use std::io::SeekFrom;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use log::trace;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    parse::{Header, Op},
 | 
				
			||||||
 | 
					    reader::BagReader,
 | 
				
			||||||
 | 
					    Error, Result,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct ConnInfo {
 | 
				
			||||||
 | 
					    pub id: u32,
 | 
				
			||||||
 | 
					    pub topic: String,
 | 
				
			||||||
 | 
					    pub conn_header: Header,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ConnInfo {
 | 
				
			||||||
 | 
					    pub fn from_headers(record_header: Header, conn_header: Header) -> Result<Self> {
 | 
				
			||||||
 | 
					        Ok(ConnInfo {
 | 
				
			||||||
 | 
					            id: record_header.read_u32(b"conn")?,
 | 
				
			||||||
 | 
					            topic: record_header.read_string(b"topic")?,
 | 
				
			||||||
 | 
					            conn_header,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn message_definition(&self) -> Result<String> {
 | 
				
			||||||
 | 
					        self.conn_header.read_string(b"message_definition")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn datatype(&self) -> Result<String> {
 | 
				
			||||||
 | 
					        self.conn_header.read_string(b"type")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn md5sum(&self) -> Result<String> {
 | 
				
			||||||
 | 
					        self.conn_header.read_string(b"md5sum")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct ChunkInfo {
 | 
				
			||||||
 | 
					    pub pos: u64,
 | 
				
			||||||
 | 
					    pub start_time: u64, // TODO: unpack time
 | 
				
			||||||
 | 
					    pub end_time: u64,
 | 
				
			||||||
 | 
					    pub conn_count: u32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ChunkInfo {
 | 
				
			||||||
 | 
					    pub fn from_header(header: Header) -> Result<Self> {
 | 
				
			||||||
 | 
					        if header.read_u32(b"ver")? != 1 {
 | 
				
			||||||
 | 
					            return Err(Error::other("unsupported ChunkInfo version"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(ChunkInfo {
 | 
				
			||||||
 | 
					            pos: header.read_u64(b"chunk_pos")?,
 | 
				
			||||||
 | 
					            start_time: header.read_u64(b"start_time")?,
 | 
				
			||||||
 | 
					            end_time: header.read_u64(b"end_time")?,
 | 
				
			||||||
 | 
					            conn_count: header.read_u32(b"count")?,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct BagIndex {
 | 
				
			||||||
 | 
					    pub connections: Vec<ConnInfo>,
 | 
				
			||||||
 | 
					    pub chunks: Vec<ChunkInfo>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl BagIndex {
 | 
				
			||||||
 | 
					    fn read_v2<R: BagReader>(reader: &mut R) -> Result<Self> {
 | 
				
			||||||
 | 
					        let file_header = reader.read_header_op(Op::FileHeader)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let data_length = reader.read_data_length()?;
 | 
				
			||||||
 | 
					        trace!("data length: {}", data_length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let index_pos = file_header.read_u64(b"index_pos")?;
 | 
				
			||||||
 | 
					        trace!("index pos: {}", index_pos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if index_pos == 0 {
 | 
				
			||||||
 | 
					            return Err(Error::Unindexed);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Ok(encryptor) = file_header.read_string(b"encryptor") {
 | 
				
			||||||
 | 
					            return Err(Error::UnsupportedEncryptor(encryptor));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reader.seek(SeekFrom::Start(index_pos))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let conn_count = file_header.read_u32(b"conn_count")?;
 | 
				
			||||||
 | 
					        trace!("connection count: {}", conn_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut connections = Vec::with_capacity(conn_count as usize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for _ in 0..conn_count {
 | 
				
			||||||
 | 
					            let conn = reader.read_conn_info()?;
 | 
				
			||||||
 | 
					            trace!("connection: id={}, topic={}", conn.id, conn.topic);
 | 
				
			||||||
 | 
					            connections.push(conn);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let chunk_count = file_header.read_u32(b"chunk_count")?;
 | 
				
			||||||
 | 
					        trace!("chunk count: {}", chunk_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut chunks = Vec::with_capacity(chunk_count as usize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for _ in 0..chunk_count {
 | 
				
			||||||
 | 
					            let chunk = reader.read_chunk_info()?;
 | 
				
			||||||
 | 
					            chunks.push(chunk);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(BagIndex {
 | 
				
			||||||
 | 
					            connections,
 | 
				
			||||||
 | 
					            chunks,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read_all<R: BagReader>(mut reader: &mut R) -> Result<Self> {
 | 
				
			||||||
 | 
					        let version = reader.read_version()?;
 | 
				
			||||||
 | 
					        trace!("bag version: {}", version);
 | 
				
			||||||
 | 
					        if (version.major, version.minor) == (2, 0) {
 | 
				
			||||||
 | 
					            Self::read_v2(reader)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Err(Error::UnsupportedVersion(version))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					mod error;
 | 
				
			||||||
 | 
					pub mod index;
 | 
				
			||||||
 | 
					pub mod parse;
 | 
				
			||||||
 | 
					pub mod reader;
 | 
				
			||||||
 | 
					pub mod message_definition;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use error::{Error, Result};
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/message_definition.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/message_definition.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					pub mod ast;
 | 
				
			||||||
 | 
					pub mod grammar;
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/message_definition/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/message_definition/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					/grammar.rs
 | 
				
			||||||
							
								
								
									
										140
									
								
								src/message_definition/ast.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/message_definition/ast.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					use core::fmt::{self, Write};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type SmallStr = String;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum PrimitiveType {
 | 
				
			||||||
 | 
					    Bool,
 | 
				
			||||||
 | 
					    Int8,
 | 
				
			||||||
 | 
					    Uint8,
 | 
				
			||||||
 | 
					    Int16,
 | 
				
			||||||
 | 
					    Uint16,
 | 
				
			||||||
 | 
					    Int32,
 | 
				
			||||||
 | 
					    Uint32,
 | 
				
			||||||
 | 
					    Int64,
 | 
				
			||||||
 | 
					    Uint64,
 | 
				
			||||||
 | 
					    Float32,
 | 
				
			||||||
 | 
					    Float64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl PrimitiveType {
 | 
				
			||||||
 | 
					    fn as_str(self) -> &'static str {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            PrimitiveType::Bool => "bool",
 | 
				
			||||||
 | 
					            PrimitiveType::Int8 => "int8_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Uint8 => "uint8_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Int16 => "int16_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Uint16 => "uint16_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Int32 => "int32_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Uint32 => "uint32_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Int64 => "int64_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Uint64 => "uint64_t",
 | 
				
			||||||
 | 
					            PrimitiveType::Float32 => "float32",
 | 
				
			||||||
 | 
					            PrimitiveType::Float64 => "float64",
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn size(self) -> usize {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            PrimitiveType::Bool => 1,
 | 
				
			||||||
 | 
					            PrimitiveType::Int8 => 1,
 | 
				
			||||||
 | 
					            PrimitiveType::Uint8 => 1,
 | 
				
			||||||
 | 
					            PrimitiveType::Int16 => 2,
 | 
				
			||||||
 | 
					            PrimitiveType::Uint16 => 2,
 | 
				
			||||||
 | 
					            PrimitiveType::Int32 => 4,
 | 
				
			||||||
 | 
					            PrimitiveType::Uint32 => 4,
 | 
				
			||||||
 | 
					            PrimitiveType::Int64 => 8,
 | 
				
			||||||
 | 
					            PrimitiveType::Uint64 => 8,
 | 
				
			||||||
 | 
					            PrimitiveType::Float32 => 4,
 | 
				
			||||||
 | 
					            PrimitiveType::Float64 => 8,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for PrimitiveType {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        f.write_str(self.as_str())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq)]
 | 
				
			||||||
 | 
					pub struct ArrayType {
 | 
				
			||||||
 | 
					    pub element_type: Box<FieldType>,
 | 
				
			||||||
 | 
					    pub length: Option<usize>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ArrayType {
 | 
				
			||||||
 | 
					    pub fn new(element_type: FieldType, length: Option<usize>) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            element_type: Box::new(element_type),
 | 
				
			||||||
 | 
					            length,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for ArrayType {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        self.element_type.fmt(f)?;
 | 
				
			||||||
 | 
					        f.write_char('[')?;
 | 
				
			||||||
 | 
					        if let Some(length) = self.length {
 | 
				
			||||||
 | 
					            length.fmt(f)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        f.write_char(']')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq)]
 | 
				
			||||||
 | 
					pub enum FieldType {
 | 
				
			||||||
 | 
					    Primitive(PrimitiveType),
 | 
				
			||||||
 | 
					    String,
 | 
				
			||||||
 | 
					    Time,
 | 
				
			||||||
 | 
					    Duration,
 | 
				
			||||||
 | 
					    Header,
 | 
				
			||||||
 | 
					    Array(ArrayType),
 | 
				
			||||||
 | 
					    Other(SmallStr),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for FieldType {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            FieldType::Primitive(primitive) => primitive.fmt(f),
 | 
				
			||||||
 | 
					            FieldType::String => "string".fmt(f),
 | 
				
			||||||
 | 
					            FieldType::Time => "time".fmt(f),
 | 
				
			||||||
 | 
					            FieldType::Duration => "duration".fmt(f),
 | 
				
			||||||
 | 
					            FieldType::Header => "Header".fmt(f),
 | 
				
			||||||
 | 
					            FieldType::Array(array) => array.fmt(f),
 | 
				
			||||||
 | 
					            FieldType::Other(other) => other.fmt(f),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq)]
 | 
				
			||||||
 | 
					pub struct MessageField {
 | 
				
			||||||
 | 
					    pub name: SmallStr,
 | 
				
			||||||
 | 
					    pub typ: FieldType,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for MessageField {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        write!(f, "{} {}", &self.typ, &self.name)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq)]
 | 
				
			||||||
 | 
					pub struct MessageDefinition {
 | 
				
			||||||
 | 
					    pub fields: Vec<MessageField>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for MessageDefinition {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        for field in &self.fields {
 | 
				
			||||||
 | 
					            writeln!(f, "{}", field)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct MessageDefinitions {
 | 
				
			||||||
 | 
					    pub primary: MessageDefinition,
 | 
				
			||||||
 | 
					    pub dependencies: Vec<(String, MessageDefinition)>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								src/message_definition/grammar.lalrpop
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/message_definition/grammar.lalrpop
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					use super::ast::{
 | 
				
			||||||
 | 
						PrimitiveType, ArrayType, FieldType, MessageField, MessageDefinition, MessageDefinitions
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					grammar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					match {
 | 
				
			||||||
 | 
						"bool",
 | 
				
			||||||
 | 
						"int8",
 | 
				
			||||||
 | 
						"uint8",
 | 
				
			||||||
 | 
						"int16",
 | 
				
			||||||
 | 
						"uint16",
 | 
				
			||||||
 | 
						"int32",
 | 
				
			||||||
 | 
						"uint32",
 | 
				
			||||||
 | 
						"int64",
 | 
				
			||||||
 | 
						"uint64",
 | 
				
			||||||
 | 
						"float32",
 | 
				
			||||||
 | 
						"float64",
 | 
				
			||||||
 | 
						"string",
 | 
				
			||||||
 | 
						"time",
 | 
				
			||||||
 | 
						"duration",
 | 
				
			||||||
 | 
						"Header",
 | 
				
			||||||
 | 
						"[",
 | 
				
			||||||
 | 
						"]",
 | 
				
			||||||
 | 
						r"==+\r?\nMSG: " => "MESSAGE_BOUNDARY",
 | 
				
			||||||
 | 
						r"[0-9]+" => "LENGTH",
 | 
				
			||||||
 | 
					    r"[\t ]*" => { },
 | 
				
			||||||
 | 
					    r"#[^\n\r]*" => { }, // Skip `# comments`
 | 
				
			||||||
 | 
					} else {
 | 
				
			||||||
 | 
						r"[a-zA-Z][a-zA-Z0-9_]*" => "IDENT",
 | 
				
			||||||
 | 
					} else {
 | 
				
			||||||
 | 
						r"[a-zA-Z][a-zA-Z0-9_]*(/[a-zA-Z][a-zA-Z0-9_]*)*" => "TYPENAME",
 | 
				
			||||||
 | 
						r"\r?\n" => "CRLF",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PrimitiveType: PrimitiveType = {
 | 
				
			||||||
 | 
						"bool" => PrimitiveType::Bool,
 | 
				
			||||||
 | 
						"int8" => PrimitiveType::Int8,
 | 
				
			||||||
 | 
						"uint8" => PrimitiveType::Uint8,
 | 
				
			||||||
 | 
						"int16" => PrimitiveType::Int16,
 | 
				
			||||||
 | 
						"uint16" => PrimitiveType::Uint16,
 | 
				
			||||||
 | 
						"int32" => PrimitiveType::Int32,
 | 
				
			||||||
 | 
						"uint32" => PrimitiveType::Uint32,
 | 
				
			||||||
 | 
						"int64" => PrimitiveType::Int64,
 | 
				
			||||||
 | 
						"uint64" => PrimitiveType::Uint64,
 | 
				
			||||||
 | 
						"float32" => PrimitiveType::Float32,
 | 
				
			||||||
 | 
						"float64" => PrimitiveType::Float64,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ArrayLength: Option<usize> = {
 | 
				
			||||||
 | 
						"[" <"LENGTH"> "]" => Some(usize::from_str(<>).unwrap()),
 | 
				
			||||||
 | 
						"[" "]" => None,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ArrayType: ArrayType =
 | 
				
			||||||
 | 
						FieldType ArrayLength => ArrayType::new(<>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ident: String = "IDENT" => <>.to_string();
 | 
				
			||||||
 | 
					TypeName: String = {
 | 
				
			||||||
 | 
						"TYPENAME" => <>.to_string(),
 | 
				
			||||||
 | 
						"IDENT" => <>.to_string(),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FieldType: FieldType = {
 | 
				
			||||||
 | 
						PrimitiveType => FieldType::Primitive(<>),
 | 
				
			||||||
 | 
						"string" => FieldType::String,
 | 
				
			||||||
 | 
						"time" => FieldType::Time,
 | 
				
			||||||
 | 
						"duration" => FieldType::Duration,
 | 
				
			||||||
 | 
						"Header" => FieldType::Header,
 | 
				
			||||||
 | 
						ArrayType => FieldType::Array(<>),
 | 
				
			||||||
 | 
						TypeName => FieldType::Other(<>),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MessageField: MessageField = {
 | 
				
			||||||
 | 
						<typ:FieldType> <name:Ident> => MessageField { <> }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MessageFields: Vec<MessageField> =
 | 
				
			||||||
 | 
						<(<MessageField> "CRLF"+)*> => <>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub MessageDefinition: MessageDefinition =
 | 
				
			||||||
 | 
						"CRLF"* <fields:MessageFields> => MessageDefinition { fields };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NamedMessageDefinition: (String, MessageDefinition) =
 | 
				
			||||||
 | 
						"MESSAGE_BOUNDARY" <TypeName> "CRLF" <MessageDefinition> => (<>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub MessageDefinitions: MessageDefinitions =
 | 
				
			||||||
 | 
						<primary:MessageDefinition> <dependencies:NamedMessageDefinition*>
 | 
				
			||||||
 | 
							=> MessageDefinitions { <> };
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/parse.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/parse.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					mod error;
 | 
				
			||||||
 | 
					mod fields;
 | 
				
			||||||
 | 
					mod header;
 | 
				
			||||||
 | 
					mod version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use error::{Error, ErrorKind};
 | 
				
			||||||
 | 
					pub use fields::Op;
 | 
				
			||||||
 | 
					pub use header::Header;
 | 
				
			||||||
 | 
					pub use version::Version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type Input<'a> = &'a [u8];
 | 
				
			||||||
 | 
					pub type InputOwned = Vec<u8>;
 | 
				
			||||||
 | 
					pub type IResult<'a, T> = nom::IResult<Input<'a>, T, Error<Input<'a>>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type InputStr<'a> = &'a str;
 | 
				
			||||||
 | 
					pub type IResultStr<'a, T> = nom::IResult<InputStr<'a>, T, Error<InputStr<'a>>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type SmallStr = String;
 | 
				
			||||||
							
								
								
									
										321
									
								
								src/parse/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								src/parse/error.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,321 @@
 | 
				
			|||||||
 | 
					use super::InputOwned;
 | 
				
			||||||
 | 
					use core::{fmt, mem, num::ParseIntError, ops::Range};
 | 
				
			||||||
 | 
					use nom::{
 | 
				
			||||||
 | 
					    error::{
 | 
				
			||||||
 | 
					        ContextError as NomContextError, ErrorKind as NomErrorKind, FromExternalError,
 | 
				
			||||||
 | 
					        ParseError as NomParseError,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    InputLength,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use std::fmt::Write;
 | 
				
			||||||
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq, Error)]
 | 
				
			||||||
 | 
					pub enum ErrorKind {
 | 
				
			||||||
 | 
					    #[error("nom error {0:?}")]
 | 
				
			||||||
 | 
					    Nom(NomErrorKind),
 | 
				
			||||||
 | 
					    #[error("expected '{0}'")]
 | 
				
			||||||
 | 
					    ExpectedChar(char),
 | 
				
			||||||
 | 
					    #[error("...in {0}")]
 | 
				
			||||||
 | 
					    Context(&'static str),
 | 
				
			||||||
 | 
					    #[error("invalid integer: {0}")]
 | 
				
			||||||
 | 
					    InvalidInteger(#[from] ParseIntError),
 | 
				
			||||||
 | 
					    #[error("expected identifier (like /[a-zA-Z][A-Za-z0-9_]*/)")]
 | 
				
			||||||
 | 
					    ExpectedIdentifier,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[allow(clippy::len_without_is_empty)]
 | 
				
			||||||
 | 
					pub trait ErrorInput: InputLength {
 | 
				
			||||||
 | 
					    /// Offset of (start of) other within self.
 | 
				
			||||||
 | 
					    /// Returns None if the start of other is not in self,
 | 
				
			||||||
 | 
					    /// or if the operation is otherwise not supported
 | 
				
			||||||
 | 
					    fn offset(&self, other: &Self) -> Option<usize>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[allow(clippy::len_without_is_empty)]
 | 
				
			||||||
 | 
					pub trait ErrorInputDisplay: fmt::Debug {
 | 
				
			||||||
 | 
					    fn len(&self) -> usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn write_range(&self, range: Range<usize>, f: &mut fmt::Formatter) -> fmt::Result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn pointer() -> &'static str;
 | 
				
			||||||
 | 
					    fn line() -> &'static str;
 | 
				
			||||||
 | 
					    fn space() -> &'static str;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, PartialEq, Debug)]
 | 
				
			||||||
 | 
					pub struct Error<I> {
 | 
				
			||||||
 | 
					    pub input: I,
 | 
				
			||||||
 | 
					    pub errors: Vec<(Option<Range<usize>>, ErrorKind)>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<I: ErrorInput> Error<I> {
 | 
				
			||||||
 | 
					    pub fn new(input: I, kind: ErrorKind) -> Self {
 | 
				
			||||||
 | 
					        let errors = vec![(Some(0..input.input_len()), kind)];
 | 
				
			||||||
 | 
					        Self { input, errors }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn append_kind(&mut self, mut input: I, kind: ErrorKind) {
 | 
				
			||||||
 | 
					        let range = if let Some(offset) = self.input.offset(&input) {
 | 
				
			||||||
 | 
					            // if the new input is contained in self, just use that
 | 
				
			||||||
 | 
					            Some(offset..(offset + input.input_len()))
 | 
				
			||||||
 | 
					        } else if let Some(offset) = input.offset(&self.input) {
 | 
				
			||||||
 | 
					            // if the new input is before self, replace the self input
 | 
				
			||||||
 | 
					            // and update all range offsets
 | 
				
			||||||
 | 
					            let input_len = input.input_len();
 | 
				
			||||||
 | 
					            mem::swap(&mut self.input, &mut input);
 | 
				
			||||||
 | 
					            for err in &mut self.errors {
 | 
				
			||||||
 | 
					                if let Some(err_range) = &mut err.0 {
 | 
				
			||||||
 | 
					                    err_range.start += offset;
 | 
				
			||||||
 | 
					                    err_range.end += offset;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Some(0..input_len)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            None
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        self.errors.push((range, kind));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn error_context<I: ErrorInput + Clone, F, P, O>(
 | 
				
			||||||
 | 
					    mut f: F,
 | 
				
			||||||
 | 
					    mut p: P,
 | 
				
			||||||
 | 
					) -> impl FnMut(I) -> nom::IResult<I, O, Error<I>>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    F: FnMut() -> ErrorKind,
 | 
				
			||||||
 | 
					    P: nom::Parser<I, O, Error<I>>,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    move |data| {
 | 
				
			||||||
 | 
					        p.parse(data.clone()).map_err(|err| {
 | 
				
			||||||
 | 
					            err.map(|mut err| {
 | 
				
			||||||
 | 
					                err.append_kind(data, f());
 | 
				
			||||||
 | 
					                err
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<I: ErrorInput> NomParseError<I> for Error<I> {
 | 
				
			||||||
 | 
					    fn from_error_kind(input: I, kind: NomErrorKind) -> Self {
 | 
				
			||||||
 | 
					        Self::new(input, ErrorKind::Nom(kind))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn append(input: I, kind: NomErrorKind, mut other: Self) -> Self {
 | 
				
			||||||
 | 
					        other.append_kind(input, ErrorKind::Nom(kind));
 | 
				
			||||||
 | 
					        other
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn from_char(input: I, c: char) -> Self {
 | 
				
			||||||
 | 
					        Self::new(input, ErrorKind::ExpectedChar(c))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<I: ErrorInput> NomContextError<I> for Error<I> {
 | 
				
			||||||
 | 
					    fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self {
 | 
				
			||||||
 | 
					        other.append_kind(input, ErrorKind::Context(ctx));
 | 
				
			||||||
 | 
					        other
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<I: ErrorInput, E: Into<ErrorKind>> FromExternalError<I, E> for Error<I> {
 | 
				
			||||||
 | 
					    fn from_external_error(input: I, _kind: NomErrorKind, e: E) -> Self {
 | 
				
			||||||
 | 
					        Self::new(input, e.into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<I> Error<I> {
 | 
				
			||||||
 | 
					    pub fn map_input<I2, F>(self, mut f: F) -> Error<I2>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        F: FnMut(I) -> I2,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Error {
 | 
				
			||||||
 | 
					            input: f(self.input),
 | 
				
			||||||
 | 
					            errors: self.errors,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> From<Error<&'a str>> for Error<&'a [u8]> {
 | 
				
			||||||
 | 
					    fn from(e: Error<&'a str>) -> Self {
 | 
				
			||||||
 | 
					        e.map_input(str::as_bytes)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<I: ToOwned + ?Sized> Error<&I> {
 | 
				
			||||||
 | 
					    pub fn into_owned(self) -> Error<I::Owned> {
 | 
				
			||||||
 | 
					        self.map_input(I::to_owned)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// adapted from https://fasterthanli.me/series/making-our-own-ping/part-9
 | 
				
			||||||
 | 
					impl<I: ErrorInputDisplay> fmt::Display for Error<I> {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        writeln!(f, "parsing error")?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let margin_left = 4;
 | 
				
			||||||
 | 
					        let margin_str = " ".repeat(margin_left);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // maximum amount of binary data we'll dump per line
 | 
				
			||||||
 | 
					        let maxlen = 60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // given a big slice, an offset, and a length, attempt to show
 | 
				
			||||||
 | 
					        // some data before, some data after, and highlight which part
 | 
				
			||||||
 | 
					        // we're talking about with tildes.
 | 
				
			||||||
 | 
					        let print_slice = |f: &mut fmt::Formatter, s: &I, range: &Range<usize>| -> fmt::Result {
 | 
				
			||||||
 | 
					            // decide which part of `s` we're going to show.
 | 
				
			||||||
 | 
					            let (range, offset, len) = {
 | 
				
			||||||
 | 
					                // see diagram further in article.
 | 
				
			||||||
 | 
					                let offset = range.start;
 | 
				
			||||||
 | 
					                let len = range.end - range.start;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let avail_after = s.len() - offset;
 | 
				
			||||||
 | 
					                let after = std::cmp::min(avail_after, maxlen / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let avail_before = offset;
 | 
				
			||||||
 | 
					                let before = std::cmp::min(avail_before, maxlen / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let new_start = offset - before;
 | 
				
			||||||
 | 
					                let new_end = offset + after;
 | 
				
			||||||
 | 
					                let new_offset = before;
 | 
				
			||||||
 | 
					                let new_len = std::cmp::min(new_end - new_start, len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                (new_start..new_end, new_offset, new_len)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            f.write_str(&margin_str)?;
 | 
				
			||||||
 | 
					            s.write_range(range.clone(), f)?;
 | 
				
			||||||
 | 
					            writeln!(f)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            write!(f, "{}", margin_str)?;
 | 
				
			||||||
 | 
					            for i in 0..range.len() {
 | 
				
			||||||
 | 
					                // each byte takes three characters, ie "FF "
 | 
				
			||||||
 | 
					                if i == offset + len - 1 {
 | 
				
			||||||
 | 
					                    // ..except the last one
 | 
				
			||||||
 | 
					                    f.write_str(I::pointer())?;
 | 
				
			||||||
 | 
					                } else if (offset..offset + len).contains(&i) {
 | 
				
			||||||
 | 
					                    f.write_str(I::line())?;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    f.write_str(I::space())?;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            writeln!(f)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Ok(())
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (range, kind) in self.errors.iter().rev() {
 | 
				
			||||||
 | 
					            writeln!(f, "{}", kind)?;
 | 
				
			||||||
 | 
					            if let Some(range) = range {
 | 
				
			||||||
 | 
					                print_slice(f, &self.input, range)?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// // TODO: should this really be the same as Display?
 | 
				
			||||||
 | 
					// impl<I: ErrorInputDisplay> fmt::Debug for Error<I> {
 | 
				
			||||||
 | 
					//     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					//         <Self as fmt::Display>::fmt(self, f)
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<I: ErrorInputDisplay> std::error::Error for Error<I> {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> ErrorInput for &'a [u8] {
 | 
				
			||||||
 | 
					    fn offset(&self, other: &Self) -> Option<usize> {
 | 
				
			||||||
 | 
					        (other.as_ptr() as usize)
 | 
				
			||||||
 | 
					            .checked_sub(self.as_ptr() as usize)
 | 
				
			||||||
 | 
					            .filter(|&offset| offset < self.len())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> ErrorInputDisplay for &'a [u8] {
 | 
				
			||||||
 | 
					    fn len(&self) -> usize {
 | 
				
			||||||
 | 
					        <[u8]>::len(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn write_range(&self, range: std::ops::Range<usize>, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        for b in &self[range] {
 | 
				
			||||||
 | 
					            write!(f, "{:02X} ", b)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn pointer() -> &'static str {
 | 
				
			||||||
 | 
					        "~~"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn line() -> &'static str {
 | 
				
			||||||
 | 
					        "~~~"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn space() -> &'static str {
 | 
				
			||||||
 | 
					        "   "
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ErrorInputDisplay for InputOwned {
 | 
				
			||||||
 | 
					    fn len(&self) -> usize {
 | 
				
			||||||
 | 
					        Vec::len(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn write_range(&self, range: std::ops::Range<usize>, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        self.as_slice().write_range(range, f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn pointer() -> &'static str {
 | 
				
			||||||
 | 
					        <&'static [u8] as ErrorInputDisplay>::pointer()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn line() -> &'static str {
 | 
				
			||||||
 | 
					        <&'static [u8] as ErrorInputDisplay>::line()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn space() -> &'static str {
 | 
				
			||||||
 | 
					        <&'static [u8] as ErrorInputDisplay>::space()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> ErrorInput for &'a str {
 | 
				
			||||||
 | 
					    fn offset(&self, other: &Self) -> Option<usize> {
 | 
				
			||||||
 | 
					        (other.as_ptr() as usize).checked_sub(self.as_ptr() as usize)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a> ErrorInputDisplay for &'a str {
 | 
				
			||||||
 | 
					    fn len(&self) -> usize {
 | 
				
			||||||
 | 
					        str::len(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn write_range(&self, range: std::ops::Range<usize>, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        for c in self[range].chars() {
 | 
				
			||||||
 | 
					            f.write_char(match c {
 | 
				
			||||||
 | 
					                '\r' => '␍',
 | 
				
			||||||
 | 
					                '\n' => '␊',
 | 
				
			||||||
 | 
					                c if c.is_control() => '␣',
 | 
				
			||||||
 | 
					                c => c,
 | 
				
			||||||
 | 
					            })?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn pointer() -> &'static str {
 | 
				
			||||||
 | 
					        "~"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn line() -> &'static str {
 | 
				
			||||||
 | 
					        "~"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn space() -> &'static str {
 | 
				
			||||||
 | 
					        " "
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ErrorInputDisplay for String {
 | 
				
			||||||
 | 
					    fn len(&self) -> usize {
 | 
				
			||||||
 | 
					        String::len(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn write_range(&self, range: std::ops::Range<usize>, f: &mut fmt::Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        self.as_str().write_range(range, f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn pointer() -> &'static str {
 | 
				
			||||||
 | 
					        <&'static str as ErrorInputDisplay>::pointer()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn line() -> &'static str {
 | 
				
			||||||
 | 
					        <&'static str as ErrorInputDisplay>::line()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn space() -> &'static str {
 | 
				
			||||||
 | 
					        <&'static str as ErrorInputDisplay>::space()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/parse/fields.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/parse/fields.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					use nom::{
 | 
				
			||||||
 | 
					    combinator::all_consuming,
 | 
				
			||||||
 | 
					    number::complete::{le_u32, le_u64, le_u8},
 | 
				
			||||||
 | 
					    Parser,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use num_enum::TryFromPrimitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::Input;
 | 
				
			||||||
 | 
					use crate::{Error, Result};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Copy, Debug, TryFromPrimitive, PartialEq)]
 | 
				
			||||||
 | 
					#[repr(u8)]
 | 
				
			||||||
 | 
					pub enum Op {
 | 
				
			||||||
 | 
					    MsgData = 0x02,
 | 
				
			||||||
 | 
					    FileHeader = 0x03,
 | 
				
			||||||
 | 
					    IndexData = 0x04,
 | 
				
			||||||
 | 
					    Chunk = 0x05,
 | 
				
			||||||
 | 
					    ChunkInfo = 0x06,
 | 
				
			||||||
 | 
					    Connection = 0x07,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Op {
 | 
				
			||||||
 | 
					    pub fn parse(input: Input) -> Result<Self> {
 | 
				
			||||||
 | 
					        let (_, op) = all_consuming(le_u8).parse(input)?;
 | 
				
			||||||
 | 
					        Op::try_from_primitive(op).map_err(Into::into)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn parse_u32(input: Input) -> Result<u32> {
 | 
				
			||||||
 | 
					    let (_, x) = all_consuming(le_u32).parse(input)?;
 | 
				
			||||||
 | 
					    Ok(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn parse_u64(input: Input) -> Result<u64> {
 | 
				
			||||||
 | 
					    let (_, x) = all_consuming(le_u64).parse(input)?;
 | 
				
			||||||
 | 
					    Ok(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn parse_string(input: Input) -> Result<String> {
 | 
				
			||||||
 | 
					    String::from_utf8(input.to_owned()).map_err(|_| Error::other("invalid utf8"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										111
									
								
								src/parse/header.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/parse/header.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					use core::fmt::{self, Write as _};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use nom::{
 | 
				
			||||||
 | 
					    bytes::complete::{tag, take_until},
 | 
				
			||||||
 | 
					    combinator::rest,
 | 
				
			||||||
 | 
					    error::context,
 | 
				
			||||||
 | 
					    multi::{length_value, many0},
 | 
				
			||||||
 | 
					    number::{complete::le_u32 as le_u32_complete, streaming::le_u32 as le_u32_streaming},
 | 
				
			||||||
 | 
					    sequence::separated_pair,
 | 
				
			||||||
 | 
					    Parser,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use smallvec::SmallVec;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{fields, IResult, Input, Op};
 | 
				
			||||||
 | 
					use crate::{Error, Result};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug)]
 | 
				
			||||||
 | 
					pub struct HeaderField {
 | 
				
			||||||
 | 
					    name: SmallVec<[u8; 16]>,
 | 
				
			||||||
 | 
					    value: SmallVec<[u8; 16]>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl HeaderField {
 | 
				
			||||||
 | 
					    pub fn name(&self) -> &[u8] {
 | 
				
			||||||
 | 
					        &self.name
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn value(&self) -> &[u8] {
 | 
				
			||||||
 | 
					        &self.value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn parse(input: Input) -> IResult<Self> {
 | 
				
			||||||
 | 
					        // Uses complete combinators because Header has length
 | 
				
			||||||
 | 
					        context(
 | 
				
			||||||
 | 
					            "Header Entry",
 | 
				
			||||||
 | 
					            length_value(
 | 
				
			||||||
 | 
					                le_u32_complete,
 | 
				
			||||||
 | 
					                separated_pair(take_until("="), tag("="), rest),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .map(|(name, value)| HeaderField {
 | 
				
			||||||
 | 
					            name: SmallVec::from_slice(name),
 | 
				
			||||||
 | 
					            value: SmallVec::from_slice(value),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .parse(input)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for HeaderField {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        let name = String::from_utf8_lossy(self.name.as_ref());
 | 
				
			||||||
 | 
					        write!(f, "{}=", name)?;
 | 
				
			||||||
 | 
					        for byte in self.value.iter().copied() {
 | 
				
			||||||
 | 
					            write!(f, "{:02x} ", byte)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug)]
 | 
				
			||||||
 | 
					pub struct Header(pub Vec<HeaderField>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Header {
 | 
				
			||||||
 | 
					    pub fn parse(input: Input) -> IResult<Self> {
 | 
				
			||||||
 | 
					        context(
 | 
				
			||||||
 | 
					            "Header",
 | 
				
			||||||
 | 
					            length_value(le_u32_streaming, many0(HeaderField::parse)),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .map(Header)
 | 
				
			||||||
 | 
					        .parse(input)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn find_field(&self, name: &[u8]) -> Result<&[u8]> {
 | 
				
			||||||
 | 
					        self.0
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .find(|field| field.name() == name)
 | 
				
			||||||
 | 
					            .map(|field| field.value())
 | 
				
			||||||
 | 
					            .ok_or_else(|| Error::MissingField(String::from_utf8_lossy(name).into_owned()))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read_op(&self) -> Result<Op> {
 | 
				
			||||||
 | 
					        self.find_field(b"op").and_then(fields::Op::parse)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read_u64(&self, field: &[u8]) -> Result<u64> {
 | 
				
			||||||
 | 
					        self.find_field(field).and_then(fields::parse_u64)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read_u32(&self, field: &[u8]) -> Result<u32> {
 | 
				
			||||||
 | 
					        self.find_field(field).and_then(fields::parse_u32)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn read_string(&self, field: &[u8]) -> Result<String> {
 | 
				
			||||||
 | 
					        self.find_field(field).and_then(fields::parse_string)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for Header {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        let mut first = true;
 | 
				
			||||||
 | 
					        for header in &self.0 {
 | 
				
			||||||
 | 
					            if first {
 | 
				
			||||||
 | 
					                first = false;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                f.write_char('\n')?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            header.fmt(f)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										45
									
								
								src/parse/version.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/parse/version.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					use core::fmt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use nom::{
 | 
				
			||||||
 | 
					    bytes::streaming::{is_a, tag},
 | 
				
			||||||
 | 
					    error::context,
 | 
				
			||||||
 | 
					    sequence::tuple,
 | 
				
			||||||
 | 
					    Parser,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{IResult, Input};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, PartialEq)]
 | 
				
			||||||
 | 
					pub struct Version {
 | 
				
			||||||
 | 
					    pub major: u16,
 | 
				
			||||||
 | 
					    pub minor: u16,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for Version {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        write!(f, "{}.{}", self.major, self.minor)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn decimal_u16(input: Input) -> IResult<u16> {
 | 
				
			||||||
 | 
					    is_a("0123456789")
 | 
				
			||||||
 | 
					        .map(|a| String::from_utf8_lossy(a).parse::<u16>().unwrap())
 | 
				
			||||||
 | 
					        .parse(input)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Version {
 | 
				
			||||||
 | 
					    pub fn parse(input: Input) -> IResult<Self> {
 | 
				
			||||||
 | 
					        context(
 | 
				
			||||||
 | 
					            "Version Header",
 | 
				
			||||||
 | 
					            tuple((
 | 
				
			||||||
 | 
					                tag(b"#ROSBAG V"),
 | 
				
			||||||
 | 
					                decimal_u16,
 | 
				
			||||||
 | 
					                tag(b"."),
 | 
				
			||||||
 | 
					                decimal_u16,
 | 
				
			||||||
 | 
					                tag(b"\n"),
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					            .map(|(_, major, _, minor, _)| Version { major, minor }),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .parse(input)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										140
									
								
								src/reader.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/reader.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					use std::io::{self, SeekFrom};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bytes::{Buf, BytesMut};
 | 
				
			||||||
 | 
					use nom::number::streaming::le_u32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::error::{Error, Result};
 | 
				
			||||||
 | 
					use crate::index::{ChunkInfo, ConnInfo};
 | 
				
			||||||
 | 
					use crate::parse::{self, Header, Op, Version};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const READ_SIZE: usize = 4096;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub trait BagReader {
 | 
				
			||||||
 | 
					    fn read_parser<'a, O: 'a, P>(&'a mut self, parser: P) -> Result<O>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        P: nom::Parser<&'a [u8], O, parse::Error<&'a [u8]>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn seek(&mut self, pos: SeekFrom) -> Result<()>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_version(&mut self) -> Result<Version> {
 | 
				
			||||||
 | 
					        self.read_parser(Version::parse)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_header(&mut self) -> Result<Header> {
 | 
				
			||||||
 | 
					        self.read_parser(Header::parse)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_header_op(&mut self, op: Op) -> Result<Header> {
 | 
				
			||||||
 | 
					        let header = self.read_header()?;
 | 
				
			||||||
 | 
					        if header.read_op()? != op {
 | 
				
			||||||
 | 
					            return Err(Error::other(format!("Expected {:?} op", op)));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(header)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_data_length(&mut self) -> Result<u32> {
 | 
				
			||||||
 | 
					        self.read_parser(le_u32)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn skip_data(&mut self) -> Result<()> {
 | 
				
			||||||
 | 
					        let data_length = self.read_data_length()?;
 | 
				
			||||||
 | 
					        self.seek(io::SeekFrom::Current(data_length as i64))?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_conn_info(&mut self) -> Result<ConnInfo> {
 | 
				
			||||||
 | 
					        let record_header = self.read_header_op(Op::Connection)?;
 | 
				
			||||||
 | 
					        let conn_header = self.read_header()?;
 | 
				
			||||||
 | 
					        ConnInfo::from_headers(record_header, conn_header)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_chunk_info(&mut self) -> Result<ChunkInfo> {
 | 
				
			||||||
 | 
					        let header = self.read_header_op(Op::ChunkInfo)?;
 | 
				
			||||||
 | 
					        // TODO: read connection message counts
 | 
				
			||||||
 | 
					        self.skip_data()?;
 | 
				
			||||||
 | 
					        ChunkInfo::from_header(header)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct IoReader<R> {
 | 
				
			||||||
 | 
					    read: R,
 | 
				
			||||||
 | 
					    buffer: BytesMut,
 | 
				
			||||||
 | 
					    consumed: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<R: io::Read + io::Seek> IoReader<R> {
 | 
				
			||||||
 | 
					    pub fn new(read: R) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            read,
 | 
				
			||||||
 | 
					            buffer: BytesMut::with_capacity(READ_SIZE),
 | 
				
			||||||
 | 
					            consumed: 0,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_more(&mut self, n: usize) -> Result<()> {
 | 
				
			||||||
 | 
					        if n == 0 {
 | 
				
			||||||
 | 
					            return Ok(());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let old_size = self.buffer.len();
 | 
				
			||||||
 | 
					        self.buffer.resize(old_size + n, 0);
 | 
				
			||||||
 | 
					        let read = self.read.read(&mut self.buffer[old_size..])?;
 | 
				
			||||||
 | 
					        self.buffer.truncate(old_size + read);
 | 
				
			||||||
 | 
					        if read == 0 {
 | 
				
			||||||
 | 
					            return Err(Error::Eof);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<R: io::Read + io::Seek> BagReader for IoReader<R> {
 | 
				
			||||||
 | 
					    fn read_parser<'a, O: 'a, P>(&'a mut self, mut parser: P) -> Result<O>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        P: nom::Parser<&'a [u8], O, parse::Error<&'a [u8]>>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        self.buffer.advance(self.consumed);
 | 
				
			||||||
 | 
					        self.consumed = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let this = self as *mut Self;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            match parser.parse(&self.buffer) {
 | 
				
			||||||
 | 
					                Ok((rest, output)) => {
 | 
				
			||||||
 | 
					                    self.consumed += self.buffer.len() - rest.len();
 | 
				
			||||||
 | 
					                    return Ok(output);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Err(nom::Err::Incomplete(needed)) => {
 | 
				
			||||||
 | 
					                    let needed = match needed {
 | 
				
			||||||
 | 
					                        nom::Needed::Unknown => 0,
 | 
				
			||||||
 | 
					                        nom::Needed::Size(n) => n.get(),
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    // Safety: this.buffer is only borrowed in the Ok case above, which
 | 
				
			||||||
 | 
					                    // immediately returns.
 | 
				
			||||||
 | 
					                    unsafe { &mut *this }.read_more(needed.max(READ_SIZE))?;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Err(nom::Err::Error(e) | nom::Err::Failure(e)) => {
 | 
				
			||||||
 | 
					                    return Err(e.into());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn seek(&mut self, mut pos: io::SeekFrom) -> Result<()> {
 | 
				
			||||||
 | 
					        if let io::SeekFrom::Current(pos) = &mut pos {
 | 
				
			||||||
 | 
					            // If seeking relative to current position, subtract data
 | 
				
			||||||
 | 
					            // read from the file but not yet consumed.
 | 
				
			||||||
 | 
					            let remaining = (self.buffer.len() - self.consumed) as i64;
 | 
				
			||||||
 | 
					            let new_pos = *pos - remaining;
 | 
				
			||||||
 | 
					            if *pos >= 0 && new_pos < 0 {
 | 
				
			||||||
 | 
					                // The new position is within the already read data, just consume more data
 | 
				
			||||||
 | 
					                self.consumed += *pos as usize;
 | 
				
			||||||
 | 
					                return Ok(());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            *pos = new_pos;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.buffer.clear();
 | 
				
			||||||
 | 
					        self.consumed = 0;
 | 
				
			||||||
 | 
					        self.read.seek(pos)?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user