Added deframer support
ci/woodpecker/tag/woodpecker Pipeline was successful Details

main 0.3.0
Joey Hines 2024-08-17 15:58:25 -06:00
parent a125c08032
commit 38ed49fbe3
No known key found for this signature in database
GPG Key ID: 995E531F7A569DDB
13 changed files with 529 additions and 322 deletions

30
Cargo.lock generated
View File

@ -66,9 +66,9 @@ dependencies = [
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.9.1" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
dependencies = [ dependencies = [
"memchr", "memchr",
"serde", "serde",
@ -159,16 +159,18 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "formaty" name = "formaty"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"byteorder", "byteorder",
"hex", "hex",
"num-bigint", "num-bigint",
"platform-dirs", "platform-dirs",
"regex",
"rust-embed", "rust-embed",
"serde", "serde",
"structopt", "structopt",
"thiserror",
"toml", "toml",
] ]
@ -383,6 +385,18 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.6" version = "0.4.6"
@ -553,22 +567,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.31" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.75", "syn 2.0.60",
] ]
[[package]] [[package]]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "formaty" name = "formaty"
version = "0.2.0" version = "0.3.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -14,6 +14,8 @@ num-bigint = "0.4.4"
bitvec = "1.0.1" bitvec = "1.0.1"
platform-dirs = "0.3.0" platform-dirs = "0.3.0"
hex = "0.4.3" hex = "0.4.3"
thiserror = "1.0.63"
regex = "1.10.6"
[dependencies.rust-embed] [dependencies.rust-embed]
version = "8.3.0" version = "8.3.0"

View File

@ -5,14 +5,15 @@ A simple configurable binary data parser. Data structures are described using TO
All formats in [formats](./formats) are included in the `formaty` binary. See [formats.md](./formats/formats.md) for All formats in [formats](./formats) are included in the `formaty` binary. See [formats.md](./formats/formats.md) for
more info. more info.
### Configuration ## Configuration
#### Format ### Format
A format is a collection of fields that describe the structure of data, so it can be parsed A format is a collection of fields that describe the structure of data, so it can be parsed
Members: Members:
* `name` - name of the format, used as the argument when selecting the format to parse the data as * `name` - name of the format, used as the argument when selecting the format to parse the data as
* `bit_flip` - should individual bytes have their bits flipped within, individual fields can override this * `bit_flip` - should individual bytes have their bits flipped within, individual fields can override this
* `deframer` - optional list of deframers to use before trying to parse the data
* `fields` - 1 or more fields to describe the structure of the data * `fields` - 1 or more fields to describe the structure of the data
#### Field #### Field
@ -46,6 +47,15 @@ Members:
* `String`: view fields as a string field * `String`: view fields as a string field
* `Default`: use the default printer for the source type * `Default`: use the default printer for the source type
### Deframer
A deframer configures how the data should be unpacked before being parsed. A deframer is made up of one or more
DeframeOperations. Deframers can be added to `Format` configs or be applied by passing in the `--deframer` option.
#### Deframe Operation
* `StartSeq` - a start sequence of bytes to check for and remove before parsing
* `StopSeq` - a stop sequence of bytes to check for and remove before parsing
* `Replace` - a simple find and replace with Regex support. All matches of `find` are replaced with `replace`
#### Example Config #### Example Config
[Example](./formats/example.toml) [Example](./formats/example.toml)
@ -68,11 +78,11 @@ Data: [1, 2, 3, 4, 5]
## Help ## Help
``` ```
Formaty 0.2.0 Formaty 0.3.0
Arbitrary Binary Data Formatting Arbitrary Binary Data Formatting
USAGE: USAGE:
formaty [FLAGS] [OPTIONS] <format> [data]... formaty [FLAGS] [OPTIONS] <format> [--] [data]...
FLAGS: FLAGS:
-h, --help Prints help information -h, --help Prints help information
@ -82,6 +92,7 @@ FLAGS:
OPTIONS: OPTIONS:
-b, --base <base> Base of the input values -b, --base <base> Base of the input values
-c, --config <config> Path to the format config [env: FORMATY_CONFIG=] -c, --config <config> Path to the format config [env: FORMATY_CONFIG=]
-d, --deframer <deframer>... Additional deframers to apply to the data before trying to parse it
-g, --global_config <global-config> Path to the global config directory [env: FORMATY_GLOBAL_CONFIG=] -g, --global_config <global-config> Path to the global config directory [env: FORMATY_GLOBAL_CONFIG=]
-f, --file <input-file> Input data from file -f, --file <input-file> Input data from file
-i, --input_type <input-type> Input data type [default: array] -i, --input_type <input-type> Input data type [default: array]

