Improved docs + a bunch of fixes
+ Better error handling + Line numbers in toml errors should now match the file + Errors during parsing show where in the bit stream they failed + Fixed some issues with string, float, and double parsing reporting the wrong bit width + Updated docs and added the example.toml config + clipppy + fmtmain
parent
f4e75fedd5
commit
70a3c260e6
36
README.md
36
README.md
|
@ -5,6 +5,42 @@ 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
|
||||||
|
|
||||||
|
#### Format
|
||||||
|
A format is a collection of fields that describe the structure of data, so it can be parsed
|
||||||
|
|
||||||
|
Members:
|
||||||
|
* `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
|
||||||
|
* `fields` - 1 or more fields to describe the structure of the data
|
||||||
|
|
||||||
|
#### Field
|
||||||
|
A field is a collection of bits that make up some sort of data. Formaty supports `Int`/`Uint` of arbitrary size,
|
||||||
|
`Float`, `Double`, `Strings` and `Bytes`. This data may not be byte aligned.
|
||||||
|
|
||||||
|
Members:
|
||||||
|
* `name` - the name of the field, used while displaying data
|
||||||
|
* `field_type` - the type of the field and associated data
|
||||||
|
* `print_type` - how to print the data, if not provided it uses the default print for a type
|
||||||
|
* `bit_flip` - overrides the global bit flip field, defaults to false if not provided
|
||||||
|
|
||||||
|
#### Field Types
|
||||||
|
* `UInt`/`Int`: standard integer types. Unsigned or signed respectively. Can be 1 or more bits.
|
||||||
|
* `bit_width`: number of bits composing the integer
|
||||||
|
* `endianess`: byte ordering of the field
|
||||||
|
* `Float`/`Double`: Standard float point types of 32-bit or 64-bit width
|
||||||
|
* `endianess`: byte ordering of the field
|
||||||
|
* `String`: Collection of bytes ended by a null character '\0'
|
||||||
|
* `max_len`: max length of the data
|
||||||
|
* `endianess`: byte ordering of the field
|
||||||
|
* `Bytes`: Collection of bytes with no termination
|
||||||
|
* `max_len`: max number of bytes
|
||||||
|
* `endianess`: byte ordering of the field
|
||||||
|
|
||||||
|
#### Example Config
|
||||||
|
[Example](./formats/example.toml)
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
```bash
|
```bash
|
||||||
./formaty ccsds "[0xe0, 0xa1, 0xc0, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]"
|
./formaty ccsds "[0xe0, 0xa1, 0xc0, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]"
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Example fields to show off configuration
|
||||||
|
# Example data to use "[0x00, 0x55, 0xff, 0x43, 0xd2 0x99 0x90, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x57, 0x6f, 0x72, 0x6c, 0x64]"
|
||||||
|
|
||||||
|
[[formats]]
|
||||||
|
# Format name
|
||||||
|
name = "example"
|
||||||
|
# Global bit flip
|
||||||
|
bit_flip = false
|
||||||
|
|
||||||
|
# signed int field, big endian
|
||||||
|
[[formats.fields]]
|
||||||
|
name = "int field"
|
||||||
|
field_type = {type = "Int", bit_width = 16, endianness = "BigEndian"}
|
||||||
|
|
||||||
|
# unsigned int field, little endian. also is printed as hex
|
||||||
|
[[formats.fields]]
|
||||||
|
name = "uint field"
|
||||||
|
print_type = {print = "Base", base=16}
|
||||||
|
field_type = {type = "UInt", bit_width = 4, endianness = "LittleEndian"}
|
||||||
|
|
||||||
|
# unsigned int field, little endian. also is printed as hex
|
||||||
|
[[formats.fields]]
|
||||||
|
name = "uint field"
|
||||||
|
print_type = {print = "Base", base=16}
|
||||||
|
field_type = {type = "UInt", bit_width = 4, endianness = "LittleEndian"}
|
||||||
|
|
||||||
|
# single precesion float field.
|
||||||
|
[[formats.fields]]
|
||||||
|
name = "float field"
|
||||||
|
bit_flip = false
|
||||||
|
field_type = {type = "Float", endianness = "BigEndian"}
|
||||||
|
|
||||||
|
# string field. Printed as a byte array
|
||||||
|
[[formats.fields]]
|
||||||
|
name = "string field"
|
||||||
|
print_type = {print = "ByteArray"}
|
||||||
|
field_type = {type = "String", endianness = "BigEndian", max_len = 55}
|
||||||
|
|
||||||
|
# byte field
|
||||||
|
[[formats.fields]]
|
||||||
|
name = "bytes field"
|
||||||
|
field_type = {type = "Bytes", endianness = "BigEndian", max_len = 55}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::formatter::format::LastByte;
|
use crate::formatter::format::ByteOrderOperations;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -43,7 +43,7 @@ impl ByteStream {
|
||||||
self.reverse_bits
|
self.reverse_bits
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bytes<T: LastByte>(
|
pub fn get_bytes<T: ByteOrderOperations>(
|
||||||
&self,
|
&self,
|
||||||
bit_ndx: usize,
|
bit_ndx: usize,
|
||||||
bit_count: usize,
|
bit_count: usize,
|
||||||
|
@ -80,6 +80,10 @@ impl ByteStream {
|
||||||
byte_stream.pop();
|
byte_stream.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if byte_count > byte_stream.len() {
|
||||||
|
T::pad_bytes(&mut byte_stream, bytes_needed)
|
||||||
|
}
|
||||||
|
|
||||||
if bits_in_last_byte != 0 {
|
if bits_in_last_byte != 0 {
|
||||||
*byte_stream.last_mut().unwrap() &= bit_mask(bits_in_last_byte);
|
*byte_stream.last_mut().unwrap() &= bit_mask(bits_in_last_byte);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use crate::byte_stream::{bit_mask, ByteStream, ByteStreamError};
|
|
||||||
use crate::formatter::printers::{base_notation, print_bytes_as_array};
|
|
||||||
use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
|
|
||||||
use num_bigint::{BigInt, BigUint};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::fmt::{Display, Formatter, Write};
|
use std::fmt::{Display, Formatter, Write};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
pub trait LastByte: ByteOrder {
|
use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt};
|
||||||
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::byte_stream::{bit_mask, ByteStream, ByteStreamError};
|
||||||
|
use crate::formatter::printers::PrintType;
|
||||||
|
|
||||||
|
pub trait ByteOrderOperations: ByteOrder {
|
||||||
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8>;
|
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8>;
|
||||||
|
|
||||||
fn big_int(buf: &[u8]) -> BigInt;
|
fn big_int(buf: &[u8]) -> BigInt;
|
||||||
|
@ -17,9 +19,11 @@ pub trait LastByte: ByteOrder {
|
||||||
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8>;
|
fn big_u_int_to_bytes(big_int: BigUint) -> Vec<u8>;
|
||||||
|
|
||||||
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8>;
|
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8>;
|
||||||
|
|
||||||
|
fn pad_bytes(buf: &mut Vec<u8>, size: usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LastByte for BigEndian {
|
impl ByteOrderOperations for BigEndian {
|
||||||
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
|
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
|
||||||
buf.first_mut()
|
buf.first_mut()
|
||||||
}
|
}
|
||||||
|
@ -39,9 +43,18 @@ impl LastByte for BigEndian {
|
||||||
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
|
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
|
||||||
big_int.to_signed_bytes_be()
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LastByte for LittleEndian {
|
impl ByteOrderOperations for LittleEndian {
|
||||||
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
|
fn last_byte(buf: &mut Vec<u8>) -> Option<&mut u8> {
|
||||||
buf.last_mut()
|
buf.last_mut()
|
||||||
}
|
}
|
||||||
|
@ -61,6 +74,17 @@ impl LastByte for LittleEndian {
|
||||||
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
|
fn big_int_to_bytes(big_int: BigInt) -> Vec<u8> {
|
||||||
big_int.to_signed_bytes_le()
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -122,75 +146,6 @@ pub enum FieldType {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
|
||||||
#[serde(tag = "print")]
|
|
||||||
pub enum PrintType {
|
|
||||||
Base { base: u32 },
|
|
||||||
ByteArray,
|
|
||||||
Default,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PrintType {
|
|
||||||
fn default() -> Self {
|
|
||||||
PrintType::Default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrintType {
|
|
||||||
pub fn print_big_int<T: LastByte>(&self, big_int: BigInt) -> String {
|
|
||||||
match self {
|
|
||||||
PrintType::Base { base: b } => {
|
|
||||||
format!("{}{}", base_notation(*b), big_int.to_str_radix(*b))
|
|
||||||
}
|
|
||||||
PrintType::ByteArray => print_bytes_as_array(&T::big_int_to_bytes(big_int)),
|
|
||||||
PrintType::Default => big_int.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_big_u_int<T: LastByte>(&self, big_int: BigUint) -> String {
|
|
||||||
match self {
|
|
||||||
PrintType::Base { base: b } => {
|
|
||||||
format!("{}{}", base_notation(*b), big_int.to_str_radix(*b))
|
|
||||||
}
|
|
||||||
PrintType::ByteArray => print_bytes_as_array(&T::big_u_int_to_bytes(big_int)),
|
|
||||||
PrintType::Default => big_int.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_float<T: LastByte>(&self, float: f32) -> String {
|
|
||||||
match self {
|
|
||||||
PrintType::ByteArray => {
|
|
||||||
let mut bytes = Vec::with_capacity(4);
|
|
||||||
bytes.write_f32::<T>(float).unwrap();
|
|
||||||
print_bytes_as_array(&bytes)
|
|
||||||
}
|
|
||||||
_ => float.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_double<T: LastByte>(&self, double: f64) -> String {
|
|
||||||
match self {
|
|
||||||
PrintType::ByteArray => {
|
|
||||||
let mut bytes = Vec::with_capacity(8);
|
|
||||||
bytes.write_f64::<T>(double).unwrap();
|
|
||||||
print_bytes_as_array(&bytes)
|
|
||||||
}
|
|
||||||
_ => double.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_bytes(&self, bytes: &[u8]) -> String {
|
|
||||||
print_bytes_as_array(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_string(&self, s: &str) -> String {
|
|
||||||
match self {
|
|
||||||
PrintType::ByteArray => print_bytes_as_array(s.as_bytes()),
|
|
||||||
_ => s.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq, Copy)]
|
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq, Copy)]
|
||||||
pub enum Endianness {
|
pub enum Endianness {
|
||||||
LittleEndian,
|
LittleEndian,
|
||||||
|
@ -211,7 +166,7 @@ pub struct Field {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Field {
|
impl Field {
|
||||||
fn format_int<T: LastByte>(
|
fn format_int<T: ByteOrderOperations>(
|
||||||
&self,
|
&self,
|
||||||
byte_stream: &ByteStream,
|
byte_stream: &ByteStream,
|
||||||
bit_ndx: usize,
|
bit_ndx: usize,
|
||||||
|
@ -236,7 +191,7 @@ impl Field {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_uint<T: LastByte>(
|
fn format_uint<T: ByteOrderOperations>(
|
||||||
&self,
|
&self,
|
||||||
byte_stream: &ByteStream,
|
byte_stream: &ByteStream,
|
||||||
bit_ndx: usize,
|
bit_ndx: usize,
|
||||||
|
@ -248,7 +203,7 @@ impl Field {
|
||||||
Ok((self.print_type.print_big_u_int::<T>(big_int), bit_width))
|
Ok((self.print_type.print_big_u_int::<T>(big_int), bit_width))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_float<T: LastByte>(
|
fn format_float<T: ByteOrderOperations>(
|
||||||
&self,
|
&self,
|
||||||
byte_stream: &ByteStream,
|
byte_stream: &ByteStream,
|
||||||
bit_ndx: usize,
|
bit_ndx: usize,
|
||||||
|
@ -257,10 +212,13 @@ impl Field {
|
||||||
let mut cursor = Cursor::new(bytes);
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
||||||
let float = cursor.read_f32::<T>().unwrap();
|
let float = cursor.read_f32::<T>().unwrap();
|
||||||
Ok((self.print_type.print_float::<T>(float), 4))
|
Ok((
|
||||||
|
self.print_type.print_float::<T>(float),
|
||||||
|
std::mem::size_of::<f32>() * 8,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_double<T: LastByte>(
|
fn format_double<T: ByteOrderOperations>(
|
||||||
&self,
|
&self,
|
||||||
byte_stream: &ByteStream,
|
byte_stream: &ByteStream,
|
||||||
bit_ndx: usize,
|
bit_ndx: usize,
|
||||||
|
@ -269,35 +227,37 @@ impl Field {
|
||||||
let mut cursor = Cursor::new(bytes);
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
||||||
let double = cursor.read_f64::<T>().unwrap();
|
let double = cursor.read_f64::<T>().unwrap();
|
||||||
Ok((self.print_type.print_double::<T>(double), 4))
|
Ok((
|
||||||
|
self.print_type.print_double::<T>(double),
|
||||||
|
std::mem::size_of::<f64>() * 8,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_string<T: LastByte>(
|
fn format_string<T: ByteOrderOperations>(
|
||||||
&self,
|
&self,
|
||||||
byte_stream: &ByteStream,
|
byte_stream: &ByteStream,
|
||||||
mut bit_ndx: usize,
|
bit_ndx: usize,
|
||||||
max_byte_len: usize,
|
max_byte_len: usize,
|
||||||
) -> Result<(String, usize), FormatError> {
|
) -> Result<(String, usize), FormatError> {
|
||||||
let mut string_bytes = Vec::new();
|
let mut string_bytes = Vec::new();
|
||||||
|
|
||||||
|
let mut bit_count = 0;
|
||||||
for _ in 0..max_byte_len {
|
for _ in 0..max_byte_len {
|
||||||
let byte = byte_stream.get_bytes::<T>(bit_ndx, 8)?[0];
|
let byte = byte_stream.get_bytes::<T>(bit_ndx + bit_count, 8)?[0];
|
||||||
|
bit_count += 8;
|
||||||
|
|
||||||
if byte == 0 {
|
if byte == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
string_bytes.push(byte);
|
string_bytes.push(byte);
|
||||||
bit_ndx += 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let byte_count = string_bytes.len();
|
|
||||||
|
|
||||||
let s = String::from_utf8(string_bytes)?;
|
let s = String::from_utf8(string_bytes)?;
|
||||||
Ok((self.print_type.print_string(&s), byte_count))
|
Ok((self.print_type.print_string(&s), bit_count))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_bytes<T: LastByte>(
|
fn format_bytes<T: ByteOrderOperations>(
|
||||||
&self,
|
&self,
|
||||||
byte_stream: &ByteStream,
|
byte_stream: &ByteStream,
|
||||||
bit_ndx: usize,
|
bit_ndx: usize,
|
||||||
|
@ -415,7 +375,10 @@ impl Format {
|
||||||
writeln!(format_str, "{}: {}", field.name, data_str).unwrap();
|
writeln!(format_str, "{}: {}", field.name, data_str).unwrap();
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error formatting field: \"{}\": {}", field.name, e);
|
println!(
|
||||||
|
"Error formatting field (bit_ndx = {}): \"{}\": {}",
|
||||||
|
bit_ndx, field.name, e
|
||||||
|
);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -439,7 +402,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for i in 0i8..7i8 {
|
for i in 0i8..7i8 {
|
||||||
|
@ -448,8 +411,11 @@ mod tests {
|
||||||
byte_vec.push((-i) as u8);
|
byte_vec.push((-i) as u8);
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(byte_vec);
|
let mut byte_stream = ByteStream::from(byte_vec);
|
||||||
let (pos_output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
let (pos_output, width1) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
let (neg_output, _) = field.format_data(&mut byte_stream, 8).unwrap();
|
let (neg_output, width2) = field.format_data(&mut byte_stream, 8).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width1, 4);
|
||||||
|
assert_eq!(width2, 4);
|
||||||
|
|
||||||
assert_eq!(pos_output, i.to_string());
|
assert_eq!(pos_output, i.to_string());
|
||||||
assert_eq!(neg_output, (-i).to_string());
|
assert_eq!(neg_output, (-i).to_string());
|
||||||
|
@ -465,11 +431,12 @@ mod tests {
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
let mut byte_stream = ByteStream::from(vec![0x1B]);
|
let mut byte_stream = ByteStream::from(vec![0x1B]);
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
let (output, width) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width, 5);
|
||||||
assert_eq!(output, "-5")
|
assert_eq!(output, "-5")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,12 +449,13 @@ mod tests {
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(vec![0xFC, 0xA5]);
|
let mut byte_stream = ByteStream::from(vec![0xFC, 0xA5]);
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
let (output, width) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width, 16);
|
||||||
assert_eq!(output, "-23044")
|
assert_eq!(output, "-23044")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,12 +468,13 @@ mod tests {
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(vec![0xC0, 0x5F, 0x0A]);
|
let mut byte_stream = ByteStream::from(vec![0xC0, 0x5F, 0x0A]);
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 4).unwrap();
|
let (output, width) = field.format_data(&mut byte_stream, 4).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width, 16);
|
||||||
assert_eq!(output, "-23044")
|
assert_eq!(output, "-23044")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,12 +486,13 @@ mod tests {
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(b"\x52\x58\xd2\xc3".to_vec());
|
let mut byte_stream = ByteStream::from(b"\x52\x58\xd2\xc3".to_vec());
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
let (output, width) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width, 32);
|
||||||
assert_eq!(output, "-420.69")
|
assert_eq!(output, "-420.69")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,12 +504,13 @@ mod tests {
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(b"\xD7\xA3\x70\x3D\x0A\x4B\x7A\xC0".to_vec());
|
let mut byte_stream = ByteStream::from(b"\xD7\xA3\x70\x3D\x0A\x4B\x7A\xC0".to_vec());
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
let (output, width) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width, 64);
|
||||||
assert_eq!(output, "-420.69")
|
assert_eq!(output, "-420.69")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,7 +522,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(b"\x3D\x70\xA3\xD7".to_vec());
|
let mut byte_stream = ByteStream::from(b"\x3D\x70\xA3\xD7".to_vec());
|
||||||
|
@ -561,70 +532,65 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_string() {
|
fn test_format_string() {
|
||||||
|
let test_string = "Hello World!";
|
||||||
let field = Field {
|
let field = Field {
|
||||||
field_type: FieldType::String { max_len: 16, endianness: Endianness::LittleEndian },
|
field_type: FieldType::String {
|
||||||
|
max_len: 16,
|
||||||
|
endianness: Endianness::LittleEndian,
|
||||||
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(b"Hello World!\0".to_vec());
|
let mut bytes = test_string.as_bytes().to_vec();
|
||||||
|
bytes.push('\0' as u8);
|
||||||
|
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
let mut byte_stream = ByteStream::from(bytes);
|
||||||
|
|
||||||
assert_eq!(output, "Hello World!".to_string())
|
let (output, width) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width, (test_string.len() + 1) * 8); //extra byte to account for the '\0' char
|
||||||
|
assert_eq!(output, test_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_bytes() {
|
fn test_format_bytes() {
|
||||||
let field = Field {
|
let field = Field {
|
||||||
field_type: FieldType::Bytes { max_len: 2, endianness: Endianness::LittleEndian },
|
field_type: FieldType::Bytes {
|
||||||
|
max_len: 2,
|
||||||
|
endianness: Endianness::LittleEndian,
|
||||||
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(vec![0xDE, 0xAD, 0xBE, 0xEF]);
|
let mut byte_stream = ByteStream::from(vec![0xDE, 0xAD, 0xBE, 0xEF]);
|
||||||
|
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
let (output, width) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(width, 16);
|
||||||
assert_eq!(output, "[222, 173]")
|
assert_eq!(output, "[222, 173]")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_bytes_max_len_bigger_than_data() {
|
fn test_format_bytes_max_len_bigger_than_data() {
|
||||||
let field = Field {
|
let field = Field {
|
||||||
field_type: FieldType::Bytes { max_len: 64, endianness: Endianness::LittleEndian },
|
field_type: FieldType::Bytes {
|
||||||
name: "test".to_string(),
|
max_len: 64,
|
||||||
bit_flip: None,
|
|
||||||
print_type: Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(vec![0xDE, 0xAD]);
|
|
||||||
|
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 0).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(output, "[222, 173]")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ccsds_apid_issue() {
|
|
||||||
let field = Field {
|
|
||||||
field_type: FieldType::UInt {
|
|
||||||
bit_width: 11,
|
|
||||||
endianness: Endianness::LittleEndian,
|
endianness: Endianness::LittleEndian,
|
||||||
},
|
},
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
bit_flip: None,
|
bit_flip: None,
|
||||||
print_type: Default::default()
|
print_type: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut byte_stream = ByteStream::from(vec![
|
let mut byte_stream = ByteStream::from(vec![0xDE, 0xAD]);
|
||||||
0xe0, 0xa1, 0xc0, 0x00, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
|
|
||||||
]);
|
|
||||||
|
|
||||||
let (output, _) = field.format_data(&mut byte_stream, 5).unwrap();
|
let (output, width) = field.format_data(&mut byte_stream, 0).unwrap();
|
||||||
|
|
||||||
assert_eq!(output, "1295")
|
assert_eq!(width, 16);
|
||||||
|
assert_eq!(output, "[222, 173]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,10 @@ use std::str;
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum FormatConfigError {
|
pub enum FormatConfigError {
|
||||||
IOError(std::io::Error),
|
IOError(std::io::Error),
|
||||||
TomlError(toml::de::Error),
|
TomlError {
|
||||||
|
file_name: String,
|
||||||
|
err: toml::de::Error,
|
||||||
|
},
|
||||||
Utf8Error(std::str::Utf8Error),
|
Utf8Error(std::str::Utf8Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,12 +30,6 @@ impl From<std::io::Error> for FormatConfigError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<toml::de::Error> for FormatConfigError {
|
|
||||||
fn from(e: toml::de::Error) -> Self {
|
|
||||||
Self::TomlError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::str::Utf8Error> for FormatConfigError {
|
impl From<std::str::Utf8Error> for FormatConfigError {
|
||||||
fn from(e: std::str::Utf8Error) -> Self {
|
fn from(e: std::str::Utf8Error) -> Self {
|
||||||
Self::Utf8Error(e)
|
Self::Utf8Error(e)
|
||||||
|
@ -43,7 +40,9 @@ impl Display for FormatConfigError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
let err_msg = match self {
|
let err_msg = match self {
|
||||||
FormatConfigError::IOError(e) => e.to_string(),
|
FormatConfigError::IOError(e) => e.to_string(),
|
||||||
FormatConfigError::TomlError(e) => e.to_string(),
|
FormatConfigError::TomlError { file_name, err } => {
|
||||||
|
format!("Error parsing {}: {}", file_name, err)
|
||||||
|
}
|
||||||
FormatConfigError::Utf8Error(e) => e.to_string(),
|
FormatConfigError::Utf8Error(e) => e.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,19 +55,26 @@ impl Display for FormatConfigError {
|
||||||
#[include = "*.toml"]
|
#[include = "*.toml"]
|
||||||
struct BuiltInFormats;
|
struct BuiltInFormats;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, Default)]
|
||||||
pub struct FormatConfig {
|
pub struct FormatConfig {
|
||||||
pub formats: Vec<Format>,
|
pub formats: Vec<Format>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatConfig {
|
impl FormatConfig {
|
||||||
pub fn new(config_path: &Option<PathBuf>) -> Result<Self, FormatConfigError> {
|
pub fn new(config_path: &Option<PathBuf>) -> Result<Self, FormatConfigError> {
|
||||||
let mut contents = String::new();
|
let mut config_str = String::new();
|
||||||
|
|
||||||
if let Some(config_path) = config_path {
|
let mut config: FormatConfig = if let Some(config_path) = config_path {
|
||||||
let mut config = File::open(config_path)?;
|
let mut config = File::open(config_path)?;
|
||||||
config.read_to_string(&mut contents)?;
|
config.read_to_string(&mut config_str)?;
|
||||||
}
|
|
||||||
|
toml::from_str(&config_str).map_err(|e| FormatConfigError::TomlError {
|
||||||
|
file_name: config_path.to_str().unwrap().to_string(),
|
||||||
|
err: e,
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
FormatConfig::default()
|
||||||
|
};
|
||||||
|
|
||||||
for format_file_path in BuiltInFormats::iter() {
|
for format_file_path in BuiltInFormats::iter() {
|
||||||
if format_file_path.ends_with("md") {
|
if format_file_path.ends_with("md") {
|
||||||
|
@ -77,9 +83,17 @@ impl FormatConfig {
|
||||||
|
|
||||||
let format_file = BuiltInFormats::get(&format_file_path).unwrap();
|
let format_file = BuiltInFormats::get(&format_file_path).unwrap();
|
||||||
|
|
||||||
contents.push_str(str::from_utf8(&format_file.data).unwrap());
|
let config_str = str::from_utf8(&format_file.data).unwrap();
|
||||||
|
|
||||||
|
let mut built_in: FormatConfig =
|
||||||
|
toml::from_str(config_str).map_err(|e| FormatConfigError::TomlError {
|
||||||
|
file_name: format_file_path.to_string(),
|
||||||
|
err: e,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
config.formats.append(&mut built_in.formats);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(toml::from_str(&contents)?)
|
Ok(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
use byteorder::WriteBytesExt;
|
||||||
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::formatter::format::ByteOrderOperations;
|
||||||
|
|
||||||
pub fn print_bytes_as_array(data: &[u8]) -> String {
|
pub fn print_bytes_as_array(data: &[u8]) -> String {
|
||||||
format!("{:?}", data)
|
format!("{:?}", data)
|
||||||
}
|
}
|
||||||
|
@ -11,3 +17,72 @@ pub fn base_notation(b: u32) -> String {
|
||||||
_ => format!("Base({}) ", b),
|
_ => format!("Base({}) ", b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(tag = "print")]
|
||||||
|
pub enum PrintType {
|
||||||
|
Base { base: u32 },
|
||||||
|
ByteArray,
|
||||||
|
Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PrintType {
|
||||||
|
fn default() -> Self {
|
||||||
|
PrintType::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrintType {
|
||||||
|
pub fn print_big_int<T: ByteOrderOperations>(&self, big_int: BigInt) -> String {
|
||||||
|
match self {
|
||||||
|
PrintType::Base { base: b } => {
|
||||||
|
format!("{}{}", base_notation(*b), big_int.to_str_radix(*b))
|
||||||
|
}
|
||||||
|
PrintType::ByteArray => print_bytes_as_array(&T::big_int_to_bytes(big_int)),
|
||||||
|
PrintType::Default => big_int.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_big_u_int<T: ByteOrderOperations>(&self, big_int: BigUint) -> String {
|
||||||
|
match self {
|
||||||
|
PrintType::Base { base: b } => {
|
||||||
|
format!("{}{}", base_notation(*b), big_int.to_str_radix(*b))
|
||||||
|
}
|
||||||
|
PrintType::ByteArray => print_bytes_as_array(&T::big_u_int_to_bytes(big_int)),
|
||||||
|
PrintType::Default => big_int.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_float<T: ByteOrderOperations>(&self, float: f32) -> String {
|
||||||
|
match self {
|
||||||
|
PrintType::ByteArray => {
|
||||||
|
let mut bytes = Vec::with_capacity(4);
|
||||||
|
bytes.write_f32::<T>(float).unwrap();
|
||||||
|
print_bytes_as_array(&bytes)
|
||||||
|
}
|
||||||
|
_ => float.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_double<T: ByteOrderOperations>(&self, double: f64) -> String {
|
||||||
|
match self {
|
||||||
|
PrintType::ByteArray => {
|
||||||
|
let mut bytes = Vec::with_capacity(8);
|
||||||
|
bytes.write_f64::<T>(double).unwrap();
|
||||||
|
print_bytes_as_array(&bytes)
|
||||||
|
}
|
||||||
|
_ => double.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_bytes(&self, bytes: &[u8]) -> String {
|
||||||
|
print_bytes_as_array(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_string(&self, s: &str) -> String {
|
||||||
|
match self {
|
||||||
|
PrintType::ByteArray => print_bytes_as_array(s.as_bytes()),
|
||||||
|
_ => s.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
25
src/main.rs
25
src/main.rs
|
@ -1,4 +1,5 @@
|
||||||
use crate::error::FormatyError;
|
use crate::error::FormatyError;
|
||||||
|
use crate::formatter::format::Format;
|
||||||
use crate::parser::parse_bytes_from_input_arg;
|
use crate::parser::parse_bytes_from_input_arg;
|
||||||
use formatter::FormatConfig;
|
use formatter::FormatConfig;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -27,7 +28,7 @@ pub struct Args {
|
||||||
data: Vec<String>,
|
data: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn formaty() -> Result<(), FormatyError> {
|
fn init() -> Result<(Vec<u8>, Format), FormatyError> {
|
||||||
let args: Args = Args::from_args();
|
let args: Args = Args::from_args();
|
||||||
|
|
||||||
let config = FormatConfig::new(&args.config)?;
|
let config = FormatConfig::new(&args.config)?;
|
||||||
|
@ -36,22 +37,24 @@ fn formaty() -> Result<(), FormatyError> {
|
||||||
.formats
|
.formats
|
||||||
.iter()
|
.iter()
|
||||||
.find(|f| f.name == args.format)
|
.find(|f| f.name == args.format)
|
||||||
.ok_or_else(|| FormatyError::FormatNotFound(args.format.to_string()))?;
|
.ok_or_else(|| FormatyError::FormatNotFound(args.format.to_string()))?
|
||||||
|
.clone();
|
||||||
|
|
||||||
let data = parse_bytes_from_input_arg(args.data).unwrap();
|
let data = parse_bytes_from_input_arg(args.data).unwrap();
|
||||||
|
Ok((data, format))
|
||||||
let data = format.format_data(&data)?;
|
|
||||||
|
|
||||||
println!("{}", data);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
match formaty() {
|
let (data, format) = match init() {
|
||||||
Ok(_) => {}
|
Ok((data, format)) => (data, format),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error formatting data: {}", e.to_string())
|
println!("Error initializing: {}", e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match format.format_data(&data) {
|
||||||
|
Ok(data) => println!("{}", data),
|
||||||
|
Err(_) => print!("Unable to parse data"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,9 +50,9 @@ pub fn parse_bytes_from_input_arg(src: Vec<String>) -> Result<Vec<u8>, ByteArray
|
||||||
|
|
||||||
let str_arr = if src.len() == 1 {
|
let str_arr = if src.len() == 1 {
|
||||||
src[0]
|
src[0]
|
||||||
.replace(",", " ")
|
.replace(',', " ")
|
||||||
.replace("[", "")
|
.replace('[', "")
|
||||||
.replace("]", "")
|
.replace(']', "")
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect()
|
.collect()
|
||||||
|
|
Loading…
Reference in New Issue