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