From 739e9a2fc4fec031c1dc97db43a7fe785386b1e4 Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Thu, 30 Nov 2023 20:52:32 +0100 Subject: [PATCH 01/10] lay foundation for communication module refactoring --- Cargo.lock | 186 +++++++++++++- Cargo.toml | 3 + src/command/common.rs | 7 +- src/command/error.rs | 5 +- src/command/execute_program.rs | 13 +- src/command/execution_context.rs | 9 +- src/command/get_status.rs | 9 +- src/command/mod.rs | 6 +- src/command/return_result.rs | 35 ++- src/command/stop_program.rs | 5 +- src/command/store_archive.rs | 9 +- src/command/update_time.rs | 5 +- src/communication/cep.rs | 109 ++++++++- src/communication/communication.rs | 257 +++++++++++++------- src/communication/mod.rs | 2 - src/communication/uart.rs | 92 ------- src/lib.rs | 1 + src/main.rs | 10 +- tests/simulation/logging.rs | 8 +- tests/simulation/mod.rs | 31 ++- tests/software_tests/command_integration.rs | 12 +- tests/software_tests/common.rs | 133 +++++----- tests/software_tests/return_result.rs | 7 +- tests/software_tests/store_archive.rs | 34 --- tests/tests.rs | 5 +- 25 files changed, 630 insertions(+), 363 deletions(-) delete mode 100644 src/communication/uart.rs diff --git a/Cargo.lock b/Cargo.lock index 6113580..025c3d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,27 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "CoreFoundation-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" +dependencies = [ + "libc", + "mach", +] + +[[package]] +name = "IOKit-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a" +dependencies = [ + "CoreFoundation-sys", + "libc", + "mach", +] + [[package]] name = "STS1_EDU_Scheduler" version = "0.1.0" @@ -12,8 +33,11 @@ dependencies = [ "log", "rppal", "serde", + "serialport", "simplelog", + "strum", "subprocess", + "test-case", "toml", ] @@ -43,6 +67,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + [[package]] name = "byteorder" version = "1.4.3" @@ -113,6 +149,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -152,9 +194,29 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.123" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] [[package]] name = "log" @@ -165,12 +227,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" +dependencies = [ + "libc", +] + +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -195,6 +286,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -262,6 +359,18 @@ dependencies = [ "libc", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.173" @@ -291,6 +400,24 @@ dependencies = [ "serde", ] +[[package]] +name = "serialport" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32634e2bd4311420caa504404a55fad2131292c485c97014cbed89a5899885f" +dependencies = [ + "CoreFoundation-sys", + "IOKit-sys", + "bitflags 2.0.2", + "cfg-if", + "libudev", + "mach2", + "nix", + "regex", + "scopeguard", + "winapi", +] + [[package]] name = "simplelog" version = "0.12.0" @@ -302,6 +429,28 @@ dependencies = [ "time", ] +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subprocess" version = "0.2.8" @@ -332,6 +481,39 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + [[package]] name = "time" version = "0.3.14" diff --git a/Cargo.toml b/Cargo.toml index dbacd6a..2ae828d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ crc = "3.0.0" rppal = "^0.13.1" toml = "0.7.6" serde = { version = "1.0.166", features = ["derive"] } +strum = { version = "0.25.0", features = ["derive"] } +serialport = "4.2.2" +test-case = "3.3.1" [dependencies.filevec] path = "lib/filevec" diff --git a/src/command/common.rs b/src/command/common.rs index b34023c..5099cc2 100644 --- a/src/command/common.rs +++ b/src/command/common.rs @@ -1,13 +1,12 @@ use std::time::Duration; - +use crate::communication::{CommunicationHandle, CEPPacket}; use super::{CommandError, CommandResult, SyncExecutionContext}; -pub const COM_TIMEOUT_DURATION: std::time::Duration = std::time::Duration::new(2, 0); - -pub fn check_length(vec: &Vec, n: usize) -> Result<(), CommandError> { +pub fn check_length(com: &mut impl CommunicationHandle, vec: &Vec, n: usize) -> Result<(), CommandError> { let actual_len = vec.len(); if actual_len != n { log::error!("Command came with {actual_len} bytes, should have {n}"); + com.send_packet(&CEPPacket::NACK)?; Err(CommandError::ProtocolViolation( format!("Received command with {actual_len} bytes, expected {n}").into(), )) diff --git a/src/command/error.rs b/src/command/error.rs index a53913c..1cbfe0c 100644 --- a/src/command/error.rs +++ b/src/command/error.rs @@ -26,9 +26,10 @@ impl From for CommandError { match e { CommunicationError::PacketInvalidError => CommandError::External(Box::new(e)), CommunicationError::CRCError => CommandError::ProtocolViolation(Box::new(e)), - CommunicationError::InterfaceError => CommandError::NonRecoverable(Box::new(e)), + CommunicationError::Io(_) => CommandError::NonRecoverable(Box::new(e)), CommunicationError::STOPCondition => CommandError::External(Box::new(e)), - CommunicationError::TimeoutError => todo!("Timeout not yet specified"), + CommunicationError::NotAcknowledged => CommandError::ProtocolViolation(Box::new(e)), + CommunicationError::TimedOut => todo!("Timeout not yet specified"), } } } diff --git a/src/command/execute_program.rs b/src/command/execute_program.rs index 127d3ec..52b78b8 100644 --- a/src/command/execute_program.rs +++ b/src/command/execute_program.rs @@ -19,8 +19,7 @@ pub fn execute_program( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 9)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 9)?; let program_id = u16::from_le_bytes([data[1], data[2]]); let timestamp = u32::from_le_bytes([data[3], data[4], data[5], data[6]]); @@ -29,7 +28,13 @@ pub fn execute_program( terminate_student_program(exec).expect("to terminate a running program"); - let student_process = create_student_process(program_id, timestamp)?; + let student_process = match create_student_process(program_id, timestamp) { + Ok(p) => p, + Err(e) => { + com.send_packet(&CEPPacket::NACK)?; + return Err(e); + } + }; // WATCHDOG THREAD let mut wd_context = exec.clone(); @@ -55,7 +60,7 @@ pub fn execute_program( l_context.running_flag = true; drop(l_context); - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::ACK)?; Ok(()) } diff --git a/src/command/execution_context.rs b/src/command/execution_context.rs index 0e234fc..a2e6197 100644 --- a/src/command/execution_context.rs +++ b/src/command/execution_context.rs @@ -23,7 +23,10 @@ pub struct ExecutionContext { } impl ExecutionContext { - pub fn new(event_file_path: String, update_pin: u8) -> Result { + pub fn new( + event_file_path: String, + update_pin: u8, + ) -> Result>, std::io::Error> { let mut ec = ExecutionContext { thread_handle: None, running_flag: false, @@ -33,7 +36,7 @@ impl ExecutionContext { ec.check_update_pin(); - Ok(ec) + Ok(Arc::new(Mutex::new(ec))) } /// Checks and sets/resets the update pin accordingly @@ -85,7 +88,7 @@ pub struct UpdatePin { #[cfg(feature = "mock")] impl UpdatePin { - pub fn new(pin: u8) -> Self { + pub fn new(_pin: u8) -> Self { let update_pin = UpdatePin { pin: false }; return update_pin; } diff --git a/src/command/get_status.rs b/src/command/get_status.rs index 0175d6d..dc56a74 100644 --- a/src/command/get_status.rs +++ b/src/command/get_status.rs @@ -9,12 +9,11 @@ pub fn get_status( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 1)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 1)?; let mut l_exec = exec.lock().unwrap(); if !l_exec.has_data_ready() { - com.send_packet(CEPPacket::DATA(vec![0]))?; + com.send_packet(&CEPPacket::DATA(vec![0]))?; return Ok(()); } @@ -22,11 +21,11 @@ pub fn get_status( l_exec.event_vec.as_ref().iter().position(|x| matches!(x, Event::Status(_))) { let event = l_exec.event_vec[index]; - com.send_packet(CEPPacket::DATA(event.to_bytes()))?; + com.send_packet(&CEPPacket::DATA(event.to_bytes()))?; l_exec.event_vec.remove(index)?; } else { let event = *l_exec.event_vec.as_ref().last().unwrap(); // Safe, because we know it is not empty - com.send_packet(CEPPacket::DATA(event.to_bytes()))?; + com.send_packet(&CEPPacket::DATA(event.to_bytes()))?; if !matches!(event, Event::Result(_)) { // Results are removed when deleted diff --git a/src/command/mod.rs b/src/command/mod.rs index 9df9bc4..ffd2479 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,5 +1,4 @@ use crate::communication::{CEPPacket, CommunicationHandle}; -use std::time::Duration; mod common; pub use common::*; @@ -33,11 +32,10 @@ pub fn handle_command(com: &mut impl CommunicationHandle, exec: &mut SyncExecuti Err(CommandError::NonRecoverable(e)) => { log::error!("Non-Recoverable error: {e}"); - panic!("Aborting now"); + panic!("Aborting now {e:?}"); } Err(CommandError::ProtocolViolation(e)) => { log::error!("Protocol Violation: {e}"); - com.send_packet(CEPPacket::NACK).unwrap(); } Err(CommandError::External(e)) => { log::error!("External error: {e}"); @@ -49,7 +47,7 @@ pub fn process_command( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - let packet = com.receive_packet(&Duration::MAX)?; + let packet = com.receive_packet()?; let data = match packet { CEPPacket::DATA(data) => data, _ => { diff --git a/src/command/return_result.rs b/src/command/return_result.rs index 31b3c57..10b2723 100644 --- a/src/command/return_result.rs +++ b/src/command/return_result.rs @@ -1,5 +1,7 @@ +use std::time::Duration; + use crate::{ - command::{check_length, CommandError, Event, ResultId, COM_TIMEOUT_DURATION}, + command::{check_length, CommandError, Event, ResultId}, communication::{CEPPacket, CommunicationHandle}, }; @@ -12,14 +14,14 @@ pub fn return_result( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 7)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 7)?; let program_id = u16::from_le_bytes([data[1], data[2]]); let timestamp = u32::from_le_bytes([data[3], data[4], data[5], data[6]]); let result_path = format!("./data/{}_{}.zip", program_id, timestamp); if !std::path::Path::new(&result_path).exists() { + com.send_packet(&CEPPacket::NACK)?; return Err(CommandError::ProtocolViolation( format!("Result {}:{} does not exist", program_id, timestamp).into(), )); @@ -27,22 +29,17 @@ pub fn return_result( let bytes = std::fs::read(result_path)?; log::info!("Returning result for {}:{}", program_id, timestamp); - com.send_multi_packet(bytes, &COM_TIMEOUT_DURATION)?; - - let response = com.receive_packet(&COM_TIMEOUT_DURATION)?; - if response == CEPPacket::ACK { - let result_id = ResultId { program_id, timestamp }; - delete_result(result_id)?; - - let mut l_exec = exec.lock().unwrap(); - let event_index = - l_exec.event_vec.as_ref().iter().position(|x| x == &Event::Result(result_id)).unwrap(); - l_exec.event_vec.remove(event_index)?; - l_exec.check_update_pin(); - drop(l_exec); - } else { - log::error!("COBC did not acknowledge result"); - } + com.send_multi_packet(&bytes)?; + + com.await_ack(&Duration::from_secs(1))?; + let result_id = ResultId { program_id, timestamp }; + delete_result(result_id)?; + + let mut l_exec = exec.lock().unwrap(); + let event_index = + l_exec.event_vec.as_ref().iter().position(|x| x == &Event::Result(result_id)).unwrap(); + l_exec.event_vec.remove(event_index)?; + l_exec.check_update_pin(); Ok(()) } diff --git a/src/command/stop_program.rs b/src/command/stop_program.rs index 3899fe6..1da72b1 100644 --- a/src/command/stop_program.rs +++ b/src/command/stop_program.rs @@ -8,11 +8,10 @@ pub fn stop_program( com: &mut impl CommunicationHandle, exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 1)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 1)?; terminate_student_program(exec).expect("to terminate student program"); - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::ACK)?; Ok(()) } diff --git a/src/command/store_archive.rs b/src/command/store_archive.rs index 3e93d61..6c55f04 100644 --- a/src/command/store_archive.rs +++ b/src/command/store_archive.rs @@ -2,7 +2,7 @@ use std::{io::Write, process::Command}; use super::{CommandError, CommandResult, SyncExecutionContext}; use crate::{ - command::{check_length, COM_TIMEOUT_DURATION}, + command::check_length, communication::{CEPPacket, CommunicationHandle}, }; @@ -12,16 +12,15 @@ pub fn store_archive( com: &mut impl CommunicationHandle, _exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 3)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 3)?; let id = u16::from_le_bytes([data[1], data[2]]).to_string(); log::info!("Storing Archive {}", id); - let bytes = com.receive_multi_packet(&COM_TIMEOUT_DURATION, || false)?; // !! TODO !! + let bytes = com.receive_multi_packet(|| false)?; // !! TODO !! unpack_archive(id, bytes)?; - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::ACK)?; Ok(()) } diff --git a/src/command/update_time.rs b/src/command/update_time.rs index 8ac08b0..5eb8eed 100644 --- a/src/command/update_time.rs +++ b/src/command/update_time.rs @@ -10,13 +10,12 @@ pub fn update_time( com: &mut impl CommunicationHandle, _exec: &mut SyncExecutionContext, ) -> CommandResult { - check_length(&data, 5)?; - com.send_packet(CEPPacket::ACK)?; + check_length(com, &data, 5)?; let time = i32::from_le_bytes([data[1], data[2], data[3], data[4]]); set_system_time(time)?; - com.send_packet(CEPPacket::ACK)?; + com.send_packet(&CEPPacket::ACK)?; Ok(()) } diff --git a/src/communication/cep.rs b/src/communication/cep.rs index 9a06628..4608bab 100644 --- a/src/communication/cep.rs +++ b/src/communication/cep.rs @@ -9,10 +9,30 @@ pub enum CEPPacket { DATA(Vec), } +#[derive(Clone, Copy, strum::FromRepr)] +pub enum CEPPacketHeader { + ACK = 0xd7, + NACK = 0x27, + STOP = 0xb4, + EOF = 0x59, + DATA = 0x8b, +} + impl CEPPacket { + pub const MAXIMUM_DATA_LENGTH: usize = 32768; + pub const MAXIMUM_PACKET_LENGTH: usize = 7 + Self::MAXIMUM_DATA_LENGTH; + const CRC: Crc = Crc::::new(&CRC_32_MPEG_2); - /// This function constructs a byte array, containing the raw bytes that can be sent + /// Calculates the CRC32 MPEG-2 checksum for the contained data. For variants other than Self::DATA, 0 is returned + pub fn checksum(&self) -> u32 { + if let Self::DATA(data) = self { + Self::CRC.checksum(&data) + } else { + 0 + } + } + pub fn serialize(self) -> Vec { match self { CEPPacket::ACK => vec![0xd7], @@ -31,7 +51,92 @@ impl CEPPacket { } } - pub fn check(data: &Vec, checksum: u32) -> bool { + pub fn crc_is_valid(data: &[u8], checksum: u32) -> bool { CEPPacket::CRC.checksum(data) == checksum } + + pub const fn header(&self) -> u8 { + let header = match self { + CEPPacket::ACK => CEPPacketHeader::ACK, + CEPPacket::NACK => CEPPacketHeader::NACK, + CEPPacket::STOP => CEPPacketHeader::STOP, + CEPPacket::EOF => CEPPacketHeader::EOF, + CEPPacket::DATA(_) => CEPPacketHeader::DATA, + }; + header as u8 + } +} + +impl From<&CEPPacket> for Vec { + fn from(value: &CEPPacket) -> Self { + match value { + CEPPacket::DATA(bytes) => { + let mut v = Vec::with_capacity(7 + bytes.len()); + v.push(value.header()); + let crc32 = CEPPacket::CRC.checksum(&bytes); + v.extend((bytes.len() as u16).to_le_bytes()); + v.extend(bytes); + v.extend(crc32.to_le_bytes()); + v + } + _ => vec![value.header()], + } + } +} + +#[derive(Debug)] +pub enum CEPParseError { + WrongLength, + InvalidHeader, + InvalidCRC, +} + +impl TryFrom> for CEPPacket { + type Error = CEPParseError; + + fn try_from(mut value: Vec) -> Result { + let header_byte = value.get(0).ok_or(CEPParseError::WrongLength)?; + let header = CEPPacketHeader::from_repr(*header_byte as usize) + .ok_or(CEPParseError::InvalidHeader)?; + + let packet = match header { + CEPPacketHeader::ACK => CEPPacket::ACK, + CEPPacketHeader::NACK => CEPPacket::NACK, + CEPPacketHeader::STOP => CEPPacket::STOP, + CEPPacketHeader::EOF => CEPPacket::EOF, + CEPPacketHeader::DATA => { + let length_bytes = value.get(1..3).ok_or(CEPParseError::WrongLength)?; + let length = u16::from_le_bytes(length_bytes.try_into().unwrap()) as usize; + value.drain(0..3); + + let crc_bytes = value.drain(length..length + 4); + let crc = u32::from_le_bytes(crc_bytes.as_slice().try_into().unwrap()); + drop(crc_bytes); + + if !CEPPacket::crc_is_valid(&value, crc) { + return Err(CEPParseError::InvalidCRC); + } + + CEPPacket::DATA(value) + } + }; + + Ok(packet) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(vec![0xD7], CEPPacket::ACK)] + #[test_case(vec![0x27], CEPPacket::NACK)] + #[test_case(vec![0x59], CEPPacket::EOF)] + #[test_case(vec![0xB4], CEPPacket::STOP)] + #[test_case(vec![0x8B, 0, 0, 0xff, 0xff, 0xff, 0xff], CEPPacket::DATA(vec![]); "empty DATA packet")] + fn packet_is_parsed_and_serialized_correctly(vec: Vec, packet: CEPPacket) { + assert_eq!(&packet.clone().serialize(), &vec); + assert_eq!(CEPPacket::try_from(vec).unwrap(), packet); + } } diff --git a/src/communication/communication.rs b/src/communication/communication.rs index 71c74ad..6aa3e20 100644 --- a/src/communication/communication.rs +++ b/src/communication/communication.rs @@ -1,85 +1,92 @@ -use super::CEPPacket; +use std::{ + io::{Read, Write}, + time::Duration, +}; + +use super::{cep::CEPPacketHeader, CEPPacket}; pub type ComResult = Result; -pub trait CommunicationHandle { - /// Sends the bytes to the COBC, packaged accordingly. This function shall block until all data - /// is sent. By returning a [`CommunicationError::InterfaceError`] it can signal that the underlying driver failed. - fn send(&mut self, bytes: Vec) -> ComResult<()>; - - /// Blocks until byte_count are received or the timeout is reached. A [`CommunicationError`] can signal that it failed - /// or timed out. - fn receive(&mut self, byte_count: u16, timeout: &std::time::Duration) -> ComResult>; - - /// Sends the supplied packet - fn send_packet(&mut self, p: CEPPacket) -> ComResult<()> { - if matches!(&p, CEPPacket::DATA(_)) { - loop { - self.send(p.clone().serialize())?; - match self.receive_packet(&std::time::Duration::MAX) { - Ok(CEPPacket::ACK) => break, - Ok(CEPPacket::NACK) => log::warn!("Received NACK after DATA, resending..."), - Ok(CEPPacket::STOP) => return Err(CommunicationError::STOPCondition), - Ok(_) => return Err(CommunicationError::PacketInvalidError), - Err(e) => return Err(e), - } - } - } else { - self.send(p.serialize())?; +pub trait CommunicationHandle: Read + Write { + const INTEGRITY_ACK_TIMEOUT: Duration; + const UNLIMITED_TIMEOUT: Duration; + + fn set_timeout(&mut self, timeout: &Duration); + + fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { + self.write_all(&[packet.header()])?; + + if let CEPPacket::DATA(data) = packet { + self.write_all(&(data.len() as u16).to_le_bytes())?; + self.write_all(&data)?; + self.write_all(&packet.checksum().to_le_bytes())?; + self.flush()?; + + self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; } Ok(()) } - /// Blocks until it receives a CEPPacket - fn receive_packet(&mut self, timeout: &std::time::Duration) -> ComResult { - let pack = match self.receive(1, timeout)?[0] { - 0xd7 => CEPPacket::ACK, - 0x27 => CEPPacket::NACK, - 0xb4 => CEPPacket::STOP, - 0x59 => CEPPacket::EOF, - 0x8b => { - let length_field = self.receive(2, timeout)?; - let length = u16::from_le_bytes([length_field[0], length_field[1]]); - let bytes = self.receive(length, timeout)?; - let crc_field = self.receive(4, timeout)?; - let crc = - u32::from_le_bytes([crc_field[0], crc_field[1], crc_field[2], crc_field[3]]); - if !CEPPacket::check(&bytes, crc) { + fn send_multi_packet(&mut self, bytes: &[u8]) -> ComResult<()> { + let chunks = bytes.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); + for chunk in chunks { + self.send_packet(&CEPPacket::DATA(chunk.into()))?; + } + + self.send_packet(&CEPPacket::EOF)?; + self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; + + Ok(()) + } + + fn receive_packet(&mut self) -> ComResult { + let mut header_buffer = [0; 1]; + self.read_exact(&mut header_buffer)?; + + let header = CEPPacketHeader::from_repr(header_buffer[0] as usize) + .ok_or(CommunicationError::PacketInvalidError)?; + let packet = match header { + CEPPacketHeader::ACK => CEPPacket::ACK, + CEPPacketHeader::NACK => CEPPacket::NACK, + CEPPacketHeader::STOP => CEPPacket::STOP, + CEPPacketHeader::EOF => CEPPacket::EOF, + CEPPacketHeader::DATA => { + let mut length_buffer = [0; 2]; + self.read_exact(&mut length_buffer)?; + let length = u16::from_le_bytes(length_buffer); + + let mut data_buffer = vec![0; length as usize]; + self.read_exact(&mut data_buffer)?; + + let mut crc_buffer = [0; 4]; + self.read_exact(&mut crc_buffer)?; + if !CEPPacket::crc_is_valid(&data_buffer, u32::from_le_bytes(crc_buffer)) { return Err(CommunicationError::CRCError); - } else { - CEPPacket::DATA(bytes) } - } - _ => { - return Err(CommunicationError::PacketInvalidError); + + self.send_packet(&CEPPacket::ACK)?; + CEPPacket::DATA(data_buffer) } }; - Ok(pack) + //self.set_timeout(&Duration::MAX); + Ok(packet) } - /// Attempts to continously receive multidata packets and returns them in a concatenated byte vector - /// `stop_fn` is evaluated after every packet and terminates the communication with a STOP packet if true - /// An error is returned in this case - fn receive_multi_packet( - &mut self, - timeout: &std::time::Duration, - stop_fn: impl Fn() -> bool, - ) -> ComResult> { + fn receive_multi_packet(&mut self, stop_fn: impl Fn() -> bool) -> ComResult> { let mut buffer = Vec::new(); loop { - let pack = self.receive_packet(timeout); + let pack = self.receive_packet(); if stop_fn() { - self.send_packet(CEPPacket::STOP)?; + self.send_packet(&CEPPacket::STOP)?; return Err(CommunicationError::STOPCondition); } match pack { Ok(CEPPacket::DATA(b)) => { buffer.extend(b); - self.send_packet(CEPPacket::ACK)?; } Ok(CEPPacket::EOF) => { break; @@ -87,48 +94,40 @@ pub trait CommunicationHandle { Ok(CEPPacket::STOP) => { return Err(CommunicationError::STOPCondition); } - Err(CommunicationError::InterfaceError) => { - return Err(CommunicationError::InterfaceError); + Err(e @ CommunicationError::Io(_)) => { + return Err(e); } - Err(CommunicationError::TimeoutError) => { + Err(CommunicationError::TimedOut) => { log::error!("Receive multipacket timed out"); - return Err(CommunicationError::TimeoutError); + return Err(CommunicationError::TimedOut); } e => { log::error!("Received invalid data {:?}", e); - self.send_packet(CEPPacket::NACK)?; + self.send_packet(&CEPPacket::NACK)?; } }; } - self.send_packet(CEPPacket::ACK)?; + self.send_packet(&CEPPacket::ACK)?; Ok(buffer) } - fn send_multi_packet( - &mut self, - bytes: Vec, - timeout: &std::time::Duration, - ) -> ComResult<()> { - let num_packets = bytes.len() / 32768 + 1; - let chunks: Vec<&[u8]> = bytes.chunks(32768).collect(); - - let mut i = 0; - loop { - if i == num_packets { - break; - } - - self.send_packet(CEPPacket::DATA(chunks[i].to_vec()))?; - i += 1; + fn await_ack(&mut self, timeout: &Duration) -> ComResult<()> { + self.set_timeout(timeout); + match self.receive_packet()? { + CEPPacket::ACK => Ok(()), + CEPPacket::NACK => Err(CommunicationError::NotAcknowledged), + _ => Err(CommunicationError::PacketInvalidError) } + } +} - self.send_packet(CEPPacket::EOF)?; - if self.receive_packet(timeout)? != CEPPacket::ACK { - return Err(CommunicationError::PacketInvalidError); - } +impl CommunicationHandle for Box { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; - Ok(()) + fn set_timeout(&mut self, timeout: &Duration) { + serialport::SerialPort::set_timeout(self.as_mut(), *timeout).unwrap() } } @@ -139,11 +138,13 @@ pub enum CommunicationError { /// Signals that the CRC checksum of a data packet was wrong CRCError, /// Signals that the underlying sending or receiving failed. Not recoverable on its own. - InterfaceError, + Io(std::io::Error), /// Signals that a multi packet receive or send was interrupted by a STOP condition STOPCondition, /// Signals that a receive timed out - TimeoutError, + TimedOut, + /// NACK was received when ACK was expected + NotAcknowledged } impl std::fmt::Display for CommunicationError { @@ -152,4 +153,88 @@ impl std::fmt::Display for CommunicationError { } } +impl From for CommunicationError { + fn from(value: std::io::Error) -> Self { + if value.kind() == std::io::ErrorKind::TimedOut { + CommunicationError::TimedOut + } else { + CommunicationError::Io(value) + } + } +} + impl std::error::Error for CommunicationError {} + +#[cfg(test)] +pub mod tests { + use super::*; + use test_case::test_case; + + #[derive(Default)] + pub struct TestComHandle { + pub written_data: Vec, + pub data_to_read: Vec, + } + impl Read for TestComHandle { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + buf.copy_from_slice(&self.data_to_read[0..buf.len()]); + self.data_to_read.drain(0..buf.len()); + Ok(buf.len()) + } + } + impl Write for TestComHandle { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.written_data.extend(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + impl CommunicationHandle for TestComHandle { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + fn set_timeout(&mut self, _timeout: &Duration) {} + } + + #[test_case(CEPPacket::ACK)] + #[test_case(CEPPacket::NACK)] + #[test_case(CEPPacket::STOP)] + #[test_case(CEPPacket::EOF)] + #[test_case(CEPPacket::DATA(vec![1, 2, 3]))] + + fn packet_is_sent_correctly(packet: CEPPacket) { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut CEPPacket::ACK.serialize()); + + com.send_packet(&packet).unwrap(); + + assert_eq!(com.written_data, packet.serialize()); + } + + #[test_case(CEPPacket::ACK)] + #[test_case(CEPPacket::NACK)] + #[test_case(CEPPacket::STOP)] + #[test_case(CEPPacket::EOF)] + #[test_case(CEPPacket::DATA(vec![1, 2, 3]))] + fn packet_is_received_correctly(packet: CEPPacket) { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut packet.clone().serialize()); + + assert_eq!(com.receive_packet().unwrap(), packet); + + if matches!(packet, CEPPacket::DATA(_)) { + assert_eq!(com.written_data, CEPPacket::ACK.serialize()); + } + } + + #[test] + fn error_on_nack() { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut CEPPacket::NACK.serialize()); + + let ret = com.send_packet(&CEPPacket::DATA(vec![1, 2, 3])).unwrap_err(); + assert!(matches!(ret, CommunicationError::NotAcknowledged)); + } +} diff --git a/src/communication/mod.rs b/src/communication/mod.rs index 8eb1ce0..d34dea9 100644 --- a/src/communication/mod.rs +++ b/src/communication/mod.rs @@ -1,7 +1,5 @@ mod cep; mod communication; -mod uart; pub use cep::CEPPacket; pub use communication::*; -pub use uart::*; diff --git a/src/communication/uart.rs b/src/communication/uart.rs deleted file mode 100644 index 1adec88..0000000 --- a/src/communication/uart.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::time::Duration; - -use crate::communication::{ComResult, CommunicationHandle}; -use rppal::uart::{Parity, Uart}; - -use super::CommunicationError; - -//Constants -const DATA_BITS: u8 = 8; -const STOP_BITS: u8 = 1; -const ALLOWED_SEND_RETRIES: u8 = 3; - -pub struct UARTHandle { - uart_PI: Uart, -} - -impl UARTHandle { - /// # ISSUE! - /// By Default UART Pins initiated this way will be TX: 14 and RX: 15. This is not coherent with PDD. - /// Pins need to be changed somehow - /// - /// ## Arguments - /// * `baud` - Bits per second - /// - /// ## Returns: - /// A `UARTHandle`(r) that uses Raspberry Pi's UART Peripheral - /// - pub fn new(path: &str, baud: u32) -> UARTHandle { - let mut uart_handler: UARTHandle = UARTHandle { - uart_PI: Uart::with_path(path, baud, Parity::None, DATA_BITS, STOP_BITS).unwrap(), - }; - - let _ = uart_handler.uart_PI.set_write_mode(true); - - uart_handler - } -} - -impl CommunicationHandle for UARTHandle { - /// Sends the given bytes via UART - /// ## Arguments - /// * `bytes` - a raw byte vector to send - /// ## Returns - /// * `Ok`: Sent correctly - /// * `InterfaceError`: UART Peripheral returns an error or failed to send everything - fn send(&mut self, bytes: Vec) -> ComResult<()> { - let mut sent_bytes: usize = 0; - - for _ in 0..=ALLOWED_SEND_RETRIES { - sent_bytes += self.uart_PI.write(&bytes[sent_bytes..]).unwrap(); - - if sent_bytes == bytes.len() { - return Ok(()); - } - } - Err(CommunicationError::InterfaceError) - } - - /// # Incomplete - /// Does not honor the supplied timeout. Planned for after HAF - /// - /// Receives a byte packet of some exepected length with a timeout (non inter-byte). Function while retry after failed attemps indefinitely during the given timeout - /// while the whole amount of expected bytes hasn't been received. - /// ## Arguments - /// * `byte_count`: the amount of bytes that is expected - /// * `timeout`: timeout for the set_read_mode from rppal. Global time of the function. Not inter-byte - /// ## Returns - /// A vector of bytes - fn receive(&mut self, byte_count: u16, _timeout: &std::time::Duration) -> ComResult> { - //Data buffer - let mut received_data_buffer: Vec = vec![0; byte_count as usize]; - - let mut received_bytes_counter: usize = 0; - let mut read_byte_count: u8; - - while received_bytes_counter < byte_count as usize { - read_byte_count = std::cmp::min(byte_count, 255) as u8; - self.uart_PI.set_read_mode(read_byte_count, Duration::ZERO).unwrap(); - - received_bytes_counter += - self.uart_PI.read(&mut received_data_buffer[received_bytes_counter..]).unwrap(); - } - - Ok(received_data_buffer) - } -} - -impl From for CommunicationError { - fn from(_: rppal::uart::Error) -> Self { - CommunicationError::InterfaceError - } -} diff --git a/src/lib.rs b/src/lib.rs index 5711b0c..9d296f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ +#![allow(non_snake_case)] pub mod command; pub mod communication; diff --git a/src/main.rs b/src/main.rs index 7c2698c..d396240 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ +#![allow(non_snake_case)] use core::time; use rppal::gpio::Gpio; use std::{ - sync::{Arc, Mutex}, thread, + time::Duration, }; +use STS1_EDU_Scheduler::communication::CommunicationHandle; use simplelog as sl; @@ -37,11 +39,11 @@ fn main() -> ! { log::info!("Scheduler started"); // construct a wrapper for UART communication - let mut com = communication::UARTHandle::new(&config.uart, config.baudrate); + let mut com = serialport::new(&config.uart, config.baudrate).open().unwrap(); + com.set_timeout(&Duration::from_secs(60)); // construct a wrapper for resources that are shared between different commands - let ec = command::ExecutionContext::new("events".to_string(), config.update_pin).unwrap(); - let mut exec = Arc::new(Mutex::new(ec)); + let mut exec = command::ExecutionContext::new("events".to_string(), config.update_pin).unwrap(); // start a thread that will update the heartbeat pin thread::spawn(move || heartbeat_loop(config.heartbeat_pin, config.heartbeat_freq)); diff --git a/tests/simulation/logging.rs b/tests/simulation/logging.rs index 1fe4de5..a52e57e 100644 --- a/tests/simulation/logging.rs +++ b/tests/simulation/logging.rs @@ -16,11 +16,11 @@ fn logfile_is_cleared_after_sent() -> std::io::Result<()> { let mut cobc_in = serial_port.stdout.take().unwrap(); let mut cobc_out = serial_port.stdin.take().unwrap(); - simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1)?; - simulate_execute_program(&mut cobc_in, &mut cobc_out, 1, 0, 3)?; + simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1).unwrap(); + simulate_execute_program(&mut cobc_in, &mut cobc_out, 1, 0, 3).unwrap(); std::thread::sleep(std::time::Duration::from_millis(100)); - let _ = simulate_return_result(&mut cobc_in, &mut cobc_out, 1, 0)?; - cobc_out.write_all(&CEPPacket::ACK.serialize())?; + let _ = simulate_return_result(&mut cobc_in, &mut cobc_out, 1, 0).unwrap(); + cobc_out.write_all(&CEPPacket::ACK.serialize()).unwrap(); std::thread::sleep(std::time::Duration::from_millis(100)); scheduler.kill().unwrap(); diff --git a/tests/simulation/mod.rs b/tests/simulation/mod.rs index 4a96d33..c6e96c7 100644 --- a/tests/simulation/mod.rs +++ b/tests/simulation/mod.rs @@ -2,12 +2,10 @@ mod command_execution; mod logging; use std::{ - fmt::format, io::{Read, Write}, process::{Child, Stdio}, }; -use crate::software_tests::common::*; use STS1_EDU_Scheduler::communication::CEPPacket; fn get_config_str(unique: &str) -> String { @@ -140,3 +138,32 @@ pub fn read_multi_data_packets( output.write_all(&CEPPacket::ACK.serialize())?; Ok(data) } + +pub fn store_archive(program_id: u16) -> Vec { + let mut vec = vec![1u8]; + vec.extend(program_id.to_le_bytes()); + vec +} + +pub fn execute_program(program_id: u16, timestamp: u32, timeout: u16) -> Vec { + let mut vec = vec![2u8]; + vec.extend(program_id.to_le_bytes()); + vec.extend(timestamp.to_le_bytes()); + vec.extend(timeout.to_le_bytes()); + vec +} + +pub fn stop_program() -> Vec { + vec![3u8] +} + +pub fn get_status() -> Vec { + vec![4u8] +} + +pub fn return_result(program_id: u16, timestamp: u32) -> Vec { + let mut vec = vec![5u8]; + vec.extend(program_id.to_le_bytes()); + vec.extend(timestamp.to_le_bytes()); + vec +} diff --git a/tests/software_tests/command_integration.rs b/tests/software_tests/command_integration.rs index 7dce9cc..a0df11f 100644 --- a/tests/software_tests/command_integration.rs +++ b/tests/software_tests/command_integration.rs @@ -1,7 +1,5 @@ -use std::fs; -use std::io::{Read, Write}; -use STS1_EDU_Scheduler::command::{self, CommandError}; -use STS1_EDU_Scheduler::communication::{CEPPacket::*, CommunicationError}; +use STS1_EDU_Scheduler::command; +use STS1_EDU_Scheduler::communication::CEPPacket::*; use crate::software_tests::common; use crate::software_tests::common::ComEvent::*; @@ -12,15 +10,15 @@ type TestResult = Result<(), Box>; fn invalid_packets_from_cobc() -> TestResult { let packets = vec![ COBC(DATA(vec![1, 2])), + EDU(ACK), EDU(NACK), COBC(DATA(vec![2, 0, 1])), - EDU(NACK), - COBC_INVALID(vec![0x8b, 2, 0, 0, 0, 0, 0, 1, 10]), // Invalid CRC + EDU(ACK), EDU(NACK), ]; let (mut com, mut exec) = common::prepare_handles(packets, "13"); - for _ in 0..3 { + for _ in 0..2 { command::handle_command(&mut com, &mut exec); } diff --git a/tests/software_tests/common.rs b/tests/software_tests/common.rs index 1de366b..fc33d06 100644 --- a/tests/software_tests/common.rs +++ b/tests/software_tests/common.rs @@ -1,7 +1,8 @@ use std::{ + collections::VecDeque, + fmt::Debug, io::{Read, Write}, - process::{Child, Stdio}, - sync::{Arc, Mutex}, + time::Duration, }; use STS1_EDU_Scheduler::{ @@ -12,7 +13,6 @@ use STS1_EDU_Scheduler::{ pub enum ComEvent { /// EDU shall want to receive the given packet COBC(CEPPacket), - COBC_INVALID(Vec), /// EDU shall send the given packet EDU(CEPPacket), /// Makes the thread sleep for the given duration. Can be used to wait for execution to complete @@ -20,97 +20,89 @@ pub enum ComEvent { /// Allow the EDU to send any packet ANY, /// EDU shall send a packet, which is then passed to a given function (e.g. to allow for further checks on data) - ACTION(Box)>), + ACTION(Box), +} + +impl Debug for ComEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::COBC(arg0) => f.debug_tuple("COBC").field(arg0).finish(), + Self::EDU(arg0) => f.debug_tuple("EDU").field(arg0).finish(), + Self::SLEEP(arg0) => f.debug_tuple("SLEEP").field(arg0).finish(), + Self::ANY => write!(f, "ANY"), + Self::ACTION(_) => f.debug_tuple("ACTION").finish(), + } + } } /// This communciation handle can simulate what is going on between EDU and COBC. Any send or receive call is /// checked against the supplied expected events vector pub struct TestCom { - expected_events: Vec, - receive_queue: Vec, - index: usize, + expected_events: VecDeque, } impl CommunicationHandle for TestCom { - fn send(&mut self, bytes: Vec) -> ComResult<()> { - match &self.expected_events[self.index] { - ComEvent::EDU(p) => { - assert_eq!( - bytes, - p.clone().serialize(), - "Wrong packet #{}, EDU: {:?}, should be {:?}", - self.index, - bytes, - p - ); - self.index += 1; - Ok(()) - } - ComEvent::SLEEP(d) => { - std::thread::sleep(*d); - self.index += 1; - Ok(()) - } - ComEvent::ANY => { - self.index += 1; - Ok(()) - } - ComEvent::ACTION(f) => { - f(bytes); - self.index += 1; - Ok(()) - } - _ => { - panic!("EDU should not send now, index {}", self.index); - } + fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { + println!("Sent {packet:?}"); + match self.expected_events.pop_front().unwrap() { + ComEvent::EDU(p) => assert_eq!(&p, packet), + ComEvent::SLEEP(d) => std::thread::sleep(d), + ComEvent::ANY => (), + ComEvent::ACTION(f) => f(packet), + event @ _ => panic!("Expected {event:?} instead of send_packet"), + } + + if matches!(packet, CEPPacket::DATA(_)) { + self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; } + + Ok(()) } - fn receive(&mut self, n: u16, timeout: &std::time::Duration) -> ComResult> { - match &self.expected_events[self.index] { + fn receive_packet(&mut self) -> ComResult { + match self.expected_events.pop_front().unwrap() { ComEvent::COBC(p) => { - if !self.receive_queue.is_empty() { - let res: Vec = self.receive_queue.drain(0..(n as usize)).collect(); - if self.receive_queue.is_empty() { - self.index += 1; - } - Ok(res) - } else { - self.receive_queue.append(&mut p.clone().serialize()); - self.receive(n, timeout) - } - } - ComEvent::COBC_INVALID(b) => { - if !self.receive_queue.is_empty() { - let res: Vec = self.receive_queue.drain(0..(n as usize)).collect(); - if self.receive_queue.is_empty() { - self.index += 1; - } - Ok(res) - } else { - self.receive_queue.append(&mut b.clone()); - self.receive(n, timeout) + println!("Received {p:?}"); + if matches!(p, CEPPacket::DATA(_)) { + self.send_packet(&CEPPacket::ACK)?; } + Ok(p) } ComEvent::SLEEP(d) => { - std::thread::sleep(*d); - self.index += 1; - self.receive(n, timeout) - } - _ => { - panic!("EDU should send now, index {}", self.index); + std::thread::sleep(d); + self.receive_packet() } + event @ _ => panic!("Expected {event:?} instead of receive_packet"), } } + + const INTEGRITY_ACK_TIMEOUT: std::time::Duration = Duration::MAX; + const UNLIMITED_TIMEOUT: std::time::Duration = Duration::MAX; + + fn set_timeout(&mut self, _timeout: &std::time::Duration) {} } impl TestCom { pub fn new(packets: Vec) -> Self { - TestCom { expected_events: packets, receive_queue: vec![], index: 0 } + TestCom { expected_events: packets.into() } } pub fn is_complete(&self) -> bool { - self.index == self.expected_events.len() + self.expected_events.is_empty() + } +} + +impl Read for TestCom { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + Ok(buf.len()) + } +} +impl Write for TestCom { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) } } @@ -139,8 +131,7 @@ pub fn prepare_handles(packets: Vec, unique: &str) -> (TestCom, SyncEx file_per_thread_logger::allow_uninitialized(); file_per_thread_logger::initialize("tests/tmp/log-"); let com = TestCom::new(packets); - let ec = ExecutionContext::new(format!("tests/tmp/{unique}"), 12).unwrap(); - let exec = Arc::new(Mutex::new(ec)); + let exec = ExecutionContext::new(format!("tests/tmp/{unique}"), 12).unwrap(); (com, exec) } diff --git a/tests/software_tests/return_result.rs b/tests/software_tests/return_result.rs index b108e36..cea5829 100644 --- a/tests/software_tests/return_result.rs +++ b/tests/software_tests/return_result.rs @@ -25,8 +25,11 @@ fn returns_result_correctly() -> TestResult { COBC(ACK), COBC(DATA(return_result(7, 3))), EDU(ACK), - ACTION(Box::new(|bytes| { - std::fs::File::create("tests/tmp/7.zip").unwrap().write(&bytes).unwrap(); + ACTION(Box::new(|packet| { + std::fs::File::create("tests/tmp/7.zip") + .unwrap() + .write(&packet.clone().serialize()) + .unwrap(); })), COBC(ACK), EDU(EOF), diff --git a/tests/software_tests/store_archive.rs b/tests/software_tests/store_archive.rs index ac82d57..a7d19e3 100644 --- a/tests/software_tests/store_archive.rs +++ b/tests/software_tests/store_archive.rs @@ -63,37 +63,3 @@ fn stopped_store() -> TestResult { common::cleanup("4"); Ok(()) } - -#[test] -fn invalid_crc() -> TestResult { - let mut bytes = std::fs::read("./tests/student_program.zip")?; - let packets = vec![ - COBC(DATA(vec![1, 14, 0])), - EDU(ACK), - COBC(DATA(bytes.drain(0..20).collect())), - EDU(ACK), - COBC_INVALID(vec![0x8b, 5, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10]), - EDU(NACK), - COBC(DATA(bytes)), - EDU(ACK), - COBC(EOF), - EDU(ACK), - EDU(ACK), - ]; - let (mut com, mut exec) = common::prepare_handles(packets, "14"); - - command::handle_command(&mut com, &mut exec); - assert!(com.is_complete()); - - assert_eq!( - 0, - std::process::Command::new("diff") // check wether the archive was stored correctly - .args(["-yq", "--strip-trailing-cr", "tests/test_data", "archives/14"]) - .status()? - .code() - .unwrap() - ); - - common::cleanup("14"); - Ok(()) -} diff --git a/tests/tests.rs b/tests/tests.rs index 1f20ca4..df7fe93 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,5 +1,4 @@ -/// This directory contains tests that only use command::handle_command(). This makes them ideal for debugging and finegrained tests -mod software_tests; - /// This directory contains tests that run the actual compiled binaries and uses socat to create a simulated serial port. mod simulation; + +mod software_tests; From a7661cd5086644973763175a13f3150ff1497984 Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Sat, 16 Dec 2023 16:51:03 +0100 Subject: [PATCH 02/10] address long outstanding clippy warnings --- src/command/common.rs | 2 +- src/command/error.rs | 2 +- src/command/execute_program.rs | 4 +- src/command/get_status.rs | 6 +- src/command/mod.rs | 4 +- src/command/return_result.rs | 4 +- src/command/stop_program.rs | 2 +- src/command/store_archive.rs | 2 +- src/command/update_time.rs | 2 +- src/communication/cep.rs | 74 +++--- src/communication/communication.rs | 240 ------------------- src/communication/mod.rs | 244 +++++++++++++++++++- src/main.rs | 4 +- tests/simulation/logging.rs | 2 +- tests/simulation/mod.rs | 20 +- tests/software_tests/command_integration.rs | 20 +- tests/software_tests/common.rs | 10 +- tests/software_tests/communication_tests.rs | 4 +- tests/software_tests/execute_program.rs | 22 +- tests/software_tests/get_status.rs | 52 ++--- tests/software_tests/return_result.rs | 106 ++++----- tests/software_tests/stop_program.rs | 22 +- tests/software_tests/store_archive.rs | 28 +-- 23 files changed, 437 insertions(+), 439 deletions(-) diff --git a/src/command/common.rs b/src/command/common.rs index 5099cc2..2b33620 100644 --- a/src/command/common.rs +++ b/src/command/common.rs @@ -6,7 +6,7 @@ pub fn check_length(com: &mut impl CommunicationHandle, vec: &Vec, n: usize) let actual_len = vec.len(); if actual_len != n { log::error!("Command came with {actual_len} bytes, should have {n}"); - com.send_packet(&CEPPacket::NACK)?; + com.send_packet(&CEPPacket::Nack)?; Err(CommandError::ProtocolViolation( format!("Received command with {actual_len} bytes, expected {n}").into(), )) diff --git a/src/command/error.rs b/src/command/error.rs index 1cbfe0c..566f7a9 100644 --- a/src/command/error.rs +++ b/src/command/error.rs @@ -27,7 +27,7 @@ impl From for CommandError { CommunicationError::PacketInvalidError => CommandError::External(Box::new(e)), CommunicationError::CRCError => CommandError::ProtocolViolation(Box::new(e)), CommunicationError::Io(_) => CommandError::NonRecoverable(Box::new(e)), - CommunicationError::STOPCondition => CommandError::External(Box::new(e)), + CommunicationError::StopCondition => CommandError::External(Box::new(e)), CommunicationError::NotAcknowledged => CommandError::ProtocolViolation(Box::new(e)), CommunicationError::TimedOut => todo!("Timeout not yet specified"), } diff --git a/src/command/execute_program.rs b/src/command/execute_program.rs index 52b78b8..1d3c832 100644 --- a/src/command/execute_program.rs +++ b/src/command/execute_program.rs @@ -31,7 +31,7 @@ pub fn execute_program( let student_process = match create_student_process(program_id, timestamp) { Ok(p) => p, Err(e) => { - com.send_packet(&CEPPacket::NACK)?; + com.send_packet(&CEPPacket::Nack)?; return Err(e); } }; @@ -60,7 +60,7 @@ pub fn execute_program( l_context.running_flag = true; drop(l_context); - com.send_packet(&CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/command/get_status.rs b/src/command/get_status.rs index dc56a74..973fec1 100644 --- a/src/command/get_status.rs +++ b/src/command/get_status.rs @@ -13,7 +13,7 @@ pub fn get_status( let mut l_exec = exec.lock().unwrap(); if !l_exec.has_data_ready() { - com.send_packet(&CEPPacket::DATA(vec![0]))?; + com.send_packet(&CEPPacket::Data(vec![0]))?; return Ok(()); } @@ -21,11 +21,11 @@ pub fn get_status( l_exec.event_vec.as_ref().iter().position(|x| matches!(x, Event::Status(_))) { let event = l_exec.event_vec[index]; - com.send_packet(&CEPPacket::DATA(event.to_bytes()))?; + com.send_packet(&CEPPacket::Data(event.to_bytes()))?; l_exec.event_vec.remove(index)?; } else { let event = *l_exec.event_vec.as_ref().last().unwrap(); // Safe, because we know it is not empty - com.send_packet(&CEPPacket::DATA(event.to_bytes()))?; + com.send_packet(&CEPPacket::Data(event.to_bytes()))?; if !matches!(event, Event::Result(_)) { // Results are removed when deleted diff --git a/src/command/mod.rs b/src/command/mod.rs index ffd2479..28e5675 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -49,10 +49,10 @@ pub fn process_command( ) -> CommandResult { let packet = com.receive_packet()?; let data = match packet { - CEPPacket::DATA(data) => data, + CEPPacket::Data(data) => data, _ => { return Err(CommandError::NonRecoverable( - format!("Received {:?} as command start, expected DATA", packet).into(), + format!("Received {:?} as command start, expected Data", packet).into(), )); } }; diff --git a/src/command/return_result.rs b/src/command/return_result.rs index 10b2723..109a886 100644 --- a/src/command/return_result.rs +++ b/src/command/return_result.rs @@ -7,7 +7,7 @@ use crate::{ use super::{truncate_to_size, CommandResult, SyncExecutionContext}; -/// Handles a complete return result command. The result zip file is only deleted if a final ACK is +/// Handles a complete return result command. The result zip file is only deleted if a final Ack is /// received. pub fn return_result( data: Vec, @@ -21,7 +21,7 @@ pub fn return_result( let result_path = format!("./data/{}_{}.zip", program_id, timestamp); if !std::path::Path::new(&result_path).exists() { - com.send_packet(&CEPPacket::NACK)?; + com.send_packet(&CEPPacket::Nack)?; return Err(CommandError::ProtocolViolation( format!("Result {}:{} does not exist", program_id, timestamp).into(), )); diff --git a/src/command/stop_program.rs b/src/command/stop_program.rs index 1da72b1..b2856ee 100644 --- a/src/command/stop_program.rs +++ b/src/command/stop_program.rs @@ -12,6 +12,6 @@ pub fn stop_program( terminate_student_program(exec).expect("to terminate student program"); - com.send_packet(&CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/command/store_archive.rs b/src/command/store_archive.rs index 6c55f04..fac5ea1 100644 --- a/src/command/store_archive.rs +++ b/src/command/store_archive.rs @@ -20,7 +20,7 @@ pub fn store_archive( let bytes = com.receive_multi_packet(|| false)?; // !! TODO !! unpack_archive(id, bytes)?; - com.send_packet(&CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/command/update_time.rs b/src/command/update_time.rs index 5eb8eed..14e5f27 100644 --- a/src/command/update_time.rs +++ b/src/command/update_time.rs @@ -15,7 +15,7 @@ pub fn update_time( let time = i32::from_le_bytes([data[1], data[2], data[3], data[4]]); set_system_time(time)?; - com.send_packet(&CEPPacket::ACK)?; + com.send_packet(&CEPPacket::Ack)?; Ok(()) } diff --git a/src/communication/cep.rs b/src/communication/cep.rs index 4608bab..c4ec597 100644 --- a/src/communication/cep.rs +++ b/src/communication/cep.rs @@ -2,20 +2,20 @@ use crc::{Crc, CRC_32_MPEG_2}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum CEPPacket { - ACK, - NACK, - STOP, - EOF, - DATA(Vec), + Ack, + Nack, + Stop, + Eof, + Data(Vec), } #[derive(Clone, Copy, strum::FromRepr)] pub enum CEPPacketHeader { - ACK = 0xd7, - NACK = 0x27, - STOP = 0xb4, - EOF = 0x59, - DATA = 0x8b, + Ack = 0xd7, + Nack = 0x27, + Stop = 0xb4, + Eof = 0x59, + Data = 0x8b, } impl CEPPacket { @@ -24,10 +24,10 @@ impl CEPPacket { const CRC: Crc = Crc::::new(&CRC_32_MPEG_2); - /// Calculates the CRC32 MPEG-2 checksum for the contained data. For variants other than Self::DATA, 0 is returned + /// Calculates the CRC32 MPEG-2 checksum for the contained data. For variants other than Self::Data, 0 is returned pub fn checksum(&self) -> u32 { - if let Self::DATA(data) = self { - Self::CRC.checksum(&data) + if let Self::Data(data) = self { + Self::CRC.checksum(data) } else { 0 } @@ -35,11 +35,11 @@ impl CEPPacket { pub fn serialize(self) -> Vec { match self { - CEPPacket::ACK => vec![0xd7], - CEPPacket::NACK => vec![0x27], - CEPPacket::STOP => vec![0xb4], - CEPPacket::EOF => vec![0x59], - CEPPacket::DATA(bytes) => { + CEPPacket::Ack => vec![0xd7], + CEPPacket::Nack => vec![0x27], + CEPPacket::Stop => vec![0xb4], + CEPPacket::Eof => vec![0x59], + CEPPacket::Data(bytes) => { let mut v = vec![0x8b]; let crc32 = CEPPacket::CRC.checksum(&bytes); v.reserve_exact(6 + bytes.len()); @@ -57,11 +57,11 @@ impl CEPPacket { pub const fn header(&self) -> u8 { let header = match self { - CEPPacket::ACK => CEPPacketHeader::ACK, - CEPPacket::NACK => CEPPacketHeader::NACK, - CEPPacket::STOP => CEPPacketHeader::STOP, - CEPPacket::EOF => CEPPacketHeader::EOF, - CEPPacket::DATA(_) => CEPPacketHeader::DATA, + CEPPacket::Ack => CEPPacketHeader::Ack, + CEPPacket::Nack => CEPPacketHeader::Nack, + CEPPacket::Stop => CEPPacketHeader::Stop, + CEPPacket::Eof => CEPPacketHeader::Eof, + CEPPacket::Data(_) => CEPPacketHeader::Data, }; header as u8 } @@ -70,10 +70,10 @@ impl CEPPacket { impl From<&CEPPacket> for Vec { fn from(value: &CEPPacket) -> Self { match value { - CEPPacket::DATA(bytes) => { + CEPPacket::Data(bytes) => { let mut v = Vec::with_capacity(7 + bytes.len()); v.push(value.header()); - let crc32 = CEPPacket::CRC.checksum(&bytes); + let crc32 = CEPPacket::CRC.checksum(bytes); v.extend((bytes.len() as u16).to_le_bytes()); v.extend(bytes); v.extend(crc32.to_le_bytes()); @@ -95,16 +95,16 @@ impl TryFrom> for CEPPacket { type Error = CEPParseError; fn try_from(mut value: Vec) -> Result { - let header_byte = value.get(0).ok_or(CEPParseError::WrongLength)?; + let header_byte = value.first().ok_or(CEPParseError::WrongLength)?; let header = CEPPacketHeader::from_repr(*header_byte as usize) .ok_or(CEPParseError::InvalidHeader)?; let packet = match header { - CEPPacketHeader::ACK => CEPPacket::ACK, - CEPPacketHeader::NACK => CEPPacket::NACK, - CEPPacketHeader::STOP => CEPPacket::STOP, - CEPPacketHeader::EOF => CEPPacket::EOF, - CEPPacketHeader::DATA => { + CEPPacketHeader::Ack => CEPPacket::Ack, + CEPPacketHeader::Nack => CEPPacket::Nack, + CEPPacketHeader::Stop => CEPPacket::Stop, + CEPPacketHeader::Eof => CEPPacket::Eof, + CEPPacketHeader::Data => { let length_bytes = value.get(1..3).ok_or(CEPParseError::WrongLength)?; let length = u16::from_le_bytes(length_bytes.try_into().unwrap()) as usize; value.drain(0..3); @@ -117,7 +117,7 @@ impl TryFrom> for CEPPacket { return Err(CEPParseError::InvalidCRC); } - CEPPacket::DATA(value) + CEPPacket::Data(value) } }; @@ -130,11 +130,11 @@ mod tests { use super::*; use test_case::test_case; - #[test_case(vec![0xD7], CEPPacket::ACK)] - #[test_case(vec![0x27], CEPPacket::NACK)] - #[test_case(vec![0x59], CEPPacket::EOF)] - #[test_case(vec![0xB4], CEPPacket::STOP)] - #[test_case(vec![0x8B, 0, 0, 0xff, 0xff, 0xff, 0xff], CEPPacket::DATA(vec![]); "empty DATA packet")] + #[test_case(vec![0xD7], CEPPacket::Ack)] + #[test_case(vec![0x27], CEPPacket::Nack)] + #[test_case(vec![0x59], CEPPacket::Eof)] + #[test_case(vec![0xB4], CEPPacket::Stop)] + #[test_case(vec![0x8B, 0, 0, 0xff, 0xff, 0xff, 0xff], CEPPacket::Data(vec![]); "empty Data packet")] fn packet_is_parsed_and_serialized_correctly(vec: Vec, packet: CEPPacket) { assert_eq!(&packet.clone().serialize(), &vec); assert_eq!(CEPPacket::try_from(vec).unwrap(), packet); diff --git a/src/communication/communication.rs b/src/communication/communication.rs index 6aa3e20..e69de29 100644 --- a/src/communication/communication.rs +++ b/src/communication/communication.rs @@ -1,240 +0,0 @@ -use std::{ - io::{Read, Write}, - time::Duration, -}; - -use super::{cep::CEPPacketHeader, CEPPacket}; - -pub type ComResult = Result; - -pub trait CommunicationHandle: Read + Write { - const INTEGRITY_ACK_TIMEOUT: Duration; - const UNLIMITED_TIMEOUT: Duration; - - fn set_timeout(&mut self, timeout: &Duration); - - fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { - self.write_all(&[packet.header()])?; - - if let CEPPacket::DATA(data) = packet { - self.write_all(&(data.len() as u16).to_le_bytes())?; - self.write_all(&data)?; - self.write_all(&packet.checksum().to_le_bytes())?; - self.flush()?; - - self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; - } - - Ok(()) - } - - fn send_multi_packet(&mut self, bytes: &[u8]) -> ComResult<()> { - let chunks = bytes.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); - for chunk in chunks { - self.send_packet(&CEPPacket::DATA(chunk.into()))?; - } - - self.send_packet(&CEPPacket::EOF)?; - self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; - - Ok(()) - } - - fn receive_packet(&mut self) -> ComResult { - let mut header_buffer = [0; 1]; - self.read_exact(&mut header_buffer)?; - - let header = CEPPacketHeader::from_repr(header_buffer[0] as usize) - .ok_or(CommunicationError::PacketInvalidError)?; - let packet = match header { - CEPPacketHeader::ACK => CEPPacket::ACK, - CEPPacketHeader::NACK => CEPPacket::NACK, - CEPPacketHeader::STOP => CEPPacket::STOP, - CEPPacketHeader::EOF => CEPPacket::EOF, - CEPPacketHeader::DATA => { - let mut length_buffer = [0; 2]; - self.read_exact(&mut length_buffer)?; - let length = u16::from_le_bytes(length_buffer); - - let mut data_buffer = vec![0; length as usize]; - self.read_exact(&mut data_buffer)?; - - let mut crc_buffer = [0; 4]; - self.read_exact(&mut crc_buffer)?; - if !CEPPacket::crc_is_valid(&data_buffer, u32::from_le_bytes(crc_buffer)) { - return Err(CommunicationError::CRCError); - } - - self.send_packet(&CEPPacket::ACK)?; - CEPPacket::DATA(data_buffer) - } - }; - - //self.set_timeout(&Duration::MAX); - Ok(packet) - } - - fn receive_multi_packet(&mut self, stop_fn: impl Fn() -> bool) -> ComResult> { - let mut buffer = Vec::new(); - - loop { - let pack = self.receive_packet(); - if stop_fn() { - self.send_packet(&CEPPacket::STOP)?; - return Err(CommunicationError::STOPCondition); - } - - match pack { - Ok(CEPPacket::DATA(b)) => { - buffer.extend(b); - } - Ok(CEPPacket::EOF) => { - break; - } - Ok(CEPPacket::STOP) => { - return Err(CommunicationError::STOPCondition); - } - Err(e @ CommunicationError::Io(_)) => { - return Err(e); - } - Err(CommunicationError::TimedOut) => { - log::error!("Receive multipacket timed out"); - return Err(CommunicationError::TimedOut); - } - e => { - log::error!("Received invalid data {:?}", e); - self.send_packet(&CEPPacket::NACK)?; - } - }; - } - - self.send_packet(&CEPPacket::ACK)?; - Ok(buffer) - } - - fn await_ack(&mut self, timeout: &Duration) -> ComResult<()> { - self.set_timeout(timeout); - match self.receive_packet()? { - CEPPacket::ACK => Ok(()), - CEPPacket::NACK => Err(CommunicationError::NotAcknowledged), - _ => Err(CommunicationError::PacketInvalidError) - } - } -} - -impl CommunicationHandle for Box { - const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); - const UNLIMITED_TIMEOUT: Duration = Duration::MAX; - - fn set_timeout(&mut self, timeout: &Duration) { - serialport::SerialPort::set_timeout(self.as_mut(), *timeout).unwrap() - } -} - -#[derive(Debug)] -pub enum CommunicationError { - /// Signals that an unknown command packet was received - PacketInvalidError, - /// Signals that the CRC checksum of a data packet was wrong - CRCError, - /// Signals that the underlying sending or receiving failed. Not recoverable on its own. - Io(std::io::Error), - /// Signals that a multi packet receive or send was interrupted by a STOP condition - STOPCondition, - /// Signals that a receive timed out - TimedOut, - /// NACK was received when ACK was expected - NotAcknowledged -} - -impl std::fmt::Display for CommunicationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for CommunicationError { - fn from(value: std::io::Error) -> Self { - if value.kind() == std::io::ErrorKind::TimedOut { - CommunicationError::TimedOut - } else { - CommunicationError::Io(value) - } - } -} - -impl std::error::Error for CommunicationError {} - -#[cfg(test)] -pub mod tests { - use super::*; - use test_case::test_case; - - #[derive(Default)] - pub struct TestComHandle { - pub written_data: Vec, - pub data_to_read: Vec, - } - impl Read for TestComHandle { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - buf.copy_from_slice(&self.data_to_read[0..buf.len()]); - self.data_to_read.drain(0..buf.len()); - Ok(buf.len()) - } - } - impl Write for TestComHandle { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.written_data.extend(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } - } - impl CommunicationHandle for TestComHandle { - const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); - const UNLIMITED_TIMEOUT: Duration = Duration::MAX; - fn set_timeout(&mut self, _timeout: &Duration) {} - } - - #[test_case(CEPPacket::ACK)] - #[test_case(CEPPacket::NACK)] - #[test_case(CEPPacket::STOP)] - #[test_case(CEPPacket::EOF)] - #[test_case(CEPPacket::DATA(vec![1, 2, 3]))] - - fn packet_is_sent_correctly(packet: CEPPacket) { - let mut com = TestComHandle::default(); - com.data_to_read.append(&mut CEPPacket::ACK.serialize()); - - com.send_packet(&packet).unwrap(); - - assert_eq!(com.written_data, packet.serialize()); - } - - #[test_case(CEPPacket::ACK)] - #[test_case(CEPPacket::NACK)] - #[test_case(CEPPacket::STOP)] - #[test_case(CEPPacket::EOF)] - #[test_case(CEPPacket::DATA(vec![1, 2, 3]))] - fn packet_is_received_correctly(packet: CEPPacket) { - let mut com = TestComHandle::default(); - com.data_to_read.append(&mut packet.clone().serialize()); - - assert_eq!(com.receive_packet().unwrap(), packet); - - if matches!(packet, CEPPacket::DATA(_)) { - assert_eq!(com.written_data, CEPPacket::ACK.serialize()); - } - } - - #[test] - fn error_on_nack() { - let mut com = TestComHandle::default(); - com.data_to_read.append(&mut CEPPacket::NACK.serialize()); - - let ret = com.send_packet(&CEPPacket::DATA(vec![1, 2, 3])).unwrap_err(); - assert!(matches!(ret, CommunicationError::NotAcknowledged)); - } -} diff --git a/src/communication/mod.rs b/src/communication/mod.rs index d34dea9..7f74bd0 100644 --- a/src/communication/mod.rs +++ b/src/communication/mod.rs @@ -1,5 +1,243 @@ mod cep; -mod communication; - pub use cep::CEPPacket; -pub use communication::*; + +use std::{ + io::{Read, Write}, + time::Duration, +}; + +use self::cep::CEPPacketHeader; + +pub type ComResult = Result; + +pub trait CommunicationHandle: Read + Write { + const INTEGRITY_ACK_TIMEOUT: Duration; + const UNLIMITED_TIMEOUT: Duration; + + fn set_timeout(&mut self, timeout: &Duration); + + fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { + self.write_all(&[packet.header()])?; + + if let CEPPacket::Data(data) = packet { + self.write_all(&(data.len() as u16).to_le_bytes())?; + self.write_all(data)?; + self.write_all(&packet.checksum().to_le_bytes())?; + self.flush()?; + + self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; + } + + Ok(()) + } + + fn send_multi_packet(&mut self, bytes: &[u8]) -> ComResult<()> { + let chunks = bytes.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); + for chunk in chunks { + self.send_packet(&CEPPacket::Data(chunk.into()))?; + } + + self.send_packet(&CEPPacket::Eof)?; + self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; + + Ok(()) + } + + fn receive_packet(&mut self) -> ComResult { + let mut header_buffer = [0; 1]; + self.read_exact(&mut header_buffer)?; + + let header = CEPPacketHeader::from_repr(header_buffer[0] as usize) + .ok_or(CommunicationError::PacketInvalidError)?; + let packet = match header { + CEPPacketHeader::Ack => CEPPacket::Ack, + CEPPacketHeader::Nack => CEPPacket::Nack, + CEPPacketHeader::Stop => CEPPacket::Stop, + CEPPacketHeader::Eof => CEPPacket::Eof, + CEPPacketHeader::Data => { + let mut length_buffer = [0; 2]; + self.read_exact(&mut length_buffer)?; + let length = u16::from_le_bytes(length_buffer); + + let mut data_buffer = vec![0; length as usize]; + self.read_exact(&mut data_buffer)?; + + let mut crc_buffer = [0; 4]; + self.read_exact(&mut crc_buffer)?; + if !CEPPacket::crc_is_valid(&data_buffer, u32::from_le_bytes(crc_buffer)) { + return Err(CommunicationError::CRCError); + } + + self.send_packet(&CEPPacket::Ack)?; + CEPPacket::Data(data_buffer) + } + }; + + //self.set_timeout(&Duration::MAX); + Ok(packet) + } + + fn receive_multi_packet(&mut self, stop_fn: impl Fn() -> bool) -> ComResult> { + let mut buffer = Vec::new(); + + loop { + let pack = self.receive_packet(); + if stop_fn() { + self.send_packet(&CEPPacket::Stop)?; + return Err(CommunicationError::StopCondition); + } + + match pack { + Ok(CEPPacket::Data(b)) => { + buffer.extend(b); + } + Ok(CEPPacket::Eof) => { + break; + } + Ok(CEPPacket::Stop) => { + return Err(CommunicationError::StopCondition); + } + Err(e @ CommunicationError::Io(_)) => { + return Err(e); + } + Err(CommunicationError::TimedOut) => { + log::error!("Receive multipacket timed out"); + return Err(CommunicationError::TimedOut); + } + e => { + log::error!("Received invalid data {:?}", e); + self.send_packet(&CEPPacket::Nack)?; + } + }; + } + + self.send_packet(&CEPPacket::Ack)?; + Ok(buffer) + } + + fn await_ack(&mut self, timeout: &Duration) -> ComResult<()> { + self.set_timeout(timeout); + match self.receive_packet()? { + CEPPacket::Ack => Ok(()), + CEPPacket::Nack => Err(CommunicationError::NotAcknowledged), + _ => Err(CommunicationError::PacketInvalidError) + } + } +} + +impl CommunicationHandle for Box { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + + fn set_timeout(&mut self, timeout: &Duration) { + serialport::SerialPort::set_timeout(self.as_mut(), *timeout).unwrap() + } +} + +#[derive(Debug)] +pub enum CommunicationError { + /// Signals that an unknown command packet was received + PacketInvalidError, + /// Signals that the CRC checksum of a data packet was wrong + CRCError, + /// Signals that the underlying sending or receiving failed. Not recoverable on its own. + Io(std::io::Error), + /// Signals that a multi packet receive or send was interrupted by a Stop condition + StopCondition, + /// Signals that a receive timed out + TimedOut, + /// Nack was received when Ack was expected + NotAcknowledged +} + +impl std::fmt::Display for CommunicationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl From for CommunicationError { + fn from(value: std::io::Error) -> Self { + if value.kind() == std::io::ErrorKind::TimedOut { + CommunicationError::TimedOut + } else { + CommunicationError::Io(value) + } + } +} + +impl std::error::Error for CommunicationError {} + +#[cfg(test)] +pub mod tests { + use super::*; + use test_case::test_case; + + #[derive(Default)] + pub struct TestComHandle { + pub written_data: Vec, + pub data_to_read: Vec, + } + impl Read for TestComHandle { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + buf.copy_from_slice(&self.data_to_read[0..buf.len()]); + self.data_to_read.drain(0..buf.len()); + Ok(buf.len()) + } + } + impl Write for TestComHandle { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.written_data.extend(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + impl CommunicationHandle for TestComHandle { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::from_millis(100); + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + fn set_timeout(&mut self, _timeout: &Duration) {} + } + + #[test_case(CEPPacket::Ack)] + #[test_case(CEPPacket::Nack)] + #[test_case(CEPPacket::Stop)] + #[test_case(CEPPacket::Eof)] + #[test_case(CEPPacket::Data(vec![1, 2, 3]))] + + fn packet_is_sent_correctly(packet: CEPPacket) { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut CEPPacket::Ack.serialize()); + + com.send_packet(&packet).unwrap(); + + assert_eq!(com.written_data, packet.serialize()); + } + + #[test_case(CEPPacket::Ack)] + #[test_case(CEPPacket::Nack)] + #[test_case(CEPPacket::Stop)] + #[test_case(CEPPacket::Eof)] + #[test_case(CEPPacket::Data(vec![1, 2, 3]))] + fn packet_is_received_correctly(packet: CEPPacket) { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut packet.clone().serialize()); + + assert_eq!(com.receive_packet().unwrap(), packet); + + if matches!(packet, CEPPacket::Data(_)) { + assert_eq!(com.written_data, CEPPacket::Ack.serialize()); + } + } + + #[test] + fn error_on_nack() { + let mut com = TestComHandle::default(); + com.data_to_read.append(&mut CEPPacket::Nack.serialize()); + + let ret = com.send_packet(&CEPPacket::Data(vec![1, 2, 3])).unwrap_err(); + assert!(matches!(ret, CommunicationError::NotAcknowledged)); + } +} diff --git a/src/main.rs b/src/main.rs index d396240..d58bcd1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,10 +56,10 @@ fn main() -> ! { fn heartbeat_loop(heartbeat_pin: u8, freq: u64) -> ! { if cfg!(feature = "mock") { - loop {} + std::thread::park(); } - let toogle_time = time::Duration::from_millis((1000 / freq / 2) as u64); + let toogle_time = time::Duration::from_millis(1000 / freq / 2); let gpio = Gpio::new().unwrap(); let mut pin = gpio.get(heartbeat_pin).unwrap().into_output(); diff --git a/tests/simulation/logging.rs b/tests/simulation/logging.rs index a52e57e..08cd64a 100644 --- a/tests/simulation/logging.rs +++ b/tests/simulation/logging.rs @@ -20,7 +20,7 @@ fn logfile_is_cleared_after_sent() -> std::io::Result<()> { simulate_execute_program(&mut cobc_in, &mut cobc_out, 1, 0, 3).unwrap(); std::thread::sleep(std::time::Duration::from_millis(100)); let _ = simulate_return_result(&mut cobc_in, &mut cobc_out, 1, 0).unwrap(); - cobc_out.write_all(&CEPPacket::ACK.serialize()).unwrap(); + cobc_out.write_all(&CEPPacket::Ack.serialize()).unwrap(); std::thread::sleep(std::time::Duration::from_millis(100)); scheduler.kill().unwrap(); diff --git a/tests/simulation/mod.rs b/tests/simulation/mod.rs index c6e96c7..5e74d2a 100644 --- a/tests/simulation/mod.rs +++ b/tests/simulation/mod.rs @@ -47,12 +47,12 @@ pub fn receive_ack(reader: &mut impl std::io::Read) -> Result<(), std::io::Error let mut buffer = [0; 1]; reader.read_exact(&mut buffer).unwrap(); - if buffer[0] == CEPPacket::ACK.serialize()[0] { + if buffer[0] == CEPPacket::Ack.serialize()[0] { Ok(()) } else { Err(std::io::Error::new( std::io::ErrorKind::Other, - format!("received {:#x} instead of ACK", buffer[0]), + format!("received {:#x} instead of Ack", buffer[0]), )) } } @@ -63,11 +63,11 @@ pub fn simulate_test_store_archive( program_id: u16, ) -> std::io::Result<()> { let archive = std::fs::read("tests/student_program.zip")?; - cobc_out.write_all(&CEPPacket::DATA(store_archive(program_id)).serialize())?; + cobc_out.write_all(&CEPPacket::Data(store_archive(program_id)).serialize())?; receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::DATA(archive).serialize())?; + cobc_out.write_all(&CEPPacket::Data(archive).serialize())?; receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::EOF.serialize())?; + cobc_out.write_all(&CEPPacket::Eof.serialize())?; receive_ack(cobc_in)?; receive_ack(cobc_in)?; @@ -82,7 +82,7 @@ pub fn simulate_execute_program( timeout: u16, ) -> std::io::Result<()> { cobc_out - .write_all(&CEPPacket::DATA(execute_program(program_id, timestamp, timeout)).serialize())?; + .write_all(&CEPPacket::Data(execute_program(program_id, timestamp, timeout)).serialize())?; receive_ack(cobc_in)?; receive_ack(cobc_in)?; Ok(()) @@ -94,7 +94,7 @@ pub fn simulate_return_result( program_id: u16, timestamp: u32, ) -> std::io::Result> { - cobc_out.write_all(&CEPPacket::DATA(return_result(program_id, timestamp)).serialize())?; + cobc_out.write_all(&CEPPacket::Data(return_result(program_id, timestamp)).serialize())?; receive_ack(cobc_in)?; let data = read_multi_data_packets(cobc_in, cobc_out)?; @@ -127,15 +127,15 @@ pub fn read_multi_data_packets( let mut data = Vec::new(); loop { read_data_packet(input, &mut data)?; - output.write_all(&CEPPacket::ACK.serialize())?; + output.write_all(&CEPPacket::Ack.serialize())?; input.read_exact(&mut eof_byte)?; - if eof_byte[0] == CEPPacket::EOF.serialize()[0] { + if eof_byte[0] == CEPPacket::Eof.serialize()[0] { break; } } - output.write_all(&CEPPacket::ACK.serialize())?; + output.write_all(&CEPPacket::Ack.serialize())?; Ok(data) } diff --git a/tests/software_tests/command_integration.rs b/tests/software_tests/command_integration.rs index a0df11f..9612977 100644 --- a/tests/software_tests/command_integration.rs +++ b/tests/software_tests/command_integration.rs @@ -9,12 +9,12 @@ type TestResult = Result<(), Box>; #[test] fn invalid_packets_from_cobc() -> TestResult { let packets = vec![ - COBC(DATA(vec![1, 2])), - EDU(ACK), - EDU(NACK), - COBC(DATA(vec![2, 0, 1])), - EDU(ACK), - EDU(NACK), + COBC(Data(vec![1, 2])), + EDU(Ack), + EDU(Nack), + COBC(Data(vec![2, 0, 1])), + EDU(Ack), + EDU(Nack), ]; let (mut com, mut exec) = common::prepare_handles(packets, "13"); @@ -31,27 +31,27 @@ fn invalid_packets_from_cobc() -> TestResult { #[test] #[should_panic] fn ack_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(ACK)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Ack)], "99"); command::handle_command(&mut com, &mut exec); } #[test] #[should_panic] fn nack_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(NACK)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Nack)], "99"); command::handle_command(&mut com, &mut exec); } #[test] #[should_panic] fn eof_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(EOF)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Eof)], "99"); command::handle_command(&mut com, &mut exec); } #[test] #[should_panic] fn stop_on_start_panic() { - let (mut com, mut exec) = common::prepare_handles(vec![COBC(STOP)], "99"); + let (mut com, mut exec) = common::prepare_handles(vec![COBC(Stop)], "99"); command::handle_command(&mut com, &mut exec); } diff --git a/tests/software_tests/common.rs b/tests/software_tests/common.rs index fc33d06..8ec41bf 100644 --- a/tests/software_tests/common.rs +++ b/tests/software_tests/common.rs @@ -49,10 +49,10 @@ impl CommunicationHandle for TestCom { ComEvent::SLEEP(d) => std::thread::sleep(d), ComEvent::ANY => (), ComEvent::ACTION(f) => f(packet), - event @ _ => panic!("Expected {event:?} instead of send_packet"), + event => panic!("Expected {event:?} instead of send_packet"), } - if matches!(packet, CEPPacket::DATA(_)) { + if matches!(packet, CEPPacket::Data(_)) { self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; } @@ -63,8 +63,8 @@ impl CommunicationHandle for TestCom { match self.expected_events.pop_front().unwrap() { ComEvent::COBC(p) => { println!("Received {p:?}"); - if matches!(p, CEPPacket::DATA(_)) { - self.send_packet(&CEPPacket::ACK)?; + if matches!(p, CEPPacket::Data(_)) { + self.send_packet(&CEPPacket::Ack)?; } Ok(p) } @@ -72,7 +72,7 @@ impl CommunicationHandle for TestCom { std::thread::sleep(d); self.receive_packet() } - event @ _ => panic!("Expected {event:?} instead of receive_packet"), + event => panic!("Expected {event:?} instead of receive_packet"), } } diff --git a/tests/software_tests/communication_tests.rs b/tests/software_tests/communication_tests.rs index 7628316..36bceab 100644 --- a/tests/software_tests/communication_tests.rs +++ b/tests/software_tests/communication_tests.rs @@ -3,14 +3,14 @@ use STS1_EDU_Scheduler::communication; #[test] fn csbi_command() { - assert!(CEPPacket::ACK.serialize() == vec![0xd7u8]); + assert!(CEPPacket::Ack.serialize() == vec![0xd7u8]); } #[test] fn csbi_data() { let b = vec![0x12u8, 0x34, 0x56]; assert_eq!( - CEPPacket::DATA(b).serialize(), + CEPPacket::Data(b).serialize(), vec![0x8bu8, 0x03, 0x00, 0x12, 0x34, 0x56, 0x57, 0x86, 0x98, 0xbe] ); } diff --git a/tests/software_tests/execute_program.rs b/tests/software_tests/execute_program.rs index f0e29f7..e16dbb8 100644 --- a/tests/software_tests/execute_program.rs +++ b/tests/software_tests/execute_program.rs @@ -11,9 +11,9 @@ type TestResult = Result<(), Box>; #[test] fn execute_program_normal() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(1, 0, 2))), // Execute Program ID 1, Timestamp 0, Timeout 2s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(1, 0, 2))), // Execute Program ID 1, Timestamp 0, Timeout 2s + EDU(Ack), + EDU(Ack), ]; common::prepare_program("1"); let (mut com, mut exec) = common::prepare_handles(packets, "1"); @@ -34,13 +34,13 @@ fn execute_program_normal() -> TestResult { #[test] fn execute_program_infinite() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(2, 1, 1))), // Execute Program ID 2, Timestamp 1, Timeout 1s - EDU(ACK), - EDU(ACK), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 2, 0, 1, 0, 0, 0, 255])), - COBC(ACK), + COBC(Data(execute_program(2, 1, 1))), // Execute Program ID 2, Timestamp 1, Timeout 1s + EDU(Ack), + EDU(Ack), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 2, 0, 1, 0, 0, 0, 255])), + COBC(Ack), ]; common::prepare_program("2"); let (mut com, mut exec) = common::prepare_handles(packets, "2"); @@ -56,7 +56,7 @@ fn execute_program_infinite() -> TestResult { #[test] fn execute_missing_program() -> TestResult { - let packets = vec![COBC(DATA(execute_program(11, 0, 2))), EDU(ACK), EDU(NACK)]; + let packets = vec![COBC(Data(execute_program(11, 0, 2))), EDU(Ack), EDU(Nack)]; let (mut com, mut exec) = common::prepare_handles(packets, "12"); command::handle_command(&mut com, &mut exec); diff --git a/tests/software_tests/get_status.rs b/tests/software_tests/get_status.rs index acf9ca4..89b2be8 100644 --- a/tests/software_tests/get_status.rs +++ b/tests/software_tests/get_status.rs @@ -8,7 +8,7 @@ type TestResult = Result<(), Box>; #[test] fn get_status_none() -> TestResult { - let packets = vec![COBC(DATA(vec![4])), EDU(ACK), EDU(DATA(vec![0])), COBC(ACK)]; + let packets = vec![COBC(Data(vec![4])), EDU(Ack), EDU(Data(vec![0])), COBC(Ack)]; let (mut com, mut exec) = common::prepare_handles(packets, "5"); command::handle_command(&mut com, &mut exec); @@ -21,18 +21,18 @@ fn get_status_none() -> TestResult { #[test] fn get_status_finished() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(6, 0, 1))), // Execute Program 6, Queue 0, Timeout 1s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(6, 0, 1))), // Execute Program 6, Queue 0, Timeout 1s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(vec![4])), // Get Status - EDU(ACK), - EDU(DATA(vec![1, 6, 0, 0, 0, 0, 0, 0])), // Program Finished - COBC(ACK), - COBC(DATA(vec![4])), // Get Status - EDU(ACK), - EDU(DATA(vec![2, 6, 0, 0, 0, 0, 0])), // Result Ready - COBC(ACK), + COBC(Data(vec![4])), // Get Status + EDU(Ack), + EDU(Data(vec![1, 6, 0, 0, 0, 0, 0, 0])), // Program Finished + COBC(Ack), + COBC(Data(vec![4])), // Get Status + EDU(Ack), + EDU(Data(vec![2, 6, 0, 0, 0, 0, 0])), // Result Ready + COBC(Ack), ]; common::prepare_program("6"); @@ -50,22 +50,22 @@ fn get_status_finished() -> TestResult { #[test] fn get_status_priority_for_status() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(15, 0, 2))), - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(15, 0, 2))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 15, 0, 0, 0, 0, 0, 0])), - COBC(ACK), - COBC(DATA(execute_program(15, 0, 2))), - EDU(ACK), - EDU(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 15, 0, 0, 0, 0, 0, 0])), + COBC(Ack), + COBC(Data(execute_program(15, 0, 2))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 15, 0, 0, 0, 0, 0, 0])), - COBC(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 15, 0, 0, 0, 0, 0, 0])), + COBC(Ack), ]; common::prepare_program("15"); let (mut com, mut exec) = common::prepare_handles(packets, "15"); diff --git a/tests/software_tests/return_result.rs b/tests/software_tests/return_result.rs index cea5829..b8b9513 100644 --- a/tests/software_tests/return_result.rs +++ b/tests/software_tests/return_result.rs @@ -11,30 +11,30 @@ type TestResult = Result<(), Box>; #[test] fn returns_result_correctly() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(7, 3, 1))), // Execute Program 7, Queue 0, Timeout 1s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(7, 3, 1))), // Execute Program 7, Queue 0, Timeout 1s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(500)), - COBC(DATA(get_status())), // Get Status - EDU(ACK), - EDU(DATA(vec![1, 7, 0, 3, 0, 0, 0, 0])), // Program Finished - COBC(ACK), - COBC(DATA(get_status())), // Get Status - EDU(ACK), - EDU(DATA(vec![2, 7, 0, 3, 0, 0, 0])), // Result Ready - COBC(ACK), - COBC(DATA(return_result(7, 3))), - EDU(ACK), + COBC(Data(get_status())), // Get Status + EDU(Ack), + EDU(Data(vec![1, 7, 0, 3, 0, 0, 0, 0])), // Program Finished + COBC(Ack), + COBC(Data(get_status())), // Get Status + EDU(Ack), + EDU(Data(vec![2, 7, 0, 3, 0, 0, 0])), // Result Ready + COBC(Ack), + COBC(Data(return_result(7, 3))), + EDU(Ack), ACTION(Box::new(|packet| { std::fs::File::create("tests/tmp/7.zip") .unwrap() .write(&packet.clone().serialize()) .unwrap(); })), - COBC(ACK), - EDU(EOF), - COBC(ACK), - COBC(ACK), + COBC(Ack), + EDU(Eof), + COBC(Ack), + COBC(Ack), ]; common::prepare_program("7"); @@ -63,14 +63,14 @@ fn returns_result_correctly() -> TestResult { #[test] fn truncate_result() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(8, 5, 5))), // Execute Program 8, Queue 5, Timeout 2s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(8, 5, 5))), // Execute Program 8, Queue 5, Timeout 2s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(3000)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 8, 0, 5, 0, 0, 0, 0])), - COBC(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 8, 0, 5, 0, 0, 0, 0])), + COBC(Ack), ]; common::prepare_program("8"); @@ -89,28 +89,28 @@ fn truncate_result() -> TestResult { #[test] fn stopped_return() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(9, 5, 3))), - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(9, 5, 3))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(3000)), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 9, 0, 5, 0, 0, 0, 0])), - COBC(ACK), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![2, 9, 0, 5, 0, 0, 0])), - COBC(ACK), - COBC(DATA(return_result(9, 5))), - EDU(ACK), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 9, 0, 5, 0, 0, 0, 0])), + COBC(Ack), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![2, 9, 0, 5, 0, 0, 0])), + COBC(Ack), + COBC(Data(return_result(9, 5))), + EDU(Ack), ANY, - COBC(ACK), + COBC(Ack), ANY, - COBC(STOP), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![2, 9, 0, 5, 0, 0, 0])), - COBC(ACK), + COBC(Stop), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![2, 9, 0, 5, 0, 0, 0])), + COBC(Ack), ]; common::prepare_program("9"); let (mut com, mut exec) = common::prepare_handles(packets, "9"); @@ -130,7 +130,7 @@ fn stopped_return() -> TestResult { #[test] fn no_result_ready() -> TestResult { - let packets = vec![COBC(DATA(return_result(99, 0))), EDU(ACK), EDU(NACK)]; + let packets = vec![COBC(Data(return_result(99, 0))), EDU(Ack), EDU(Nack)]; let (mut com, mut exec) = common::prepare_handles(packets, "10"); command::handle_command(&mut com, &mut exec); @@ -143,17 +143,17 @@ fn no_result_ready() -> TestResult { #[test] fn result_is_not_deleted_after_corrupted_transfer() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(50, 0, 3))), - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(50, 0, 3))), + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_millis(2000)), - COBC(DATA(return_result(50, 0))), - EDU(ACK), + COBC(Data(return_result(50, 0))), + EDU(Ack), ANY, - COBC(ACK), - EDU(EOF), - COBC(ACK), - COBC(NACK), + COBC(Ack), + EDU(Eof), + COBC(Ack), + COBC(Nack), ]; common::prepare_program("50"); let (mut com, mut exec) = common::prepare_handles(packets, "50"); diff --git a/tests/software_tests/stop_program.rs b/tests/software_tests/stop_program.rs index f8cd0ef..facc61f 100644 --- a/tests/software_tests/stop_program.rs +++ b/tests/software_tests/stop_program.rs @@ -9,17 +9,17 @@ type TestResult = Result<(), Box>; #[test] fn stops_running_program() -> TestResult { let packets = vec![ - COBC(DATA(execute_program(3, 1, 10))), // Execute Program 3, Queue 1, Timeout 10s - EDU(ACK), - EDU(ACK), + COBC(Data(execute_program(3, 1, 10))), // Execute Program 3, Queue 1, Timeout 10s + EDU(Ack), + EDU(Ack), SLEEP(std::time::Duration::from_secs(1)), - COBC(DATA(stop_program())), - EDU(ACK), - EDU(ACK), - COBC(DATA(get_status())), - EDU(ACK), - EDU(DATA(vec![1, 3, 0, 1, 0, 0, 0, 255])), - COBC(ACK), + COBC(Data(stop_program())), + EDU(Ack), + EDU(Ack), + COBC(Data(get_status())), + EDU(Ack), + EDU(Data(vec![1, 3, 0, 1, 0, 0, 0, 255])), + COBC(Ack), ]; common::prepare_program("3"); let (mut com, mut exec) = common::prepare_handles(packets, "3"); @@ -35,7 +35,7 @@ fn stops_running_program() -> TestResult { #[test] fn stop_no_running_program() -> TestResult { - let packets = vec![COBC(DATA(stop_program())), EDU(ACK), EDU(ACK)]; + let packets = vec![COBC(Data(stop_program())), EDU(Ack), EDU(Ack)]; let (mut com, mut exec) = common::prepare_handles(packets, "11"); command::handle_command(&mut com, &mut exec); assert!(com.is_complete()); diff --git a/tests/software_tests/store_archive.rs b/tests/software_tests/store_archive.rs index a7d19e3..a94b9b7 100644 --- a/tests/software_tests/store_archive.rs +++ b/tests/software_tests/store_archive.rs @@ -9,13 +9,13 @@ type TestResult = Result<(), Box>; fn store_archive() -> TestResult { // Define what should happen during communication. How this should look is defined in the PDD let packets = vec![ - COBC(DATA(vec![0x01, 0x00, 0x00])), // COBC sends Store Archive Command (0x01 -> Header, [0x00, 0x00] -> Program Id) - EDU(ACK), // EDU acknowledges packet integrity - COBC(DATA(std::fs::read("./tests/student_program.zip")?)), // COBC sends the archive - EDU(ACK), // EDU acknowledges packet integrity - COBC(EOF), // COBC signals end of packets - EDU(ACK), // EDU acknowledges EOF - EDU(ACK), // EDU signals successful Store Archive + COBC(Data(vec![0x01, 0x00, 0x00])), // COBC sends Store Archive Command (0x01 -> Header, [0x00, 0x00] -> Program Id) + EDU(Ack), // EDU acknowledges packet integrity + COBC(Data(std::fs::read("./tests/student_program.zip")?)), // COBC sends the archive + EDU(Ack), // EDU acknowledges packet integrity + COBC(Eof), // COBC signals end of packets + EDU(Ack), // EDU acknowledges Eof + EDU(Ack), // EDU signals successful Store Archive ]; // Setup testing environment @@ -45,13 +45,13 @@ fn store_archive() -> TestResult { #[test] fn stopped_store() -> TestResult { let packets = vec![ - COBC(DATA(vec![0x01, 0x04, 0x00])), // Store Archive with ID 0 - EDU(ACK), - COBC(DATA(std::fs::read("./tests/student_program.zip")?)), - EDU(ACK), - COBC(DATA(vec![0, 1, 2, 3])), - EDU(ACK), - COBC(STOP), + COBC(Data(vec![0x01, 0x04, 0x00])), // Store Archive with ID 0 + EDU(Ack), + COBC(Data(std::fs::read("./tests/student_program.zip")?)), + EDU(Ack), + COBC(Data(vec![0, 1, 2, 3])), + EDU(Ack), + COBC(Stop), ]; let (mut com, mut exec) = common::prepare_handles(packets, "4"); From aea75b072c2f18559fdda1eab0c4192f73279137 Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Sat, 16 Dec 2023 23:15:03 +0100 Subject: [PATCH 03/10] cleanup simulation framework --- Makefile | 4 +- src/command/common.rs | 10 +- src/communication/mod.rs | 4 +- src/main.rs | 5 +- tests/simulation/command_execution.rs | 8 +- tests/simulation/full_run.rs | 27 ++++ tests/simulation/logging.rs | 21 ++- tests/simulation/mod.rs | 179 +++++++++++++------------- 8 files changed, 144 insertions(+), 114 deletions(-) create mode 100644 tests/simulation/full_run.rs diff --git a/Makefile b/Makefile index c80ec69..cc7d0fe 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ coverage: build_with_cov firefox ./target/debug/coverage/index.html& sw_test: - RUST_LOG=info cargo test --release --features mock + cargo build --release && RUST_LOG=info cargo test --release --features mock packs: cargo test build_pack --features rpi @@ -24,4 +24,4 @@ clean: rebuild_student_archive: rm -f tests/student_program.zip - cd tests/test_data; zip -r ../student_program.zip * \ No newline at end of file + cd tests/test_data; zip -r ../student_program.zip * diff --git a/src/command/common.rs b/src/command/common.rs index 2b33620..f512657 100644 --- a/src/command/common.rs +++ b/src/command/common.rs @@ -1,8 +1,12 @@ -use std::time::Duration; -use crate::communication::{CommunicationHandle, CEPPacket}; use super::{CommandError, CommandResult, SyncExecutionContext}; +use crate::communication::{CEPPacket, CommunicationHandle}; +use std::time::Duration; -pub fn check_length(com: &mut impl CommunicationHandle, vec: &Vec, n: usize) -> Result<(), CommandError> { +pub fn check_length( + com: &mut impl CommunicationHandle, + vec: &Vec, + n: usize, +) -> Result<(), CommandError> { let actual_len = vec.len(); if actual_len != n { log::error!("Command came with {actual_len} bytes, should have {n}"); diff --git a/src/communication/mod.rs b/src/communication/mod.rs index 7f74bd0..8bd501c 100644 --- a/src/communication/mod.rs +++ b/src/communication/mod.rs @@ -120,7 +120,7 @@ pub trait CommunicationHandle: Read + Write { match self.receive_packet()? { CEPPacket::Ack => Ok(()), CEPPacket::Nack => Err(CommunicationError::NotAcknowledged), - _ => Err(CommunicationError::PacketInvalidError) + _ => Err(CommunicationError::PacketInvalidError), } } } @@ -147,7 +147,7 @@ pub enum CommunicationError { /// Signals that a receive timed out TimedOut, /// Nack was received when Ack was expected - NotAcknowledged + NotAcknowledged, } impl std::fmt::Display for CommunicationError { diff --git a/src/main.rs b/src/main.rs index d58bcd1..9e40868 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,7 @@ #![allow(non_snake_case)] use core::time; use rppal::gpio::Gpio; -use std::{ - thread, - time::Duration, -}; +use std::{thread, time::Duration}; use STS1_EDU_Scheduler::communication::CommunicationHandle; use simplelog as sl; diff --git a/tests/simulation/command_execution.rs b/tests/simulation/command_execution.rs index f42c722..d306168 100644 --- a/tests/simulation/command_execution.rs +++ b/tests/simulation/command_execution.rs @@ -2,11 +2,10 @@ use crate::simulation::*; #[test] fn simulate_archive_is_stored_correctly() -> Result<(), std::io::Error> { - let (mut scheduler, mut serial_port) = start_scheduler("archive_is_stored_correctly")?; - let mut cobc_in = serial_port.stdout.take().unwrap(); - let mut cobc_out = serial_port.stdin.take().unwrap(); + let (mut com, _socat) = SimulationComHandle::with_socat_proc("archive_is_stored_correctly"); + let _sched = start_scheduler("archive_is_stored_correctly")?; - simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1)?; + simulate_test_store_archive(&mut com, 1).unwrap(); std::thread::sleep(std::time::Duration::from_millis(400)); assert_eq!( @@ -23,6 +22,5 @@ fn simulate_archive_is_stored_correctly() -> Result<(), std::io::Error> { .unwrap() ); - scheduler.kill()?; Ok(()) } diff --git a/tests/simulation/full_run.rs b/tests/simulation/full_run.rs new file mode 100644 index 0000000..441fa9b --- /dev/null +++ b/tests/simulation/full_run.rs @@ -0,0 +1,27 @@ +use crate::simulation::*; +use std::{io::Cursor, time::Duration}; + +#[test] +fn full_run() { + let (mut com, _socat) = SimulationComHandle::with_socat_proc("full_run"); + let _sched = start_scheduler("full_run").unwrap(); + + // store and execute program + simulate_test_store_archive(&mut com, 1).unwrap(); + simulate_execute_program(&mut com, 1, 3, 3).unwrap(); + std::thread::sleep(Duration::from_secs(1)); + + // read program finished and result ready + assert_eq!(simulate_get_status(&mut com).unwrap(), [1, 1, 0, 3, 0, 0, 0, 0]); + assert_eq!(simulate_get_status(&mut com).unwrap(), [2, 1, 0, 3, 0, 0, 0]); + + // Check result + let result = simulate_return_result(&mut com, 1, 3).unwrap(); + let mut result_archive = zip::ZipArchive::new(Cursor::new(result)).unwrap(); + com.send_packet(&CEPPacket::Ack).unwrap(); + + let result_file = result_archive.by_name(&"3").unwrap(); + assert_eq!(result_file.bytes().map(|b| b.unwrap()).collect::>(), vec![0xde, 0xad]); + + assert_eq!(simulate_get_status(&mut com).unwrap(), [0]); +} diff --git a/tests/simulation/logging.rs b/tests/simulation/logging.rs index 08cd64a..332af4c 100644 --- a/tests/simulation/logging.rs +++ b/tests/simulation/logging.rs @@ -2,9 +2,9 @@ use crate::simulation::*; #[test] fn logfile_is_created() -> Result<(), std::io::Error> { - let (mut scheduler, _) = start_scheduler("log_created")?; + let (_, _proc) = SimulationComHandle::with_socat_proc("log_created"); + let _sched = start_scheduler("log_created")?; std::thread::sleep(std::time::Duration::from_millis(400)); - scheduler.kill().unwrap(); assert!(std::path::Path::new("./tests/tmp/log_created/log").exists()); Ok(()) @@ -12,18 +12,17 @@ fn logfile_is_created() -> Result<(), std::io::Error> { #[test] fn logfile_is_cleared_after_sent() -> std::io::Result<()> { - let (mut scheduler, mut serial_port) = start_scheduler("log_is_cleared_after_sent")?; - let mut cobc_in = serial_port.stdout.take().unwrap(); - let mut cobc_out = serial_port.stdin.take().unwrap(); + let (mut com, _socat) = SimulationComHandle::with_socat_proc("log_is_cleared_after_sent"); + let _sched = start_scheduler("log_is_cleared_after_sent")?; - simulate_test_store_archive(&mut cobc_in, &mut cobc_out, 1).unwrap(); - simulate_execute_program(&mut cobc_in, &mut cobc_out, 1, 0, 3).unwrap(); - std::thread::sleep(std::time::Duration::from_millis(100)); - let _ = simulate_return_result(&mut cobc_in, &mut cobc_out, 1, 0).unwrap(); - cobc_out.write_all(&CEPPacket::Ack.serialize()).unwrap(); + simulate_test_store_archive(&mut com, 1).unwrap(); + com.send_packet(&CEPPacket::Data(execute_program(1, 0, 3))).unwrap(); + com.await_ack(&Duration::MAX).unwrap(); std::thread::sleep(std::time::Duration::from_millis(100)); - scheduler.kill().unwrap(); + let _ = simulate_return_result(&mut com, 1, 0).unwrap(); + com.send_packet(&CEPPacket::Ack).unwrap(); + std::thread::sleep(std::time::Duration::from_millis(100)); let log_metadata = std::fs::metadata("./tests/tmp/log_is_cleared_after_sent/log")?; assert!(log_metadata.len() < 50, "Logfile is not empty"); diff --git a/tests/simulation/mod.rs b/tests/simulation/mod.rs index 5e74d2a..6d457bc 100644 --- a/tests/simulation/mod.rs +++ b/tests/simulation/mod.rs @@ -1,12 +1,65 @@ mod command_execution; +mod full_run; mod logging; use std::{ io::{Read, Write}, - process::{Child, Stdio}, + process::{Child, ChildStdin, ChildStdout, Stdio}, + time::Duration, }; +use STS1_EDU_Scheduler::communication::{CEPPacket, CommunicationError, CommunicationHandle}; -use STS1_EDU_Scheduler::communication::CEPPacket; +pub struct SimulationComHandle { + cobc_in: T, + cobc_out: U, +} + +impl SimulationComHandle { + fn with_socat_proc(unique: &str) -> (Self, PoisonedChild) { + let mut proc = std::process::Command::new("socat") + .arg("stdio") + .arg(format!("pty,raw,echo=0,link=/tmp/ttySTS1-{},b921600,wait-slave", unique)) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + loop { + if std::path::Path::new(&format!("/tmp/ttySTS1-{unique}")).exists() { + break; + } + std::thread::sleep(Duration::from_millis(50)); + } + + ( + Self { cobc_in: proc.stdout.take().unwrap(), cobc_out: proc.stdin.take().unwrap() }, + PoisonedChild(proc), + ) + } +} + +impl Read for SimulationComHandle { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.cobc_in.read(buf) + } +} + +impl Write for SimulationComHandle { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.cobc_out.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.cobc_out.flush() + } +} + +impl CommunicationHandle for SimulationComHandle { + const INTEGRITY_ACK_TIMEOUT: Duration = Duration::MAX; + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + + fn set_timeout(&mut self, _timeout: &std::time::Duration) {} +} fn get_config_str(unique: &str) -> String { format!( @@ -22,120 +75,72 @@ fn get_config_str(unique: &str) -> String { ) } -pub fn start_scheduler(unique: &str) -> Result<(Child, Child), std::io::Error> { +/// A simple wrapper that ensures child processes are killed when dropped +struct PoisonedChild(pub Child); +impl Drop for PoisonedChild { + fn drop(&mut self) { + self.0.kill().unwrap(); + } +} + +fn start_scheduler(unique: &str) -> Result { let test_dir = format!("./tests/tmp/{}", unique); let scheduler_bin = std::fs::canonicalize("./target/release/STS1_EDU_Scheduler")?; let _ = std::fs::remove_dir_all(&test_dir); std::fs::create_dir_all(&test_dir)?; std::fs::write(format!("{}/config.toml", &test_dir), get_config_str(unique))?; - let serial_port = std::process::Command::new("socat") - .arg("stdio") - .arg(format!("pty,raw,echo=0,link=/tmp/ttySTS1-{},b921600,wait-slave", unique)) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - std::thread::sleep(std::time::Duration::from_millis(100)); - let scheduler = std::process::Command::new(scheduler_bin).current_dir(test_dir).spawn().unwrap(); - Ok((scheduler, serial_port)) -} - -pub fn receive_ack(reader: &mut impl std::io::Read) -> Result<(), std::io::Error> { - let mut buffer = [0; 1]; - reader.read_exact(&mut buffer).unwrap(); - - if buffer[0] == CEPPacket::Ack.serialize()[0] { - Ok(()) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("received {:#x} instead of Ack", buffer[0]), - )) - } + Ok(PoisonedChild(scheduler)) } pub fn simulate_test_store_archive( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, + com: &mut impl CommunicationHandle, program_id: u16, -) -> std::io::Result<()> { - let archive = std::fs::read("tests/student_program.zip")?; - cobc_out.write_all(&CEPPacket::Data(store_archive(program_id)).serialize())?; - receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::Data(archive).serialize())?; - receive_ack(cobc_in)?; - cobc_out.write_all(&CEPPacket::Eof.serialize())?; - receive_ack(cobc_in)?; - receive_ack(cobc_in)?; +) -> Result<(), CommunicationError> { + let archive = std::fs::read("tests/student_program.zip").unwrap(); + com.send_packet(&CEPPacket::Data(store_archive(program_id)))?; + com.send_multi_packet(&archive)?; + com.await_ack(&Duration::MAX)?; Ok(()) } pub fn simulate_execute_program( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, + com: &mut impl CommunicationHandle, program_id: u16, timestamp: u32, timeout: u16, -) -> std::io::Result<()> { - cobc_out - .write_all(&CEPPacket::Data(execute_program(program_id, timestamp, timeout)).serialize())?; - receive_ack(cobc_in)?; - receive_ack(cobc_in)?; +) -> Result<(), CommunicationError> { + com.send_packet(&CEPPacket::Data(execute_program(program_id, timestamp, timeout)))?; + com.await_ack(&Duration::MAX)?; + Ok(()) } -pub fn simulate_return_result( - cobc_in: &mut impl std::io::Read, - cobc_out: &mut impl std::io::Write, - program_id: u16, - timestamp: u32, -) -> std::io::Result> { - cobc_out.write_all(&CEPPacket::Data(return_result(program_id, timestamp)).serialize())?; - receive_ack(cobc_in)?; +pub fn simulate_get_status( + com: &mut impl CommunicationHandle, +) -> Result, CommunicationError> { + com.send_packet(&CEPPacket::Data(get_status()))?; + let response = com.receive_packet()?; - let data = read_multi_data_packets(cobc_in, cobc_out)?; - Ok(data) -} - -/// Reads a data packet from input and returns the data content (does not check CRC) -pub fn read_data_packet(input: &mut impl std::io::Read, data: &mut Vec) -> std::io::Result<()> { - let mut header = [0; 3]; - input.read_exact(&mut header)?; - if header[0] != 0x8B { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Expected data header (0x8B), received {:#04x}", header[0]), - )); + if let CEPPacket::Data(data) = response { + Ok(data) + } else { + Err(CommunicationError::PacketInvalidError) } - - let data_len = u16::from_le_bytes([header[1], header[2]]); - input.take((data_len + 4).into()).read_to_end(data)?; - - Ok(()) } -/// Reads a multi packet round without checking the CRC and returns the concatenated contents -pub fn read_multi_data_packets( - input: &mut impl std::io::Read, - output: &mut impl std::io::Write, -) -> std::io::Result> { - let mut eof_byte = [0; 1]; - let mut data = Vec::new(); - loop { - read_data_packet(input, &mut data)?; - output.write_all(&CEPPacket::Ack.serialize())?; - - input.read_exact(&mut eof_byte)?; - if eof_byte[0] == CEPPacket::Eof.serialize()[0] { - break; - } - } +pub fn simulate_return_result( + com: &mut impl CommunicationHandle, + program_id: u16, + timestamp: u32, +) -> Result, CommunicationError> { + com.send_packet(&CEPPacket::Data(return_result(program_id, timestamp)))?; + let data = com.receive_multi_packet(|| false)?; - output.write_all(&CEPPacket::Ack.serialize())?; Ok(data) } From 1aabfd6f2bebae71dc4eec28a971c1826ca0fb65 Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Tue, 26 Dec 2023 20:10:17 +0100 Subject: [PATCH 04/10] properly separate communication handling and packet parsing --- src/command/error.rs | 2 +- src/communication/cep.rs | 55 +++++++++++-- src/communication/communication.rs | 0 src/communication/mod.rs | 127 ++++++++++++++++++----------- 4 files changed, 127 insertions(+), 57 deletions(-) delete mode 100644 src/communication/communication.rs diff --git a/src/command/error.rs b/src/command/error.rs index 566f7a9..74b2085 100644 --- a/src/command/error.rs +++ b/src/command/error.rs @@ -25,7 +25,7 @@ impl From for CommandError { fn from(e: CommunicationError) -> Self { match e { CommunicationError::PacketInvalidError => CommandError::External(Box::new(e)), - CommunicationError::CRCError => CommandError::ProtocolViolation(Box::new(e)), + CommunicationError::CepParsing(_) => CommandError::ProtocolViolation(Box::new(e)), CommunicationError::Io(_) => CommandError::NonRecoverable(Box::new(e)), CommunicationError::StopCondition => CommandError::External(Box::new(e)), CommunicationError::NotAcknowledged => CommandError::ProtocolViolation(Box::new(e)), diff --git a/src/communication/cep.rs b/src/communication/cep.rs index c4ec597..55e23b4 100644 --- a/src/communication/cep.rs +++ b/src/communication/cep.rs @@ -1,3 +1,5 @@ +use std::io::Read; + use crc::{Crc, CRC_32_MPEG_2}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -34,20 +36,18 @@ impl CEPPacket { } pub fn serialize(self) -> Vec { + let header = self.header(); match self { - CEPPacket::Ack => vec![0xd7], - CEPPacket::Nack => vec![0x27], - CEPPacket::Stop => vec![0xb4], - CEPPacket::Eof => vec![0x59], CEPPacket::Data(bytes) => { - let mut v = vec![0x8b]; + let mut v = Vec::with_capacity(7 + bytes.len()); let crc32 = CEPPacket::CRC.checksum(&bytes); - v.reserve_exact(6 + bytes.len()); + v.push(header); v.extend((bytes.len() as u16).to_le_bytes()); v.extend(bytes); v.extend(crc32.to_le_bytes()); v } + _ => vec![header], } } @@ -65,6 +65,38 @@ impl CEPPacket { }; header as u8 } + + pub fn try_from_read(reader: &mut (impl Read + ?Sized)) -> Result { + let mut header_buffer = [0; 1]; + reader.read_exact(&mut header_buffer)?; + + let header = CEPPacketHeader::from_repr(header_buffer[0] as usize) + .ok_or(CEPParseError::WrongLength)?; + let packet = match header { + CEPPacketHeader::Ack => CEPPacket::Ack, + CEPPacketHeader::Nack => CEPPacket::Nack, + CEPPacketHeader::Stop => CEPPacket::Stop, + CEPPacketHeader::Eof => CEPPacket::Eof, + CEPPacketHeader::Data => { + let mut length_buffer = [0; 2]; + reader.read_exact(&mut length_buffer)?; + let length = u16::from_le_bytes(length_buffer); + + let mut data_buffer = vec![0; length as usize]; + reader.read_exact(&mut data_buffer)?; + + let mut crc_buffer = [0; 4]; + reader.read_exact(&mut crc_buffer)?; + if !CEPPacket::crc_is_valid(&data_buffer, u32::from_le_bytes(crc_buffer)) { + return Err(CEPParseError::InvalidCRC); + } + + CEPPacket::Data(data_buffer) + } + }; + + Ok(packet) + } } impl From<&CEPPacket> for Vec { @@ -84,11 +116,20 @@ impl From<&CEPPacket> for Vec { } } -#[derive(Debug)] +#[derive(Debug, strum::Display)] pub enum CEPParseError { WrongLength, InvalidHeader, InvalidCRC, + Io(std::io::Error), +} + +impl std::error::Error for CEPParseError {} + +impl From for CEPParseError { + fn from(value: std::io::Error) -> Self { + CEPParseError::Io(value) + } } impl TryFrom> for CEPPacket { diff --git a/src/communication/communication.rs b/src/communication/communication.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/communication/mod.rs b/src/communication/mod.rs index 8bd501c..096df87 100644 --- a/src/communication/mod.rs +++ b/src/communication/mod.rs @@ -6,29 +6,42 @@ use std::{ time::Duration, }; -use self::cep::CEPPacketHeader; +use self::cep::CEPParseError; pub type ComResult = Result; pub trait CommunicationHandle: Read + Write { const INTEGRITY_ACK_TIMEOUT: Duration; - const UNLIMITED_TIMEOUT: Duration; + const UNLIMITED_TIMEOUT: Duration = Duration::MAX; + + const DATA_PACKET_RETRIES: usize = 4; fn set_timeout(&mut self, timeout: &Duration); fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { - self.write_all(&[packet.header()])?; + let bytes = Vec::from(packet); + self.write_all(&bytes)?; - if let CEPPacket::Data(data) = packet { - self.write_all(&(data.len() as u16).to_le_bytes())?; - self.write_all(data)?; - self.write_all(&packet.checksum().to_le_bytes())?; - self.flush()?; + if matches!(packet, CEPPacket::Data(_)) { + for _ in 0..Self::DATA_PACKET_RETRIES { + let response = self.receive_packet()?; + match response { + CEPPacket::Ack => return Ok(()), + CEPPacket::Nack => log::warn!("Received NACK after data packet; Retrying"), + p => { + log::error!("Received {p:?} after data packet"); + return Err(CommunicationError::PacketInvalidError); + } + } - self.await_ack(&Self::INTEGRITY_ACK_TIMEOUT)?; + self.write_all(&bytes)?; + } + } else { + return Ok(()); } - Ok(()) + log::error!("No ACK after {} retries, giving up", Self::DATA_PACKET_RETRIES); + Err(CommunicationError::PacketInvalidError) } fn send_multi_packet(&mut self, bytes: &[u8]) -> ComResult<()> { @@ -44,37 +57,24 @@ pub trait CommunicationHandle: Read + Write { } fn receive_packet(&mut self) -> ComResult { - let mut header_buffer = [0; 1]; - self.read_exact(&mut header_buffer)?; - - let header = CEPPacketHeader::from_repr(header_buffer[0] as usize) - .ok_or(CommunicationError::PacketInvalidError)?; - let packet = match header { - CEPPacketHeader::Ack => CEPPacket::Ack, - CEPPacketHeader::Nack => CEPPacket::Nack, - CEPPacketHeader::Stop => CEPPacket::Stop, - CEPPacketHeader::Eof => CEPPacket::Eof, - CEPPacketHeader::Data => { - let mut length_buffer = [0; 2]; - self.read_exact(&mut length_buffer)?; - let length = u16::from_le_bytes(length_buffer); - - let mut data_buffer = vec![0; length as usize]; - self.read_exact(&mut data_buffer)?; - - let mut crc_buffer = [0; 4]; - self.read_exact(&mut crc_buffer)?; - if !CEPPacket::crc_is_valid(&data_buffer, u32::from_le_bytes(crc_buffer)) { - return Err(CommunicationError::CRCError); + for _ in 0..Self::DATA_PACKET_RETRIES { + match CEPPacket::try_from_read(self) { + Ok(p @ CEPPacket::Data(_)) => { + self.send_packet(&CEPPacket::Ack)?; + return Ok(p); + } + Ok(p) => return Ok(p), + Err(CEPParseError::InvalidCRC) => { + log::warn!("Received data packet with invalid CRC; Retrying") + } + Err(e) => { + log::error!("Failed to read packet: {e:?}"); + return Err(e.into()); } - - self.send_packet(&CEPPacket::Ack)?; - CEPPacket::Data(data_buffer) } - }; + } - //self.set_timeout(&Duration::MAX); - Ok(packet) + todo!() } fn receive_multi_packet(&mut self, stop_fn: impl Fn() -> bool) -> ComResult> { @@ -138,8 +138,8 @@ impl CommunicationHandle for Box { pub enum CommunicationError { /// Signals that an unknown command packet was received PacketInvalidError, - /// Signals that the CRC checksum of a data packet was wrong - CRCError, + /// Relays an error from trying to parse a CEP packet + CepParsing(CEPParseError), /// Signals that the underlying sending or receiving failed. Not recoverable on its own. Io(std::io::Error), /// Signals that a multi packet receive or send was interrupted by a Stop condition @@ -158,10 +158,19 @@ impl std::fmt::Display for CommunicationError { impl From for CommunicationError { fn from(value: std::io::Error) -> Self { - if value.kind() == std::io::ErrorKind::TimedOut { - CommunicationError::TimedOut - } else { - CommunicationError::Io(value) + match value.kind() { + std::io::ErrorKind::TimedOut => CommunicationError::TimedOut, + std::io::ErrorKind::InvalidData => CommunicationError::PacketInvalidError, + _ => CommunicationError::Io(value), + } + } +} + +impl From for CommunicationError { + fn from(value: CEPParseError) -> Self { + match value { + CEPParseError::Io(e) => Self::Io(e), + e => Self::CepParsing(e), } } } @@ -169,7 +178,7 @@ impl From for CommunicationError { impl std::error::Error for CommunicationError {} #[cfg(test)] -pub mod tests { +mod tests { use super::*; use test_case::test_case; @@ -206,7 +215,6 @@ pub mod tests { #[test_case(CEPPacket::Stop)] #[test_case(CEPPacket::Eof)] #[test_case(CEPPacket::Data(vec![1, 2, 3]))] - fn packet_is_sent_correctly(packet: CEPPacket) { let mut com = TestComHandle::default(); com.data_to_read.append(&mut CEPPacket::Ack.serialize()); @@ -233,11 +241,32 @@ pub mod tests { } #[test] - fn error_on_nack() { + fn retry_on_nack() { let mut com = TestComHandle::default(); com.data_to_read.append(&mut CEPPacket::Nack.serialize()); + com.data_to_read.append(&mut CEPPacket::Nack.serialize()); + com.data_to_read.append(&mut CEPPacket::Ack.serialize()); + + com.send_packet(&CEPPacket::Data(vec![1, 2, 3])).unwrap(); + + let mut expected = CEPPacket::Data(vec![1, 2, 3]).serialize(); + expected.extend(CEPPacket::Data(vec![1, 2, 3]).serialize()); + expected.extend(CEPPacket::Data(vec![1, 2, 3]).serialize()); + assert_eq!(com.written_data, expected); + } + + #[test] + fn fail_after_retries() { + let mut com = TestComHandle::default(); + for _ in 0..TestComHandle::DATA_PACKET_RETRIES { + com.data_to_read.append(&mut CEPPacket::Nack.serialize()); + } - let ret = com.send_packet(&CEPPacket::Data(vec![1, 2, 3])).unwrap_err(); - assert!(matches!(ret, CommunicationError::NotAcknowledged)); + assert!( + matches!( + com.send_packet(&CEPPacket::Data(vec![1, 2, 3])), + Err(CommunicationError::PacketInvalidError) + ) + ); } } From a697996e6994ea4d98e8d349e3d46b4aac6c17d0 Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Tue, 26 Dec 2023 20:11:15 +0100 Subject: [PATCH 05/10] update dependencies and remove log path from config --- .github/workflows/rust.yml | 2 +- Cargo.lock | 754 +++++++++++++++++++++++++++++++------ Cargo.toml | 5 +- Makefile | 8 +- config.toml | 1 - 5 files changed, 638 insertions(+), 132 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 488c328..7eb9df9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install - run: sudo apt install socat + run: sudo apt install socat libudev-dev - name: Build run: cargo build --release - name: Run tests diff --git a/Cargo.lock b/Cargo.lock index 025c3d3..2f61e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,27 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "CoreFoundation-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" -dependencies = [ - "libc", - "mach", -] - -[[package]] -name = "IOKit-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a" -dependencies = [ - "CoreFoundation-sys", - "libc", - "mach", -] - [[package]] name = "STS1_EDU_Scheduler" version = "0.1.0" @@ -39,26 +18,33 @@ dependencies = [ "subprocess", "test-case", "toml", + "zip", ] [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ - "memchr", + "cfg-if", + "cipher", + "cpufeatures", ] [[package]] -name = "atty" -version = "0.2.14" +name = "aho-corasick" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "memchr", ] [[package]] @@ -67,6 +53,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -75,15 +67,55 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.0.2" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] [[package]] name = "cfg-if" @@ -91,29 +123,108 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + [[package]] name = "crc" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] [[package]] name = "env_logger" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", @@ -121,15 +232,25 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "file-per-thread-logger" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +checksum = "8a3cc21c33af89af0930c8cae4ade5e6fdc17b5d2c97b3d2e2edb67a1cf683f3" dependencies = [ "env_logger", "log", @@ -143,11 +264,31 @@ dependencies = [ "serde", ] +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -157,11 +298,17 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "libc", + "digest", ] [[package]] @@ -172,31 +319,64 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] -name = "lazy_static" -version = "1.4.0" +name = "jobserver" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libudev" @@ -219,22 +399,16 @@ dependencies = [ ] [[package]] -name = "log" -version = "0.4.16" +name = "linux-raw-sys" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" -dependencies = [ - "cfg-if", -] +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] -name = "mach" -version = "0.1.2" +name = "log" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" -dependencies = [ - "libc", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "mach2" @@ -247,9 +421,18 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] [[package]] name = "nix" @@ -264,9 +447,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -280,41 +463,88 @@ dependencies = [ "libc", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "regex" -version = "1.6.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -323,15 +553,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rmp" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" dependencies = [ "byteorder", "num-traits", @@ -340,9 +570,9 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" dependencies = [ "byteorder", "rmp", @@ -351,12 +581,24 @@ dependencies = [ [[package]] name = "rppal" -version = "0.13.1" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23aff72e177b0e0b0e0801bbe9f41de2abafd396dfb69af9c602ffba8c2091b3" +dependencies = [ + "libc", +] + +[[package]] +name = "rustix" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88c9c6248de4d337747b619d8f671055ef48a87dc21b97998833f189a0bbd4f" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "lazy_static", + "bitflags 2.4.1", + "errno", "libc", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] @@ -373,18 +615,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.173" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.173" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -393,36 +635,59 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] [[package]] name = "serialport" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c32634e2bd4311420caa504404a55fad2131292c485c97014cbed89a5899885f" +checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828" dependencies = [ - "CoreFoundation-sys", - "IOKit-sys", - "bitflags 2.0.2", + "bitflags 2.4.1", "cfg-if", + "core-foundation-sys", + "io-kit-sys", "libudev", "mach2", "nix", "regex", "scopeguard", + "unescaper", "winapi", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "simplelog" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786" +checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" dependencies = [ "log", "termcolor", @@ -453,19 +718,25 @@ dependencies = [ [[package]] name = "subprocess" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055cf3ebc2981ad8f0a5a17ef6652f652d87831f79fddcba2ac57bcb9a0aa407" +checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" dependencies = [ "libc", "winapi", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" -version = "2.0.26" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -514,29 +785,62 @@ dependencies = [ "test-case-core", ] +[[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" -version = "0.3.14" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", "libc", "num_threads", + "powerfmt", + "serde", + "time-core", "time-macros", ] +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] [[package]] name = "toml" -version = "0.7.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", @@ -546,18 +850,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ "indexmap", "serde", @@ -566,11 +870,32 @@ dependencies = [ "winnow", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unescaper" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f0f68e58d297ba8b22b8b5a96a87b863ba6bb46aaf51e19a4b02c5a6dd5b7f" +dependencies = [ + "thiserror", +] + [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "winapi" @@ -590,9 +915,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -603,11 +928,192 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.0" +version = "0.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" dependencies = [ "memchr", ] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 2ae828d..0304230 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ log = "*" simplelog = "^0.12.0" subprocess = "0.2.8" crc = "3.0.0" -rppal = "^0.13.1" -toml = "0.7.6" +toml = "0.8.8" +rppal = "0.16.0" serde = { version = "1.0.166", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] } serialport = "4.2.2" @@ -27,3 +27,4 @@ rpi = [] [dev-dependencies] file-per-thread-logger = "*" +zip = "0.6.6" diff --git a/Makefile b/Makefile index cc7d0fe..3019758 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ # Prerequisites 'rustup component add llvm-tools-preview' and 'cargo install grcov' build_with_cov: - RUSTFLAGS="-Cinstrument-coverage" cargo build + RUSTFLAGS="-Cinstrument-coverage" cargo build --release --features mock coverage: build_with_cov - RUSTFLAGS="-Cinstrument-coverage" LLVM_PROFILE_FILE="STS1-%p-%m.profraw" cargo test --features mock - grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage/ - firefox ./target/debug/coverage/index.html& + LLVM_PROFILE_FILE="STS1-%p-%m.profraw" cargo test --features mock --release + grcov . -s . --binary-path ./target/release/ -t html --branch --ignore-not-existing -o ./target/release/coverage/ + firefox ./target/release/coverage/index.html& sw_test: cargo build --release && RUST_LOG=info cargo test --release --features mock diff --git a/config.toml b/config.toml index 7bb1c24..d72a478 100644 --- a/config.toml +++ b/config.toml @@ -3,4 +3,3 @@ baudrate = 921600 heartbeat_pin = 34 update_pin = 35 heartbeat_freq = 10 # Hz -log_path = "./log" \ No newline at end of file From 6ae3b5e40019dd32d7eeae4da19ae4dfd04ee6ef Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Wed, 27 Dec 2023 14:28:06 +0100 Subject: [PATCH 06/10] improve test coverage --- src/communication/cep.rs | 61 ++++++++++++++++++------------------- src/communication/mod.rs | 66 ++++++++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 47 deletions(-) diff --git a/src/communication/cep.rs b/src/communication/cep.rs index 55e23b4..3e0c69c 100644 --- a/src/communication/cep.rs +++ b/src/communication/cep.rs @@ -21,7 +21,7 @@ pub enum CEPPacketHeader { } impl CEPPacket { - pub const MAXIMUM_DATA_LENGTH: usize = 32768; + pub const MAXIMUM_DATA_LENGTH: usize = 11000; pub const MAXIMUM_PACKET_LENGTH: usize = 7 + Self::MAXIMUM_DATA_LENGTH; const CRC: Crc = Crc::::new(&CRC_32_MPEG_2); @@ -71,7 +71,7 @@ impl CEPPacket { reader.read_exact(&mut header_buffer)?; let header = CEPPacketHeader::from_repr(header_buffer[0] as usize) - .ok_or(CEPParseError::WrongLength)?; + .ok_or(CEPParseError::InvalidLength)?; let packet = match header { CEPPacketHeader::Ack => CEPPacket::Ack, CEPPacketHeader::Nack => CEPPacket::Nack, @@ -82,6 +82,10 @@ impl CEPPacket { reader.read_exact(&mut length_buffer)?; let length = u16::from_le_bytes(length_buffer); + if length as usize > Self::MAXIMUM_DATA_LENGTH { + return Err(CEPParseError::InvalidLength); + } + let mut data_buffer = vec![0; length as usize]; reader.read_exact(&mut data_buffer)?; @@ -118,7 +122,7 @@ impl From<&CEPPacket> for Vec { #[derive(Debug, strum::Display)] pub enum CEPParseError { - WrongLength, + InvalidLength, InvalidHeader, InvalidCRC, Io(std::io::Error), @@ -135,34 +139,8 @@ impl From for CEPParseError { impl TryFrom> for CEPPacket { type Error = CEPParseError; - fn try_from(mut value: Vec) -> Result { - let header_byte = value.first().ok_or(CEPParseError::WrongLength)?; - let header = CEPPacketHeader::from_repr(*header_byte as usize) - .ok_or(CEPParseError::InvalidHeader)?; - - let packet = match header { - CEPPacketHeader::Ack => CEPPacket::Ack, - CEPPacketHeader::Nack => CEPPacket::Nack, - CEPPacketHeader::Stop => CEPPacket::Stop, - CEPPacketHeader::Eof => CEPPacket::Eof, - CEPPacketHeader::Data => { - let length_bytes = value.get(1..3).ok_or(CEPParseError::WrongLength)?; - let length = u16::from_le_bytes(length_bytes.try_into().unwrap()) as usize; - value.drain(0..3); - - let crc_bytes = value.drain(length..length + 4); - let crc = u32::from_le_bytes(crc_bytes.as_slice().try_into().unwrap()); - drop(crc_bytes); - - if !CEPPacket::crc_is_valid(&value, crc) { - return Err(CEPParseError::InvalidCRC); - } - - CEPPacket::Data(value) - } - }; - - Ok(packet) + fn try_from(value: Vec) -> Result { + Self::try_from_read(&mut std::io::Cursor::new(value)) } } @@ -176,8 +154,29 @@ mod tests { #[test_case(vec![0x59], CEPPacket::Eof)] #[test_case(vec![0xB4], CEPPacket::Stop)] #[test_case(vec![0x8B, 0, 0, 0xff, 0xff, 0xff, 0xff], CEPPacket::Data(vec![]); "empty Data packet")] + #[test_case(vec![0x8B, 4, 0, 0x0a, 0x0b, 0x05, 0x73, 0x52, 0x27, 0x92, 0xf4], CEPPacket::Data(vec![0x0a, 0x0b, 0x05, 0x73]); "filled data packet")] fn packet_is_parsed_and_serialized_correctly(vec: Vec, packet: CEPPacket) { assert_eq!(&packet.clone().serialize(), &vec); assert_eq!(CEPPacket::try_from(vec).unwrap(), packet); } + + #[test] + fn invalid_crc_is_rejected() { + assert!( + matches!( + CEPPacket::try_from(vec![0x8B, 4, 0, 0x0a, 0x0b, 0x05, 0x74, 0x52, 0x27, 0x92, 0xf4]), + Err(CEPParseError::InvalidCRC) + ) + ) + } + + #[test] + fn invalid_length_is_rejected() { + assert!( + matches!( + CEPPacket::try_from(vec![0x8B, 0xff, 0xff]), + Err(CEPParseError::InvalidLength) + ) + ) + } } diff --git a/src/communication/mod.rs b/src/communication/mod.rs index 096df87..f85c060 100644 --- a/src/communication/mod.rs +++ b/src/communication/mod.rs @@ -20,10 +20,11 @@ pub trait CommunicationHandle: Read + Write { fn send_packet(&mut self, packet: &CEPPacket) -> ComResult<()> { let bytes = Vec::from(packet); - self.write_all(&bytes)?; - if matches!(packet, CEPPacket::Data(_)) { - for _ in 0..Self::DATA_PACKET_RETRIES { + for _ in 0..Self::DATA_PACKET_RETRIES { + self.write_all(&bytes)?; + + if matches!(packet, CEPPacket::Data(_)) { let response = self.receive_packet()?; match response { CEPPacket::Ack => return Ok(()), @@ -33,11 +34,9 @@ pub trait CommunicationHandle: Read + Write { return Err(CommunicationError::PacketInvalidError); } } - - self.write_all(&bytes)?; + } else { + return Ok(()); } - } else { - return Ok(()); } log::error!("No ACK after {} retries, giving up", Self::DATA_PACKET_RETRIES); @@ -249,10 +248,8 @@ mod tests { com.send_packet(&CEPPacket::Data(vec![1, 2, 3])).unwrap(); - let mut expected = CEPPacket::Data(vec![1, 2, 3]).serialize(); - expected.extend(CEPPacket::Data(vec![1, 2, 3]).serialize()); - expected.extend(CEPPacket::Data(vec![1, 2, 3]).serialize()); - assert_eq!(com.written_data, expected); + assert_eq!(com.written_data, CEPPacket::Data(vec![1, 2, 3]).serialize().repeat(3)); + assert!(com.data_to_read.is_empty()); } #[test] @@ -262,11 +259,48 @@ mod tests { com.data_to_read.append(&mut CEPPacket::Nack.serialize()); } - assert!( - matches!( - com.send_packet(&CEPPacket::Data(vec![1, 2, 3])), - Err(CommunicationError::PacketInvalidError) - ) + assert!(matches!( + com.send_packet(&CEPPacket::Data(vec![1, 2, 3])), + Err(CommunicationError::PacketInvalidError) + )); + assert!(com.data_to_read.is_empty()); + assert_eq!( + com.written_data, + CEPPacket::Data(vec![1, 2, 3]).serialize().repeat(TestComHandle::DATA_PACKET_RETRIES) ); } + + #[test] + fn multi_packet_is_sent_correctly() { + let mut com = TestComHandle::default(); + + let data = vec![123u8; 2 * CEPPacket::MAXIMUM_DATA_LENGTH + 50]; + let chunks = data.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); + com.data_to_read = CEPPacket::Ack.serialize().repeat(chunks.len() + 1); + + com.send_multi_packet(&data).unwrap(); + + assert!(com.data_to_read.is_empty()); + for c in chunks { + assert_eq!(com.written_data.drain(0..c.len()+7).as_slice(), CEPPacket::Data(c.to_vec()).serialize()); + } + assert_eq!(com.written_data, CEPPacket::Eof.serialize()); + } + + #[test] + fn multi_packet_is_received_correctly() { + let mut com = TestComHandle::default(); + + let data = vec![123u8; 2 * CEPPacket::MAXIMUM_DATA_LENGTH + 50]; + let chunks = data.chunks(CEPPacket::MAXIMUM_DATA_LENGTH); + for c in chunks.clone() { + com.data_to_read.append(&mut CEPPacket::Data(c.to_vec()).serialize()); + } + com.data_to_read.append(&mut CEPPacket::Eof.serialize()); + + assert_eq!(com.receive_multi_packet(|| false).unwrap(), data); + assert!(com.data_to_read.is_empty()); + assert_eq!(com.written_data, CEPPacket::Ack.serialize().repeat(chunks.len() + 1)) + } + } From 5cc9cd3a0f0409d2d07f281f336f935d2eb9a749 Mon Sep 17 00:00:00 2001 From: zsofiak96 Date: Sun, 26 Nov 2023 16:22:48 +0100 Subject: [PATCH 07/10] refactor(build-result-archive): Change result packaging to tar --- src/command/execute_program.rs | 24 ++++++++++++++++-------- src/command/return_result.rs | 6 +++--- src/command/store_archive.rs | 1 + 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/command/execute_program.rs b/src/command/execute_program.rs index 1d3c832..9a6720e 100644 --- a/src/command/execute_program.rs +++ b/src/command/execute_program.rs @@ -44,7 +44,7 @@ pub fn execute_program( log::info!("Program {}:{} finished with {}", program_id, timestamp, exit_code); let sid = ProgramStatus { program_id, timestamp, exit_code }; let rid = ResultId { program_id, timestamp }; - build_result_archive(rid).unwrap(); // create the zip file with result and log + build_result_archive(rid).unwrap(); // create the tar file with result and log let mut context = wd_context.lock().unwrap(); context.event_vec.push(Event::Status(sid)).unwrap(); @@ -137,13 +137,13 @@ fn run_until_timeout( Err(()) } -/// The function uses `zip` to create an uncompressed archive that includes the result file specified, as well as +/// The function uses `tar` to create an uncompressed archive that includes the result file specified, as well as /// the programs stdout/stderr and the schedulers log file. If any of the files is missing, the archive /// is created without them. fn build_result_archive(res: ResultId) -> Result<(), std::io::Error> { let res_path = format!("./archives/{}/results/{}", res.program_id, res.timestamp); let log_path = format!("./data/{}_{}.log", res.program_id, res.timestamp); - let out_path = format!("./data/{}_{}.zip", res.program_id, res.timestamp); + let out_path = format!("./data/{}_{}.tar", res.program_id, res.timestamp); const MAXIMUM_FILE_SIZE: u64 = 1_000_000; for path in [&res_path, &log_path, &out_path, &"log".into()] { @@ -152,13 +152,21 @@ fn build_result_archive(res: ResultId) -> Result<(), std::io::Error> { } } - let _ = Command::new("zip") - .arg("-0") + let path_to_res = format!("./archives/{}/results", res.program_id); + let result = format!("{}", res.timestamp); + let path_to_log = String::from("../../../data"); + let log = format!("{}_{}.log", res.program_id, res.timestamp); + let _ = Command::new("tar") + .arg("-cf") .arg(out_path) - .arg("--junk-paths") + .arg("--exclude") .arg("log") - .arg(res_path) - .arg(log_path) + .arg("-C") + .arg(path_to_res) + .arg(result) + .arg("-C") + .arg(path_to_log) + .arg(log) .status(); Ok(()) diff --git a/src/command/return_result.rs b/src/command/return_result.rs index 109a886..6724298 100644 --- a/src/command/return_result.rs +++ b/src/command/return_result.rs @@ -7,7 +7,7 @@ use crate::{ use super::{truncate_to_size, CommandResult, SyncExecutionContext}; -/// Handles a complete return result command. The result zip file is only deleted if a final Ack is +/// Handles a complete return result command. The result tar file is only deleted if a final Ack is /// received. pub fn return_result( data: Vec, @@ -18,7 +18,7 @@ pub fn return_result( let program_id = u16::from_le_bytes([data[1], data[2]]); let timestamp = u32::from_le_bytes([data[3], data[4], data[5], data[6]]); - let result_path = format!("./data/{}_{}.zip", program_id, timestamp); + let result_path = format!("./data/{}_{}.tar", program_id, timestamp); if !std::path::Path::new(&result_path).exists() { com.send_packet(&CEPPacket::Nack)?; @@ -49,7 +49,7 @@ pub fn return_result( fn delete_result(res: ResultId) -> CommandResult { let res_path = format!("./archives/{}/results/{}", res.program_id, res.timestamp); let log_path = format!("./data/{}_{}.log", res.program_id, res.timestamp); - let out_path = format!("./data/{}_{}.zip", res.program_id, res.timestamp); + let out_path = format!("./data/{}_{}.tar", res.program_id, res.timestamp); let _ = std::fs::remove_file(res_path); let _ = std::fs::remove_file(log_path); let _ = std::fs::remove_file(out_path); diff --git a/src/command/store_archive.rs b/src/command/store_archive.rs index fac5ea1..ffcb232 100644 --- a/src/command/store_archive.rs +++ b/src/command/store_archive.rs @@ -31,6 +31,7 @@ pub fn store_archive( /// /// Returns Ok or passes along a file access/unzip process error fn unpack_archive(folder: String, bytes: Vec) -> CommandResult { + // Store bytes into temporary file // Store bytes into temporary file let zip_path = format!("./data/{}.zip", folder); let mut zip_file = std::fs::File::create(&zip_path)?; From 4fce4242afd43aaf8dc7f8f5b0fffab3a5bc9342 Mon Sep 17 00:00:00 2001 From: zsofiak96 Date: Sun, 3 Dec 2023 16:41:03 +0100 Subject: [PATCH 08/10] test: Adjust tests to result packaging to tar --- Cargo.lock | 44 +++++++++++++++++++++++++++ Cargo.toml | 1 + src/command/store_archive.rs | 1 - tests/simulation/full_run.rs | 9 ++++-- tests/software_tests/return_result.rs | 18 +++++------ 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f61e1d..328804b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,7 @@ dependencies = [ "simplelog", "strum", "subprocess", + "tar", "test-case", "toml", "zip", @@ -256,6 +257,18 @@ dependencies = [ "log", ] +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + [[package]] name = "filevec" version = "0.1.0" @@ -528,6 +541,15 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.10.2" @@ -743,6 +765,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1069,6 +1102,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "xattr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index 0304230..0000487 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ serde = { version = "1.0.166", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] } serialport = "4.2.2" test-case = "3.3.1" +tar = "0.4.40" [dependencies.filevec] path = "lib/filevec" diff --git a/src/command/store_archive.rs b/src/command/store_archive.rs index ffcb232..fac5ea1 100644 --- a/src/command/store_archive.rs +++ b/src/command/store_archive.rs @@ -31,7 +31,6 @@ pub fn store_archive( /// /// Returns Ok or passes along a file access/unzip process error fn unpack_archive(folder: String, bytes: Vec) -> CommandResult { - // Store bytes into temporary file // Store bytes into temporary file let zip_path = format!("./data/{}.zip", folder); let mut zip_file = std::fs::File::create(&zip_path)?; diff --git a/tests/simulation/full_run.rs b/tests/simulation/full_run.rs index 441fa9b..e357112 100644 --- a/tests/simulation/full_run.rs +++ b/tests/simulation/full_run.rs @@ -17,10 +17,15 @@ fn full_run() { // Check result let result = simulate_return_result(&mut com, 1, 3).unwrap(); - let mut result_archive = zip::ZipArchive::new(Cursor::new(result)).unwrap(); + let mut result_archive = tar::Archive::new(Cursor::new(result)); com.send_packet(&CEPPacket::Ack).unwrap(); - let result_file = result_archive.by_name(&"3").unwrap(); + let result_file = result_archive + .entries() + .unwrap() + .find(|x| x.as_ref().unwrap().header().path().unwrap().ends_with("3")) + .unwrap() + .unwrap(); assert_eq!(result_file.bytes().map(|b| b.unwrap()).collect::>(), vec![0xde, 0xad]); assert_eq!(simulate_get_status(&mut com).unwrap(), [0]); diff --git a/tests/software_tests/return_result.rs b/tests/software_tests/return_result.rs index b8b9513..f5d16c3 100644 --- a/tests/software_tests/return_result.rs +++ b/tests/software_tests/return_result.rs @@ -26,10 +26,8 @@ fn returns_result_correctly() -> TestResult { COBC(Data(return_result(7, 3))), EDU(Ack), ACTION(Box::new(|packet| { - std::fs::File::create("tests/tmp/7.zip") - .unwrap() - .write(&packet.clone().serialize()) - .unwrap(); + let bytes = packet.clone().serialize(); + std::fs::write("tests/tmp/7.tar", &bytes[3..bytes.len()-4]).unwrap(); })), COBC(Ack), EDU(Eof), @@ -46,10 +44,10 @@ fn returns_result_correctly() -> TestResult { command::handle_command(&mut com, &mut exec); assert!(com.is_complete()); - std::process::Command::new("unzip") + std::process::Command::new("tar") .current_dir("./tests/tmp") - .arg("-o") - .arg("7.zip") + .arg("xf") + .arg("7.tar") .status()?; assert_eq!(std::fs::read("tests/tmp/3")?, vec![0xde, 0xad]); @@ -80,7 +78,7 @@ fn truncate_result() -> TestResult { command::handle_command(&mut com, &mut exec); assert!(com.is_complete()); - assert!(std::fs::File::open("./data/8_5.zip")?.metadata()?.len() < 1_001_000); + assert!(std::fs::File::open("./data/8_5.tar")?.metadata()?.len() < 1_005_000); common::cleanup("8"); Ok(()) @@ -122,7 +120,7 @@ fn stopped_return() -> TestResult { command::handle_command(&mut com, &mut exec); assert!(com.is_complete()); - assert!(std::fs::File::open("./data/9_5.zip").is_ok()); + assert!(std::fs::File::open("./data/9_5.tar").is_ok()); common::cleanup("9"); Ok(()) @@ -162,7 +160,7 @@ fn result_is_not_deleted_after_corrupted_transfer() -> TestResult { command::handle_command(&mut com, &mut exec); assert!(com.is_complete()); - assert!(std::fs::File::open("./data/50_0.zip").is_ok()); + assert!(std::fs::File::open("./data/50_0.tar").is_ok()); common::cleanup("50"); Ok(()) From 3dc1414a7709af0c4b0c3c4b8ee50e65193f2cde Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Mon, 1 Jan 2024 14:34:41 +0100 Subject: [PATCH 09/10] Make result tar paths absolute --- src/command/execute_program.rs | 14 +++----------- tests/software_tests/return_result.rs | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/command/execute_program.rs b/src/command/execute_program.rs index 9a6720e..a8fcdb7 100644 --- a/src/command/execute_program.rs +++ b/src/command/execute_program.rs @@ -152,22 +152,14 @@ fn build_result_archive(res: ResultId) -> Result<(), std::io::Error> { } } - let path_to_res = format!("./archives/{}/results", res.program_id); - let result = format!("{}", res.timestamp); - let path_to_log = String::from("../../../data"); - let log = format!("{}_{}.log", res.program_id, res.timestamp); let _ = Command::new("tar") .arg("-cf") .arg(out_path) - .arg("--exclude") + .arg(res_path) + .arg(log_path) .arg("log") - .arg("-C") - .arg(path_to_res) - .arg(result) - .arg("-C") - .arg(path_to_log) - .arg(log) .status(); Ok(()) } + diff --git a/tests/software_tests/return_result.rs b/tests/software_tests/return_result.rs index f5d16c3..3d022ee 100644 --- a/tests/software_tests/return_result.rs +++ b/tests/software_tests/return_result.rs @@ -50,8 +50,8 @@ fn returns_result_correctly() -> TestResult { .arg("7.tar") .status()?; - assert_eq!(std::fs::read("tests/tmp/3")?, vec![0xde, 0xad]); - assert!(std::fs::read("tests/tmp/7_3.log").is_ok()); + assert_eq!(std::fs::read("tests/tmp/archives/7/results/3")?, vec![0xde, 0xad]); + assert!(std::fs::read("tests/tmp/data/7_3.log").is_ok()); common::cleanup("7"); Ok(()) From e285c99b29d67fee71bfd98c00062007232af84f Mon Sep 17 00:00:00 2001 From: Florian Guggi Date: Wed, 3 Jan 2024 13:34:05 +0100 Subject: [PATCH 10/10] Replace tar invocation with tar crate --- Cargo.lock | 302 -------------------------- Cargo.toml | 1 - src/command/common.rs | 11 +- src/command/execute_program.rs | 39 ++-- src/command/return_result.rs | 5 +- tests/software_tests/return_result.rs | 11 +- 6 files changed, 37 insertions(+), 332 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 328804b..c5a26df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,24 +19,6 @@ dependencies = [ "tar", "test-case", "toml", - "zip", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aes" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", ] [[package]] @@ -54,12 +36,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bitflags" version = "1.3.2" @@ -72,89 +48,24 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "cc" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "jobserver", - "libc", -] - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "cpufeatures" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" -dependencies = [ - "libc", -] - [[package]] name = "crc" version = "3.0.1" @@ -170,34 +81,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "deranged" version = "0.3.10" @@ -207,17 +90,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - [[package]] name = "env_logger" version = "0.10.1" @@ -277,26 +149,6 @@ dependencies = [ "serde", ] -[[package]] -name = "flate2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "hashbrown" version = "0.14.3" @@ -315,15 +167,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "humantime" version = "2.1.0" @@ -340,15 +183,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - [[package]] name = "io-kit-sys" version = "0.4.0" @@ -376,15 +210,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "libc" version = "0.2.151" @@ -438,15 +263,6 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - [[package]] name = "nix" version = "0.26.4" @@ -476,35 +292,12 @@ dependencies = [ "libc", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", -] - [[package]] name = "pkg-config" version = "0.3.27" @@ -535,12 +328,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - [[package]] name = "redox_syscall" version = "0.4.1" @@ -683,28 +470,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "simplelog" version = "0.12.1" @@ -748,12 +513,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - [[package]] name = "syn" version = "2.0.41" @@ -903,12 +662,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "unescaper" version = "0.1.3" @@ -924,12 +677,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "winapi" version = "0.3.9" @@ -1112,52 +859,3 @@ dependencies = [ "linux-raw-sys", "rustix", ] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2", - "sha1", - "time", - "zstd", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 0000487..00e44ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,4 +28,3 @@ rpi = [] [dev-dependencies] file-per-thread-logger = "*" -zip = "0.6.6" diff --git a/src/command/common.rs b/src/command/common.rs index f512657..e3ec682 100644 --- a/src/command/common.rs +++ b/src/command/common.rs @@ -19,18 +19,15 @@ pub fn check_length( } } -/// Truncates the file at `path` to the given size. Returns wether it actually had to truncate. -pub fn truncate_to_size(path: &str, n_bytes: u64) -> Result { - log::info!("Truncating {:?}", &path); - let file = std::fs::File::options().write(true).open(path)?; +/// Truncates the files to at most `n_bytes` +pub fn truncate_to_size(file: &mut std::fs::File, n_bytes: u64) -> Result<(), std::io::Error> { let size = file.metadata()?.len(); if size > n_bytes { file.set_len(n_bytes)?; file.sync_all()?; - Ok(true) - } else { - Ok(false) } + + Ok(()) } /// If no program is currently running, this function simply returns. Otherwise it signals the diff --git a/src/command/execute_program.rs b/src/command/execute_program.rs index a8fcdb7..7b889bf 100644 --- a/src/command/execute_program.rs +++ b/src/command/execute_program.rs @@ -1,4 +1,8 @@ -use std::{path::Path, process::Command, time::Duration}; +use std::{ + io::ErrorKind, + path::{Path, PathBuf}, + time::Duration, +}; use subprocess::Popen; @@ -141,25 +145,28 @@ fn run_until_timeout( /// the programs stdout/stderr and the schedulers log file. If any of the files is missing, the archive /// is created without them. fn build_result_archive(res: ResultId) -> Result<(), std::io::Error> { - let res_path = format!("./archives/{}/results/{}", res.program_id, res.timestamp); - let log_path = format!("./data/{}_{}.log", res.program_id, res.timestamp); - let out_path = format!("./data/{}_{}.tar", res.program_id, res.timestamp); + let res_path = + PathBuf::from(format!("./archives/{}/results/{}", res.program_id, res.timestamp)); + let student_log_path = + PathBuf::from(format!("./data/{}_{}.log", res.program_id, res.timestamp)); + let log_path = PathBuf::from("log"); + + let out_path = PathBuf::from(&format!("./data/{}_{}.tar", res.program_id, res.timestamp)); + let mut archive = tar::Builder::new(std::fs::File::create(out_path)?); const MAXIMUM_FILE_SIZE: u64 = 1_000_000; - for path in [&res_path, &log_path, &out_path, &"log".into()] { - if let Ok(true) = truncate_to_size(path, MAXIMUM_FILE_SIZE) { - log::warn!("Truncating {} from {} bytes", path, MAXIMUM_FILE_SIZE); - } + for path in &[res_path, student_log_path, log_path] { + let mut file = match std::fs::File::options().read(true).write(true).open(path) { + Ok(f) => f, + Err(e) if e.kind() == ErrorKind::NotFound => continue, + Err(e) => return Err(e), + }; + + truncate_to_size(&mut file, MAXIMUM_FILE_SIZE)?; + archive.append_file(path.file_name().unwrap(), &mut file)?; } - let _ = Command::new("tar") - .arg("-cf") - .arg(out_path) - .arg(res_path) - .arg(log_path) - .arg("log") - .status(); + archive.finish()?; Ok(()) } - diff --git a/src/command/return_result.rs b/src/command/return_result.rs index 6724298..0f0292e 100644 --- a/src/command/return_result.rs +++ b/src/command/return_result.rs @@ -53,7 +53,10 @@ fn delete_result(res: ResultId) -> CommandResult { let _ = std::fs::remove_file(res_path); let _ = std::fs::remove_file(log_path); let _ = std::fs::remove_file(out_path); - let _ = truncate_to_size("log", 0); + + if let Ok(mut file) = std::fs::File::options().write(true).open("log") { + truncate_to_size(&mut file, 0)?; + } Ok(()) } diff --git a/tests/software_tests/return_result.rs b/tests/software_tests/return_result.rs index 3d022ee..d783e45 100644 --- a/tests/software_tests/return_result.rs +++ b/tests/software_tests/return_result.rs @@ -27,7 +27,7 @@ fn returns_result_correctly() -> TestResult { EDU(Ack), ACTION(Box::new(|packet| { let bytes = packet.clone().serialize(); - std::fs::write("tests/tmp/7.tar", &bytes[3..bytes.len()-4]).unwrap(); + std::fs::write("tests/tmp/7.tar", &bytes[3..bytes.len() - 4]).unwrap(); })), COBC(Ack), EDU(Eof), @@ -44,14 +44,15 @@ fn returns_result_correctly() -> TestResult { command::handle_command(&mut com, &mut exec); assert!(com.is_complete()); + let _ = std::fs::create_dir("./tests/tmp/7_unpack"); std::process::Command::new("tar") - .current_dir("./tests/tmp") + .current_dir("./tests/tmp/7_unpack") .arg("xf") - .arg("7.tar") + .arg("../7.tar") .status()?; - assert_eq!(std::fs::read("tests/tmp/archives/7/results/3")?, vec![0xde, 0xad]); - assert!(std::fs::read("tests/tmp/data/7_3.log").is_ok()); + assert_eq!(std::fs::read("./tests/tmp/7_unpack/3")?, vec![0xde, 0xad]); + assert!(std::fs::read("./tests/tmp/7_unpack/7_3.log").is_ok()); common::cleanup("7"); Ok(())