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 for FormatConfigError { fn from(e: std::io::Error) -> Self { Self::IOError(e) } } impl From 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, } impl FormatConfig { fn parse_config_file( src_file_name: &str, config_data: &str, ) -> Result { 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) -> 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, ) -> 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, global_config_path: &Option, ) -> Result { 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 { 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, } 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 { 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) } } }