View File

@ -4,5 +4,4 @@ at run time by using the `--config` flag.
## Formats Included ## Formats Included
* [CCSDS](ccsds.toml) - Standard CCSDS command/tlm packets * [CCSDS](ccsds.toml) - Standard CCSDS command/tlm packets
* `ccsds`: CCSDS packet with primary header + raw payload * `ccsds`: CCSDS packet with primary header + raw payload
* `ccsds_sec`: CCSDS packet with primary header + secondary header + raw payload

23
formats/slip.toml 100644
View File

@ -0,0 +1,23 @@
# Serial Line Internet Protocol Deframmer
# Ref: https://datatracker.ietf.org/doc/html/rfc1055
[[deframers]]
name = "slip"
[[deframers.operations]]
operation = "StartSeq"
start_seq = [0xC0]
[[deframers.operations]]
operation = "StopSeq"
stop_seq = [0xC0]
[[deframers.operations]]
operation = "Replace"
find = "\\xDB\\xDC"
replace = [0xC0]
[[deframers.operations]]
operation = "Replace"
find = "\\xDB\\xDD"
replace = [0xDB]

View File

@ -1,20 +1,13 @@
use crate::formatter::format::ByteOrderOperations; use crate::formatter::bytes_operations::ByteOrderOperations;
use bitvec::prelude::*; use bitvec::prelude::*;
use std::fmt::{Display, Formatter}; use thiserror::Error;
#[derive(Clone, Debug)] #[derive(Clone, Debug, Error)]
pub enum ByteStreamError { pub enum ByteStreamError {
#[error("Requested value out of range")]
OutOfRange, OutOfRange,
} }
impl Display for ByteStreamError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ByteStreamError::OutOfRange => write!(f, "Requested values out of range"),
}
}
}
pub const fn bit_mask(mask: u8) -> u8 { pub const fn bit_mask(mask: u8) -> u8 {
match mask { match mask {
0 => 0x00, 0 => 0x00,

148
src/deframer/mod.rs 100644
View File

@ -0,0 +1,148 @@
use regex::bytes::RegexBuilder;
use serde::Deserialize;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DeframeError {
#[error("Start sequence not found")]
StartSeqInvalid,
#[error("Stop sequence not found")]
StopSeqInvalid,
#[error("Regex Error")]
RegexError(#[from] regex::Error),
}
#[derive(Debug, Deserialize, Clone)]
#[serde(tag = "operation")]
pub enum DeframeOperation {
StartSeq { start_seq: Vec<u8> },
StopSeq { stop_seq: Vec<u8> },
Replace { find: String, replace: Vec<u8> },
}
impl DeframeOperation {
pub fn do_operation(&self, mut data: Vec<u8>) -> Result<Vec<u8>, DeframeError> {
Ok(match self {
DeframeOperation::StartSeq { start_seq } => {
if data.starts_with(start_seq) {
data.drain(0..start_seq.len());
data
} else {
return Err(DeframeError::StartSeqInvalid);
}
}
DeframeOperation::StopSeq { stop_seq } => {
if data.ends_with(stop_seq) {
data.drain(data.len() - stop_seq.len()..data.len());
data
} else {
return Err(DeframeError::StopSeqInvalid);
}
}
DeframeOperation::Replace { find, replace } => {
let reg = RegexBuilder::new(find).unicode(false).build()?;
reg.replace_all(&data, replace).to_vec()
}
})
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct Deframer {
pub name: String,
pub operations: Vec<DeframeOperation>,
}
impl Deframer {
pub fn deframe(&self, mut data: Vec<u8>) -> Vec<u8> {
for operation in &self.operations {
data = operation.do_operation(data).unwrap();
}
data
}
}
#[cfg(test)]
mod test {
use crate::deframer::{DeframeOperation, Deframer};
use crate::formatter::format_config::FormatConfig;
#[test]
fn test_start_seq() {
let op = DeframeOperation::StartSeq {
start_seq: vec![0x11, 0x00],
};
let data = vec![0x11, 0x00, 0x55, 0x55];
let output = op.do_operation(data).unwrap();
assert_eq!(output, vec![0x55, 0x55]);
}
#[test]
fn test_stop_seq() {
let op = DeframeOperation::StopSeq {
stop_seq: vec![0x11, 0x00],
};
let data = vec![0x55, 0x55, 0x11, 0x00];
let output = op.do_operation(data).unwrap();
assert_eq!(output, vec![0x55, 0x55]);
}
#[test]
fn test_replace_seq() {
let op = DeframeOperation::Replace {
find: "\x55\x11".to_string(),
replace: vec![0x11],
};
let data = vec![0x55, 0x11, 0x55, 0x11, 0x00, 0x55];
let output = op.do_operation(data).unwrap();
assert_eq!(output, vec![0x11, 0x11, 0x00, 0x55]);
}
#[test]
fn test_deframer() {
let deframer = Deframer {
name: "Test SLIP Deframmer".to_string(),
operations: vec![
DeframeOperation::StartSeq {
start_seq: vec![0xc0],
},
DeframeOperation::StopSeq {
stop_seq: vec![0xc0],
},
DeframeOperation::Replace {
find: r"\xDB\xDC".to_string(),
replace: vec![0xC0],
},
DeframeOperation::Replace {
find: r"\xDB\xDD".to_string(),
replace: vec![0xDB],
},
],
};
let data = vec![0xC0, 0xDB, 0xDC, 0xDB, 0xDD, 0xC0];
let output = deframer.deframe(data);
assert_eq!(output, vec![0xc0, 0xDB]);
}
#[test]
fn deserialize_deframer() {
let config = FormatConfig::new(&None, &None).unwrap();
let slip = config.get_deframer("slip").unwrap();
assert_eq!(slip.name, "slip");
assert_eq!(slip.operations.len(), 4);
}
}

View File

@ -0,0 +1,97 @@
use byteorder::{BigEndian, ByteOrder, LittleEndian};
use num_bigint::{BigInt, BigUint};
#[allow(dead_code)]
pub trait ByteOrderOperations: ByteOrder {
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8>;
fn big_int(buf: &[u8], trim: usize) -> BigInt;
fn big_uint(buf: &[u8], trim: usize) -> BigUint;
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8>;
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8>;
fn pad_bytes(buf: &mut Vec<u8>, size: usize);
fn flip() -> bool;
}
impl ByteOrderOperations for BigEndian {
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
buf.first_mut()
}
fn big_int(buf: &[u8], trim: usize) -> BigInt {
let mut buf = buf.to_vec();
let byte = buf.first_mut().unwrap();
*byte = (*byte << trim) >> trim;
BigInt::from_signed_bytes_be(&buf)
}
fn big_uint(buf: &[u8], trim: usize) -> BigUint {
let mut buf = buf.to_vec();
let byte = buf.first_mut().unwrap();
*byte = (*byte << trim) >> trim;
BigUint::from_bytes_be(&buf)
}
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8> {
big_int.to_bytes_be()
}
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
big_int.to_signed_bytes_be()
}
fn pad_bytes(buf: &mut Vec<u8>, size: usize) {
if size <= buf.len() {
return;
}
let pad_to = size - buf.len();
buf.splice(0..0, vec![0_u8; pad_to].iter().cloned());
}
fn flip() -> bool {
true
}
}
impl ByteOrderOperations for LittleEndian {
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
buf.last_mut()
}
fn big_int(buf: &[u8], trim: usize) -> BigInt {
BigInt::from_signed_bytes_le(buf) >> trim
}
fn big_uint(buf: &[u8], trim: usize) -> BigUint {
BigUint::from_bytes_le(buf) >> trim
}
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8> {
big_int.to_bytes_le()
}
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
big_int.to_signed_bytes_le()
}
fn pad_bytes(buf: &mut Vec<u8>, size: usize) {
if size <= buf.len() {
return;
}
let pad_to = size - buf.len();
let mut pad = vec![0_u8; pad_to];
buf.append(&mut pad);
}
fn flip() -> bool {
false
}
}

View File

@ -1,142 +1,28 @@
use std::fmt::{Display, Formatter, Write}; use std::fmt::Write;
use std::io::Cursor; use std::io::Cursor;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt};
use num_bigint::{BigInt, BigUint};
use serde::Deserialize;
use crate::byte_stream::{bit_mask, ByteStream, ByteStreamError}; use crate::byte_stream::{bit_mask, ByteStream, ByteStreamError};
use crate::formatter::bytes_operations::ByteOrderOperations;
use crate::formatter::format_config::FormatConfig;
use crate::formatter::printers::PrintType; use crate::formatter::printers::PrintType;
use crate::FormatConfig; use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
use serde::Deserialize;
use thiserror::Error;
#[allow(dead_code)] #[derive(Debug, Clone, Error)]
pub trait ByteOrderOperations: ByteOrder {
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8>;
fn big_int(buf: &[u8], trim: usize) -> BigInt;
fn big_uint(buf: &[u8], trim: usize) -> BigUint;
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8>;
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8>;
fn pad_bytes(buf: &mut Vec<u8>, size: usize);
fn flip() -> bool;
}
impl ByteOrderOperations for BigEndian {
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
buf.first_mut()
}
fn big_int(buf: &[u8], trim: usize) -> BigInt {
let mut buf = buf.to_vec();
let byte = buf.first_mut().unwrap();
*byte = (*byte << trim) >> trim;
BigInt::from_signed_bytes_be(&buf)
}
fn big_uint(buf: &[u8], trim: usize) -> BigUint {
let mut buf = buf.to_vec();
let byte = buf.first_mut().unwrap();
*byte = (*byte << trim) >> trim;
BigUint::from_bytes_be(&buf)
}
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8> {
big_int.to_bytes_be()
}
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
big_int.to_signed_bytes_be()
}
fn pad_bytes(buf: &mut Vec<u8>, size: usize) {
if size <= buf.len() {
return;
}
let pad_to = size - buf.len();
buf.splice(0..0, vec![0_u8; pad_to].iter().cloned());
}
fn flip() -> bool {
true
}
}
impl ByteOrderOperations for LittleEndian {
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
buf.last_mut()
}
fn big_int(buf: &[u8], trim: usize) -> BigInt {
BigInt::from_signed_bytes_le(buf) >> trim
}
fn big_uint(buf: &[u8], trim: usize) -> BigUint {
BigUint::from_bytes_le(buf) >> trim
}
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8> {
big_int.to_bytes_le()
}
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
big_int.to_signed_bytes_le()
}
fn pad_bytes(buf: &mut Vec<u8>, size: usize) {
if size <= buf.len() {
return;
}
let pad_to = size - buf.len();
let mut pad = vec![0_u8; pad_to];
buf.append(&mut pad);
}
fn flip() -> bool {
false
}
}
#[derive(Debug, Clone)]
pub enum FormatError { pub enum FormatError {
ByteSteamError(ByteStreamError), #[error("Byte Stream Error")]
ByteSteamError(#[from] ByteStreamError),
#[allow(dead_code)] #[allow(dead_code)]
#[error("Not Supported")]
NotSupported, NotSupported,
StringParseError(FromUtf8Error), #[error("String Parse Error")]
StringParseError(#[from] FromUtf8Error),
#[error("Format Not Found")]
FormatNotFound(String), FormatNotFound(String),
} #[error("Deframmer Not Found")]
DeframmerNotFound(String),
impl From<ByteStreamError> for FormatError {
fn from(e: ByteStreamError) -> Self {
FormatError::ByteSteamError(e)
}
}
impl From<FromUtf8Error> for FormatError {
fn from(e: FromUtf8Error) -> Self {
FormatError::StringParseError(e)
}
}
impl Display for FormatError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FormatError::ByteSteamError(e) => writeln!(f, "Byte stream error: {}", e),
FormatError::NotSupported => write!(f, "Field type not supported"),
FormatError::StringParseError(e) => write!(f, "String parse error: {}", e),
FormatError::FormatNotFound(format) => {
write!(f, "Unable to find sub-format: {}", format)
}
}
}
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
@ -393,6 +279,9 @@ pub struct Format {
pub name: String, pub name: String,
/// Flip bits /// Flip bits
pub bit_flip: bool, pub bit_flip: bool,
/// Deframmers to apply before parsing,
#[serde(default)]
pub deframers: Vec<String>,
/// Elements of the format /// Elements of the format
pub fields: Vec<Field>, pub fields: Vec<Field>,
} }
@ -429,7 +318,29 @@ impl Format {
Ok((format_str, bit_ndx)) Ok((format_str, bit_ndx))
} }
pub fn format_data(&self, data: &[u8], config: &FormatConfig) -> Result<String, FormatError> { pub fn format_data(
&self,
data: &[u8],
config: &FormatConfig,
additional_deframers: Option<Vec<String>>,
) -> Result<String, FormatError> {
let mut data = data.to_vec();
let deframers = if let Some(additional_deframers) = additional_deframers {
additional_deframers
.into_iter()
.chain(self.deframers.clone())
.collect()
} else {
self.deframers.clone()
};
for deframer_name in deframers {
let deframer = config.get_deframer(&deframer_name)?;
data = deframer.deframe(data)
}
let mut byte_stream = ByteStream::from(data); let mut byte_stream = ByteStream::from(data);
let (format_str, _) = self.format_byte_stream(&mut byte_stream, 0, config)?; let (format_str, _) = self.format_byte_stream(&mut byte_stream, 0, config)?;
@ -442,7 +353,8 @@ impl Format {
mod tests { mod tests {
use crate::byte_stream::ByteStream; use crate::byte_stream::ByteStream;
use crate::formatter::format::{Endianness, Field, FieldType}; use crate::formatter::format::{Endianness, Field, FieldType};
use crate::{Format, FormatConfig}; use crate::formatter::format_config::FormatConfig;
use crate::Format;
#[test] #[test]
fn test_format_int_4_bits() { fn test_format_int_4_bits() {
@ -725,9 +637,11 @@ mod tests {
}; };
let config = FormatConfig { let config = FormatConfig {
deframers: vec![],
formats: vec![Format { formats: vec![Format {
name: "sub_format".to_string(), name: "sub_format".to_string(),
bit_flip: false, bit_flip: false,
deframers: vec![],
fields: vec![Field { fields: vec![Field {
name: "test".to_string(), name: "test".to_string(),
field_type: FieldType::String { field_type: FieldType::String {

View File

@ -0,0 +1,132 @@
use crate::deframer::Deframer;
use crate::formatter::format::{Format, FormatError};
use crate::formatter::FormatConfigError;
use platform_dirs::AppDirs;
use rust_embed::RustEmbed;
use serde::Deserialize;
use std::fs::{read_dir, File};
use std::io::Read;
use std::path::PathBuf;
use std::{fs, str};
#[derive(RustEmbed)]
#[folder = "formats/"]
#[include = "*.toml"]
struct BuiltInFormats;
#[derive(Debug, Deserialize, Clone, Default)]
pub struct FormatConfig {
#[serde(default)]
pub deframers: Vec<Deframer>,
#[serde(default)]
pub formats: Vec<Format>,
}
impl FormatConfig {
fn parse_config_file(
src_file_name: &str,
config_data: &str,
) -> Result<Self, FormatConfigError> {
toml::from_str(config_data).map_err(|e| FormatConfigError::TomlError {
file_name: src_file_name.to_string(),
err: e,
})
}
fn get_built_in_config(&mut self) -> Result<(), FormatConfigError> {
for format_file_path in BuiltInFormats::iter() {
if format_file_path.ends_with("md") {
continue;
}
let format_file = BuiltInFormats::get(&format_file_path).unwrap();
let config_str = str::from_utf8(&format_file.data).unwrap();
let mut built_in: FormatConfig =
Self::parse_config_file(&format_file_path, config_str)?;
self.formats.append(&mut built_in.formats);
self.deframers.append(&mut built_in.deframers)
}
Ok(())
}
fn get_file_config(&mut self, config_path: &Option<PathBuf>) -> Result<(), FormatConfigError> {
if let Some(config_path) = config_path {
let mut config_file = File::open(config_path)?;
let mut config_data = String::new();
config_file.read_to_string(&mut config_data)?;
let mut arg_config =
Self::parse_config_file(config_path.to_str().unwrap(), &config_data)?;
self.formats.append(&mut arg_config.formats);
self.deframers.append(&mut arg_config.deframers);
}
Ok(())
}
fn parse_directory(
&mut self,
directory_path: &Option<PathBuf>,
) -> Result<(), FormatConfigError> {
if let Some(directory_path) = directory_path {
for path in read_dir(directory_path)? {
let file_name = path?.file_name();
let config_file_path = directory_path.join(file_name);
self.get_file_config(&Some(config_file_path))?;
}
}
Ok(())
}
pub fn new(
config_path: &Option<PathBuf>,
global_config_path: &Option<PathBuf>,
) -> Result<Self, FormatConfigError> {
let mut config = FormatConfig::default();
config.get_built_in_config()?;
config.get_file_config(config_path)?;
let global_dir = match global_config_path {
Some(g) => g.clone(),
None => {
let app_dirs = AppDirs::new(Some("formaty"), true).unwrap();
if !app_dirs.config_dir.exists() {
fs::create_dir(&app_dirs.config_dir)?;
}
app_dirs.config_dir
}
};
config.parse_directory(&Some(global_dir))?;
Ok(config)
}
pub fn get_format(&self, name: &str) -> Result<Format, FormatError> {
Ok(self
.formats
.iter()
.find(|f| f.name.as_str() == name)
.ok_or_else(|| FormatError::FormatNotFound(name.to_string()))?
.clone())
}
pub fn get_deframer(&self, name: &str) -> Result<Deframer, FormatError> {
Ok(self
.deframers
.iter()
.find(|f| f.name.as_str() == name)
.ok_or_else(|| FormatError::DeframmerNotFound(name.to_string()))?
.clone())
}
}

View File

@ -1,171 +1,33 @@
pub mod bytes_operations;
pub mod format; pub mod format;
pub mod format_config;
mod printers; mod printers;
use crate::formatter::format::Format; use crate::formatter::format::FormatError;
use platform_dirs::AppDirs; use std::str;
use rust_embed::RustEmbed; use thiserror::Error;
use serde::Deserialize;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::fs::{read_dir, File};
use std::io::Read;
use std::path::PathBuf;
use std::{fs, str};
#[derive(Debug)] #[derive(Debug, Error)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
pub enum FormatConfigError { pub enum FormatConfigError {
IOError(std::io::Error), #[error("IO error")]
IOError(#[from] std::io::Error),
#[error("TOML Format Error")]
TomlError { TomlError {
file_name: String, file_name: String,
err: toml::de::Error, err: toml::de::Error,
}, },
Utf8Error(str::Utf8Error), #[error("UTF-8 String Decode Error")]
FormatNotFound(String), Utf8Error(#[from] str::Utf8Error),
} #[error("Format Error")]
FormatError(#[from] FormatError),
impl Error for FormatConfigError {}
impl From<std::io::Error> for FormatConfigError {
fn from(e: std::io::Error) -> Self {
Self::IOError(e)
}
}
impl From<str::Utf8Error> for FormatConfigError {
fn from(e: str::Utf8Error) -> Self {
Self::Utf8Error(e)
}
}
impl Display for FormatConfigError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let err_msg = match self {
FormatConfigError::IOError(e) => e.to_string(),
FormatConfigError::TomlError { file_name, err } => {
format!("Error parsing {}: {}", file_name, err)
}
FormatConfigError::Utf8Error(e) => e.to_string(),
FormatConfigError::FormatNotFound(s) => format!("{} was not found.", s),
};
write!(f, "Format Config Error: {}", err_msg)
}
}
#[derive(RustEmbed)]
#[folder = "formats/"]
#[include = "*.toml"]
struct BuiltInFormats;
#[derive(Debug, Deserialize, Clone, Default)]
pub struct FormatConfig {
pub formats: Vec<Format>,
}
impl FormatConfig {
fn parse_config_file(
src_file_name: &str,
config_data: &str,
) -> Result<Self, FormatConfigError> {
toml::from_str(config_data).map_err(|e| FormatConfigError::TomlError {
file_name: src_file_name.to_string(),
err: e,
})
}
fn get_built_in_config(&mut self) -> Result<(), FormatConfigError> {
for format_file_path in BuiltInFormats::iter() {
if format_file_path.ends_with("md") {
continue;
}
let format_file = BuiltInFormats::get(&format_file_path).unwrap();
let config_str = str::from_utf8(&format_file.data).unwrap();
let mut built_in: FormatConfig =
Self::parse_config_file(&format_file_path, config_str)?;
self.formats.append(&mut built_in.formats);
}
Ok(())
}
fn get_file_config(&mut self, config_path: &Option<PathBuf>) -> Result<(), FormatConfigError> {
if let Some(config_path) = config_path {
let mut config_file = File::open(config_path)?;
let mut config_data = String::new();
config_file.read_to_string(&mut config_data)?;
let mut arg_config =
Self::parse_config_file(config_path.to_str().unwrap(), &config_data)?;
self.formats.append(&mut arg_config.formats);
}
Ok(())
}
fn parse_directory(
&mut self,
directory_path: &Option<PathBuf>,
) -> Result<(), FormatConfigError> {
if let Some(directory_path) = directory_path {
for path in read_dir(directory_path)? {
let file_name = path?.file_name();
let config_file_path = directory_path.join(file_name);
self.get_file_config(&Some(config_file_path))?;
}
}
Ok(())
}
pub fn new(
config_path: &Option<PathBuf>,
global_config_path: &Option<PathBuf>,
) -> Result<Self, FormatConfigError> {
let mut config = FormatConfig::default();
config.get_built_in_config()?;
config.get_file_config(config_path)?;
let global_dir = match global_config_path {
Some(g) => g.clone(),
None => {
let app_dirs = AppDirs::new(Some("formaty"), true).unwrap();
if !app_dirs.config_dir.exists() {
fs::create_dir(&app_dirs.config_dir)?;
}
app_dirs.config_dir
}
};
config.parse_directory(&Some(global_dir))?;
Ok(config)
}
pub fn get_format(&self, name: &str) -> Result<Format, FormatConfigError> {
Ok(self
.formats
.iter()
.find(|f| f.name.as_str() == name)
.ok_or_else(|| FormatConfigError::FormatNotFound(name.to_string()))?
.clone())
}
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::formatter::format::Format; use crate::formatter::format::Format;
use crate::formatter::format_config::FormatConfig;
use crate::formatter::printers::print_bytes_as_array; use crate::formatter::printers::print_bytes_as_array;
use crate::formatter::FormatConfig;
#[derive(PartialOrd, PartialEq, Debug, Clone)] #[derive(PartialOrd, PartialEq, Debug, Clone)]
struct CCSDSPacket { struct CCSDSPacket {
@ -286,7 +148,9 @@ mod test {
let format = get_ccsds_format(); let format = get_ccsds_format();
let data = ccsds_packet.to_bytes(); let data = ccsds_packet.to_bytes();
let output = format.format_data(&data, &FormatConfig::default()).unwrap(); let output = format
.format_data(&data, &FormatConfig::default(), None)
.unwrap();
assert_eq!(ccsds_packet.print(), output) assert_eq!(ccsds_packet.print(), output)
} }
@ -308,7 +172,9 @@ mod test {
}; };
let data = ccsds_packet.to_bytes(); let data = ccsds_packet.to_bytes();
let output = format.format_data(&data, &FormatConfig::default()).unwrap(); let output = format
.format_data(&data, &FormatConfig::default(), None)
.unwrap();
assert_eq!(ccsds_packet.print(), output) assert_eq!(ccsds_packet.print(), output)
} }

View File

@ -2,7 +2,7 @@ use byteorder::WriteBytesExt;
use num_bigint::{BigInt, BigUint}; use num_bigint::{BigInt, BigUint};
use serde::Deserialize; use serde::Deserialize;
use crate::formatter::format::ByteOrderOperations; use crate::formatter::bytes_operations::ByteOrderOperations;
pub fn print_bytes_as_array(data: &[u8]) -> String { pub fn print_bytes_as_array(data: &[u8]) -> String {
format!("{:?}", data) format!("{:?}", data)

View File

@ -1,11 +1,12 @@
use crate::error::FormatyError; use crate::error::FormatyError;
use crate::formatter::format::Format; use crate::formatter::format::Format;
use crate::parser::InputTypes; use crate::parser::InputTypes;
use formatter::FormatConfig; use formatter::format_config::FormatConfig;
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
mod byte_stream; mod byte_stream;
mod deframer;
mod error; mod error;
mod formatter; mod formatter;
mod parser; mod parser;
@ -48,6 +49,13 @@ pub struct Args {
#[structopt(help = "Input data from stdin", short = "s", long = "stdin")] #[structopt(help = "Input data from stdin", short = "s", long = "stdin")]
input_stdin: bool, input_stdin: bool,
#[structopt(
help = "Additional deframers to apply to the data before trying to parse it",
short = "d",
long = "deframer"
)]
deframer: Option<Vec<String>>,
#[structopt(help = "Format to parse data as")] #[structopt(help = "Format to parse data as")]
format: String, format: String,
@ -55,26 +63,26 @@ pub struct Args {
data: Vec<String>, data: Vec<String>,
} }
fn init() -> Result<(Vec<u8>, Format, FormatConfig), FormatyError> { fn init(args: &Args) -> Result<(Vec<u8>, Format, FormatConfig), FormatyError> {
let args: Args = Args::from_args();
let config = FormatConfig::new(&args.config, &args.global_config)?; let config = FormatConfig::new(&args.config, &args.global_config)?;
let format = config.get_format(&args.format)?; let format = config.get_format(&args.format)?;
let data = if args.input_stdin { let data = if args.input_stdin {
args.input_type.parse_stdin(args.base)? args.input_type.parse_stdin(args.base)?
} else if let Some(input_file) = args.input_file { } else if let Some(input_file) = &args.input_file {
args.input_type.parse_file(&input_file, args.base)? args.input_type.parse_file(input_file, args.base)?
} else { } else {
args.input_type.parse_arg_input(args.data, args.base)? args.input_type
.parse_arg_input(args.data.clone(), args.base)?
}; };
Ok((data, format, config)) Ok((data, format, config))
} }
fn main() { fn main() {
let (data, format, config) = match init() { let args: Args = Args::from_args();
let (data, format, config) = match init(&args) {
Ok((data, format, config)) => (data, format, config), Ok((data, format, config)) => (data, format, config),
Err(e) => { Err(e) => {
println!("Error initializing: {}", e); println!("Error initializing: {}", e);
@ -82,8 +90,8 @@ fn main() {
} }
}; };
match format.format_data(&data, &config) { match format.format_data(&data, &config, args.deframer) {
Ok(data) => println!("{}", data), Ok(data) => println!("{}", data),
Err(_) => print!("Unable to parse data"), Err(err) => print!("Unable to parse data: {}", err),
} }
} }