formaty/src/formatter/mod.rs

317 lines
8.9 KiB
Rust

pub mod format;
mod printers;
use crate::formatter::format::Format;
use platform_dirs::AppDirs;
use rust_embed::RustEmbed;
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)]
#[allow(clippy::enum_variant_names)]
pub enum FormatConfigError {
IOError(std::io::Error),
TomlError {
file_name: String,
err: toml::de::Error,
},
Utf8Error(str::Utf8Error),
FormatNotFound(String),
}
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)]
mod test {
use crate::formatter::format::Format;
use crate::formatter::printers::print_bytes_as_array;
use crate::formatter::FormatConfig;
#[derive(PartialOrd, PartialEq, Debug, Clone)]
struct CCSDSPacket {
packet_version: u8,
packet_type: bool,
sec_header_flag: bool,
apid: u16,
seq_flag: u8,
seq: u16,
packet_len: u16,
data: Vec<u8>,
}
impl CCSDSPacket {
fn from_bytes(data: &[u8]) -> Self {
let packet_version = data[0] >> 5;
let packet_type = ((data[0] >> 4) & 0x01) == 1;
let sec_header_flag = ((data[0] >> 3) & 0x01) == 1;
let apid = (((data[0] & 0x7) as u16) << 8) | (data[1] as u16);
let seq_flag = data[2] >> 6;
let seq = (((data[2] & 0x3F) as u16) << 8) | (data[3] as u16);
let packet_len = ((data[4] as u16) << 8) | (data[5] as u16);
CCSDSPacket {
packet_version,
packet_type,
sec_header_flag,
apid,
seq_flag,
seq,
packet_len,
data: data[6..].to_vec(),
}
}
fn to_bytes(&self) -> Vec<u8> {
let mut data = vec![0u8; 6];
data[0] = (self.packet_version << 5)
| ((self.packet_type as u8) << 4)
| ((self.sec_header_flag as u8) << 3)
| ((self.apid >> 8) as u8);
data[1] = (self.apid & 0xFF) as u8;
data[2] = (self.seq_flag << 6) | ((self.seq >> 8) as u8);
data[3] = (self.seq & 0xFF) as u8;
data[4] = (self.packet_len >> 8) as u8;
data[5] = (self.packet_len & 0xff) as u8;
data.append(&mut self.data.clone());
data
}
// based off formats/ccsds.toml config
fn print(&self) -> String {
format!(
"\
Version Number: {}\n\
Packet Type: {}\n\
Secondary Header Flag: {}\n\
APID: {:#x}\n\
Sequence Flags: {}\n\
Packet Sequence Count: {}\n\
Data Length: {}\n\
Data: {}\n",
self.packet_version,
self.packet_type as u8,
self.sec_header_flag as u8,
self.apid,
self.seq_flag,
self.seq,
self.packet_len,
print_bytes_as_array(&self.data)
)
}
}
#[test]
fn test_ccsds() {
let ccsds_packet = CCSDSPacket {
packet_version: 0,
packet_type: true,
sec_header_flag: false,
apid: 0x2FF,
seq_flag: 3,
seq: 0,
packet_len: 5,
data: vec![0x01, 0x02, 0x03, 0x04, 0x05],
};
let bytes = ccsds_packet.to_bytes();
let parsed_packet = CCSDSPacket::from_bytes(&bytes);
assert_eq!(ccsds_packet, parsed_packet);
assert_eq!(bytes, parsed_packet.to_bytes())
}
fn get_ccsds_format() -> Format {
let format_config = FormatConfig::new(&None, &None).unwrap();
format_config.get_format("ccsds").unwrap()
}
#[test]
fn test_ccsds_parse() {
let ccsds_packet = CCSDSPacket {
packet_version: 0,
packet_type: true,
sec_header_flag: false,
apid: 0x2FF,
seq_flag: 3,
seq: 0,
packet_len: 5,
data: vec![0x01, 0x02, 0x03, 0x04, 0x05],
};
let format = get_ccsds_format();
let data = ccsds_packet.to_bytes();
let output = format.format_data(&data).unwrap();
assert_eq!(ccsds_packet.print(), output)
}
#[test]
fn test_ccsds_parse_hammer() {
let format = get_ccsds_format();
for apid in 0..(2u16.pow(11)) {
let ccsds_packet = CCSDSPacket {
packet_version: 0,
packet_type: (apid % 2) == 0,
sec_header_flag: (apid % 2) == 1,
apid,
seq_flag: (apid % 4) as u8,
seq: (apid % 11),
packet_len: 5,
data: vec![0x01, 0x02, 0x03, 0x04, 0x05],
};
let data = ccsds_packet.to_bytes();
let output = format.format_data(&data).unwrap();
assert_eq!(ccsds_packet.print(), output)
}
}
}