diff --git a/satrs-core/Cargo.toml b/satrs-core/Cargo.toml index 6fa8535..d16f0a0 100644 --- a/satrs-core/Cargo.toml +++ b/satrs-core/Cargo.toml @@ -73,11 +73,10 @@ features = ["all"] optional = true [dependencies.spacepackets] -version = "0.7.0-beta.2" +version = "0.7.0-beta.4" default-features = false # git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" -# rev = "79d26e1a6" -# branch = "" +# rev = "297cfad22637d3b07a1b27abe56d9a607b5b82a7" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" @@ -91,6 +90,7 @@ zerocopy = "0.7" once_cell = "1.13" serde_json = "1" rand = "0.8" +tempfile = "3" [dev-dependencies.postcard] version = "1" diff --git a/satrs-core/src/cfdp/dest.rs b/satrs-core/src/cfdp/dest.rs index b66365a..1899abd 100644 --- a/satrs-core/src/cfdp/dest.rs +++ b/satrs-core/src/cfdp/dest.rs @@ -1,47 +1,32 @@ -use core::str::{from_utf8, Utf8Error}; -use std::{ - fs::{metadata, File}, - io::{BufReader, Read, Seek, SeekFrom, Write}, - path::{Path, PathBuf}, -}; - use crate::cfdp::user::TransactionFinishedParams; +use core::str::{from_utf8, Utf8Error}; +use std::path::{Path, PathBuf}; use super::{ - user::{CfdpUser, MetadataReceivedParams}, - PacketInfo, PacketTarget, State, TransactionId, TransactionStep, CRC_32, + filestore::{FilestoreError, VirtualFilestore}, + user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams}, + CheckTimer, CheckTimerCreator, EntityType, LocalEntityConfig, PacketInfo, PacketTarget, + RemoteEntityConfig, RemoteEntityConfigProvider, State, TimerContext, TransactionId, + TransactionStep, }; +use alloc::boxed::Box; use smallvec::SmallVec; use spacepackets::{ cfdp::{ pdu::{ eof::EofPdu, file_data::FileDataPdu, - finished::{DeliveryCode, FileStatus, FinishedPdu}, - metadata::{MetadataGenericParams, MetadataPdu}, - CommonPduConfig, FileDirectiveType, PduError, PduHeader, + finished::{DeliveryCode, FileStatus, FinishedPduCreator}, + metadata::{MetadataGenericParams, MetadataPduReader}, + CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }, - tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, TlvType}, - ConditionCode, PduType, TransmissionMode, + tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, TlvType}, + ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode, }, util::UnsignedByteField, }; use thiserror::Error; -pub struct DestinationHandler { - id: UnsignedByteField, - step: TransactionStep, - state: State, - tparams: TransactionParams, - packets_to_send_ctx: PacketsToSendContext, -} - -#[derive(Debug, Default)] -struct PacketsToSendContext { - packet_available: bool, - directive: Option, -} - #[derive(Debug)] struct FileProperties { src_file_name: [u8; u8::MAX as usize], @@ -51,28 +36,45 @@ struct FileProperties { dest_path_buf: PathBuf, } +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +enum CompletionDisposition { + Completed = 0, + Cancelled = 1, +} + #[derive(Debug)] struct TransferState { transaction_id: Option, - progress: usize, + metadata_params: MetadataGenericParams, + progress: u64, + metadata_only: bool, condition_code: ConditionCode, delivery_code: DeliveryCode, file_status: FileStatus, - metadata_params: MetadataGenericParams, + completion_disposition: CompletionDisposition, + checksum: u32, + current_check_count: u32, + current_check_timer: Option>, } impl Default for TransferState { fn default() -> Self { Self { transaction_id: None, + metadata_params: Default::default(), progress: Default::default(), + metadata_only: false, condition_code: ConditionCode::NoError, delivery_code: DeliveryCode::Incomplete, file_status: FileStatus::Unreported, - metadata_params: Default::default(), + completion_disposition: CompletionDisposition::Completed, + checksum: 0, + current_check_count: 0, + current_check_timer: None, } } } + #[derive(Debug)] struct TransactionParams { tstate: TransferState, @@ -81,6 +83,13 @@ struct TransactionParams { cksum_buf: [u8; 1024], msgs_to_user_size: usize, msgs_to_user_buf: [u8; 1024], + remote_cfg: Option, +} + +impl TransactionParams { + fn transmission_mode(&self) -> TransmissionMode { + self.pdu_conf.trans_mode + } } impl Default for FileProperties { @@ -96,8 +105,8 @@ impl Default for FileProperties { } impl TransactionParams { - fn file_size(&self) -> usize { - self.tstate.metadata_params.file_size as usize + fn file_size(&self) -> u64 { + self.tstate.metadata_params.file_size } fn metadata_params(&self) -> &MetadataGenericParams { @@ -114,6 +123,7 @@ impl Default for TransactionParams { msgs_to_user_buf: [0; 1024], tstate: Default::default(), file_properties: Default::default(), + remote_cfg: None, } } } @@ -122,6 +132,7 @@ impl TransactionParams { fn reset(&mut self) { self.tstate.condition_code = ConditionCode::NoError; self.tstate.delivery_code = DeliveryCode::Incomplete; + self.tstate.file_status = FileStatus::Unreported; } } @@ -141,36 +152,137 @@ pub enum DestError { EmptySrcFileField, #[error("empty dest file field")] EmptyDestFileField, + #[error("packets to be sent are still left")] + PacketToSendLeft, #[error("pdu error {0}")] Pdu(#[from] PduError), #[error("io error {0}")] Io(#[from] std::io::Error), + #[error("file store error {0}")] + Filestore(#[from] FilestoreError), #[error("path conversion error {0}")] PathConversion(#[from] Utf8Error), #[error("error building dest path from source file name and dest folder")] - PathConcatError, + PathConcat, + #[error("no remote entity configuration found for {0:?}")] + NoRemoteCfgFound(UnsignedByteField), +} + +pub trait CfdpPacketSender: Send { + fn send_pdu( + &mut self, + pdu_type: PduType, + file_directive_type: Option, + raw_pdu: &[u8], + ) -> Result<(), PduError>; +} + +/// This is the primary CFDP destination handler. It models the CFDP destination entity, which is +/// primarily responsible for receiving files sent from another CFDP entity. It performs the +/// reception side of File Copy Operations. +/// +/// The [DestinationHandler::state_machine] function is the primary function to drive the +/// destination handler. It can be used to insert packets into the destination +/// handler and driving the state machine, which might generate new +/// packets to be sent to the remote entity. Please note that the destination handler can also +/// only process Metadata, EOF and Prompt PDUs in addition to ACK PDUs where the acknowledged +/// PDU is the Finished PDU. +/// +/// All generated packets are sent via the [CfdpPacketSender] trait, which is implemented by the +/// user and passed as a constructor parameter. The number of generated packets is returned +/// by the state machine call. +pub struct DestinationHandler { + local_cfg: LocalEntityConfig, + step: TransactionStep, + state: State, + tparams: TransactionParams, + packet_buf: alloc::vec::Vec, + packet_sender: Box, + vfs: Box, + remote_cfg_table: Box, + check_timer_creator: Box, } impl DestinationHandler { - pub fn new(id: impl Into) -> Self { + /// Constructs a new destination handler. + /// + /// # Arguments + /// + /// * `local_cfg` - The local CFDP entity configuration, consisting of the local entity ID, + /// the indication configuration, and the fault handlers. + /// * `max_packet_len` - The maximum expected generated packet size in bytes. Each time a + /// packet is sent, it will be buffered inside an internal buffer. The length of this buffer + /// will be determined by this parameter. This parameter can either be a known upper bound, + /// or it can specifically be determined by the largest packet size parameter of all remote + /// entity configurations in the passed `remote_cfg_table`. + /// * `packet_sender` - All generated packets are sent via this abstraction. + /// * `vfs` - Virtual filestore implementation to decouple the CFDP implementation from the + /// underlying filestore/filesystem. This allows to use this handler for embedded systems + /// where a standard runtime might not be available. + /// * `remote_cfg_table` - A table of all expected remote entities this entity will communicate + /// with. It contains various configuration parameters required for file transfers. + /// * `check_timer_creator` - This is used by the CFDP handler to generate timers required + /// by various tasks. + pub fn new( + local_cfg: LocalEntityConfig, + max_packet_len: usize, + packet_sender: Box, + vfs: Box, + remote_cfg_table: Box, + check_timer_creator: Box, + ) -> Self { Self { - id: id.into(), + local_cfg, step: TransactionStep::Idle, state: State::Idle, tparams: Default::default(), - packets_to_send_ctx: Default::default(), + packet_buf: alloc::vec![0; max_packet_len], + packet_sender, + vfs, + remote_cfg_table, + check_timer_creator, } } - pub fn state_machine(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { + /// This is the core function to drive the destination handler. It is also used to insert + /// packets into the destination handler. + /// + /// The state machine should either be called if a packet with the appropriate destination ID + /// is received, or periodically in IDLE periods to perform all CFDP related tasks, for example + /// checking for timeouts or missed file segments. + pub fn state_machine( + &mut self, + cfdp_user: &mut impl CfdpUser, + packet_to_insert: Option<&PacketInfo>, + ) -> Result { + if let Some(packet) = packet_to_insert { + self.insert_packet(cfdp_user, packet)?; + } match self.state { State::Idle => todo!(), - State::BusyClass1Nacked => self.fsm_nacked(cfdp_user), - State::BusyClass2Acked => todo!("acknowledged mode not implemented yet"), + State::Busy => self.fsm_busy(cfdp_user), + State::Suspended => todo!(), } } - pub fn insert_packet(&mut self, packet_info: &PacketInfo) -> Result<(), DestError> { + /// Returns [None] if the state machine is IDLE, and the transmission mode of the current + /// request otherwise. + pub fn transmission_mode(&self) -> Option { + if self.state == State::Idle { + return None; + } + Some(self.tparams.transmission_mode()) + } + + pub fn transaction_id(&self) -> Option { + self.tstate().transaction_id + } + + fn insert_packet( + &mut self, + cfdp_user: &mut impl CfdpUser, + packet_info: &PacketInfo, + ) -> Result<(), DestError> { if packet_info.target() != PacketTarget::DestEntity { // Unwrap is okay here, a PacketInfo for a file data PDU should always have the // destination as the target. @@ -184,68 +296,23 @@ impl DestinationHandler { return Err(DestError::DirectiveExpected); } self.handle_file_directive( + cfdp_user, packet_info.pdu_directive.unwrap(), packet_info.raw_packet, ) } - PduType::FileData => self.handle_file_data(packet_info.raw_packet), + PduType::FileData => self.handle_file_data(cfdp_user, packet_info.raw_packet), } } - pub fn packet_to_send_ready(&self) -> bool { - self.packets_to_send_ctx.packet_available - } - - pub fn get_next_packet_to_send( - &self, - buf: &mut [u8], - ) -> Result, DestError> { - if !self.packet_to_send_ready() { - return Ok(None); - } - let directive = self.packets_to_send_ctx.directive.unwrap(); - let written_size = match directive { - FileDirectiveType::FinishedPdu => { - let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0); - let finished_pdu = if self.tparams.tstate.condition_code == ConditionCode::NoError - || self.tparams.tstate.condition_code == ConditionCode::UnsupportedChecksumType - { - FinishedPdu::new_default( - pdu_header, - self.tparams.tstate.delivery_code, - self.tparams.tstate.file_status, - ) - } else { - // TODO: Are there cases where this ID is actually the source entity ID? - let entity_id = EntityIdTlv::new(self.id); - FinishedPdu::new_with_error( - pdu_header, - self.tparams.tstate.condition_code, - self.tparams.tstate.delivery_code, - self.tparams.tstate.file_status, - entity_id, - ) - }; - finished_pdu.write_to_bytes(buf)? - } - FileDirectiveType::AckPdu => todo!(), - FileDirectiveType::NakPdu => todo!(), - FileDirectiveType::KeepAlivePdu => todo!(), - _ => { - // This should never happen and is considered an internal impl error - panic!("invalid file directive {directive:?} for dest handler send packet"); - } - }; - Ok(Some((directive, written_size))) - } - - pub fn handle_file_directive( + fn handle_file_directive( &mut self, + cfdp_user: &mut impl CfdpUser, pdu_directive: FileDirectiveType, raw_packet: &[u8], ) -> Result<(), DestError> { match pdu_directive { - FileDirectiveType::EofPdu => self.handle_eof_pdu(raw_packet)?, + FileDirectiveType::EofPdu => self.handle_eof_pdu(cfdp_user, raw_packet)?, FileDirectiveType::FinishedPdu | FileDirectiveType::NakPdu | FileDirectiveType::KeepAlivePdu => { @@ -262,30 +329,47 @@ impl DestinationHandler { Ok(()) } - pub fn handle_metadata_pdu(&mut self, raw_packet: &[u8]) -> Result<(), DestError> { + fn handle_metadata_pdu(&mut self, raw_packet: &[u8]) -> Result<(), DestError> { if self.state != State::Idle { return Err(DestError::RecvdMetadataButIsBusy); } - let metadata_pdu = MetadataPdu::from_bytes(raw_packet)?; + let metadata_pdu = MetadataPduReader::from_bytes(raw_packet)?; self.tparams.reset(); self.tparams.tstate.metadata_params = *metadata_pdu.metadata_params(); + let remote_cfg = self + .remote_cfg_table + .get_remote_config(metadata_pdu.source_id().value()); + if remote_cfg.is_none() { + return Err(DestError::NoRemoteCfgFound(metadata_pdu.dest_id())); + } + self.tparams.remote_cfg = Some(*remote_cfg.unwrap()); + + // TODO: Support for metadata only PDUs. let src_name = metadata_pdu.src_file_name(); - if src_name.is_empty() { + let dest_name = metadata_pdu.dest_file_name(); + if src_name.is_empty() && dest_name.is_empty() { + self.tparams.tstate.metadata_only = true; + } + if !self.tparams.tstate.metadata_only && src_name.is_empty() { return Err(DestError::EmptySrcFileField); } - self.tparams.file_properties.src_file_name[..src_name.len_value()] - .copy_from_slice(src_name.value()); - self.tparams.file_properties.src_file_name_len = src_name.len_value(); - let dest_name = metadata_pdu.dest_file_name(); - if dest_name.is_empty() { + if !self.tparams.tstate.metadata_only && dest_name.is_empty() { return Err(DestError::EmptyDestFileField); } - self.tparams.file_properties.dest_file_name[..dest_name.len_value()] - .copy_from_slice(dest_name.value()); - self.tparams.file_properties.dest_file_name_len = dest_name.len_value(); - self.tparams.pdu_conf = *metadata_pdu.pdu_header().common_pdu_conf(); - self.tparams.msgs_to_user_size = 0; - if metadata_pdu.options().is_some() { + if !self.tparams.tstate.metadata_only { + self.tparams.file_properties.src_file_name[..src_name.len_value()] + .copy_from_slice(src_name.value()); + self.tparams.file_properties.src_file_name_len = src_name.len_value(); + if dest_name.is_empty() { + return Err(DestError::EmptyDestFileField); + } + self.tparams.file_properties.dest_file_name[..dest_name.len_value()] + .copy_from_slice(dest_name.value()); + self.tparams.file_properties.dest_file_name_len = dest_name.len_value(); + self.tparams.pdu_conf = *metadata_pdu.pdu_header().common_pdu_conf(); + self.tparams.msgs_to_user_size = 0; + } + if !metadata_pdu.options().is_empty() { for option_tlv in metadata_pdu.options_iter().unwrap() { if option_tlv.is_standard_tlv() && option_tlv.tlv_type().unwrap() == TlvType::MsgToUser @@ -297,82 +381,198 @@ impl DestinationHandler { } } } - if self.tparams.pdu_conf.trans_mode == TransmissionMode::Unacknowledged { - self.state = State::BusyClass1Nacked; - } else { - self.state = State::BusyClass2Acked; - } + self.state = State::Busy; self.step = TransactionStep::TransactionStart; Ok(()) } - pub fn handle_file_data(&mut self, raw_packet: &[u8]) -> Result<(), DestError> { - if self.state == State::Idle || self.step != TransactionStep::ReceivingFileDataPdus { + fn handle_file_data( + &mut self, + user: &mut impl CfdpUser, + raw_packet: &[u8], + ) -> Result<(), DestError> { + if self.state == State::Idle + || (self.step != TransactionStep::ReceivingFileDataPdus + && self.step != TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling) + { return Err(DestError::WrongStateForFileDataAndEof); } let fd_pdu = FileDataPdu::from_bytes(raw_packet)?; - let mut dest_file = File::options() - .write(true) - .open(&self.tparams.file_properties.dest_path_buf)?; - dest_file.seek(SeekFrom::Start(fd_pdu.offset()))?; - dest_file.write_all(fd_pdu.file_data())?; + if self.local_cfg.indication_cfg.file_segment_recv { + user.file_segment_recvd_indication(&FileSegmentRecvdParams { + id: self.tstate().transaction_id.unwrap(), + offset: fd_pdu.offset(), + length: fd_pdu.file_data().len(), + segment_metadata: fd_pdu.segment_metadata(), + }); + } + if let Err(e) = self.vfs.write_data( + self.tparams.file_properties.dest_path_buf.to_str().unwrap(), + fd_pdu.offset(), + fd_pdu.file_data(), + ) { + self.declare_fault(ConditionCode::FilestoreRejection); + return Err(e.into()); + } + self.tstate_mut().progress += fd_pdu.file_data().len() as u64; Ok(()) } - #[allow(clippy::needless_if)] - pub fn handle_eof_pdu(&mut self, raw_packet: &[u8]) -> Result<(), DestError> { + fn handle_eof_pdu( + &mut self, + cfdp_user: &mut impl CfdpUser, + raw_packet: &[u8], + ) -> Result<(), DestError> { if self.state == State::Idle || self.step != TransactionStep::ReceivingFileDataPdus { return Err(DestError::WrongStateForFileDataAndEof); } let eof_pdu = EofPdu::from_bytes(raw_packet)?; - let checksum = eof_pdu.file_checksum(); - // For a standard disk based file system, which is assumed to be used for now, the file - // will always be retained. This might change in the future. - self.tparams.tstate.file_status = FileStatus::Retained; - if self.checksum_check(checksum)? { - self.tparams.tstate.condition_code = ConditionCode::NoError; - self.tparams.tstate.delivery_code = DeliveryCode::Complete; - } else { - self.tparams.tstate.condition_code = ConditionCode::FileChecksumFailure; + if self.local_cfg.indication_cfg.eof_recv { + // Unwrap is okay here, application logic ensures that transaction ID is valid here. + cfdp_user.eof_recvd_indication(self.tparams.tstate.transaction_id.as_ref().unwrap()); } - // TODO: Check progress, and implement transfer completion timer as specified in the - // standard. This timer protects against out of order arrival of packets. - if self.tparams.tstate.progress != self.tparams.file_size() {} - if self.state == State::BusyClass1Nacked { - self.step = TransactionStep::TransferCompletion; + let regular_transfer_finish = if eof_pdu.condition_code() == ConditionCode::NoError { + self.handle_no_error_eof_pdu(&eof_pdu)? } else { - self.step = TransactionStep::SendingAckPdu; + todo!("implement cancel request handling"); + }; + if regular_transfer_finish { + self.file_transfer_complete_transition(); } Ok(()) } + /// Returns whether the transfer can be completed regularly. + fn handle_no_error_eof_pdu(&mut self, eof_pdu: &EofPdu) -> Result { + // CFDP 4.6.1.2.9: Declare file size error if progress exceeds file size + if self.tparams.tstate.progress > eof_pdu.file_size() + && self.declare_fault(ConditionCode::FileSizeError) != FaultHandlerCode::IgnoreError + { + return Ok(false); + } else if (self.tparams.tstate.progress < eof_pdu.file_size()) + && self.tparams.transmission_mode() == TransmissionMode::Acknowledged + { + // CFDP 4.6.4.3.1: The end offset of the last received file segment and the file + // size as stated in the EOF PDU is not the same, so we need to add that segment to + // the lost segments for the deferred lost segment detection procedure. + // TODO: Proper lost segment handling. + // self._params.acked_params.lost_seg_tracker.add_lost_segment( + // (self._params.fp.progress, self._params.fp.file_size_eof) + // ) + } + + self.tparams.tstate.checksum = eof_pdu.file_checksum(); + if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged + && !self.checksum_verify(self.tparams.tstate.checksum) + { + if self.declare_fault(ConditionCode::FileChecksumFailure) + != FaultHandlerCode::IgnoreError + { + return Ok(false); + } + self.start_check_limit_handling(); + return Ok(false); + } + Ok(true) + } + + fn file_transfer_complete_transition(&mut self) { + if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged { + self.step = TransactionStep::TransferCompletion; + } else { + // TODO: Prepare ACK PDU somehow. + self.step = TransactionStep::SendingAckPdu; + } + } + + fn checksum_verify(&mut self, checksum: u32) -> bool { + let mut file_delivery_complete = false; + if self.tparams.metadata_params().checksum_type == ChecksumType::NullChecksum + || self.tparams.tstate.metadata_only + { + file_delivery_complete = true; + self.tparams.tstate.delivery_code = DeliveryCode::Complete; + self.tparams.tstate.condition_code = ConditionCode::NoError; + } else { + match self.vfs.checksum_verify( + self.tparams.file_properties.dest_path_buf.to_str().unwrap(), + self.tparams.metadata_params().checksum_type, + checksum, + &mut self.tparams.cksum_buf, + ) { + Ok(checksum_success) => { + file_delivery_complete = checksum_success; + } + Err(e) => match e { + FilestoreError::ChecksumTypeNotImplemented(_) => { + self.declare_fault(ConditionCode::UnsupportedChecksumType); + // For this case, the applicable algorithm shall be the the null checksum, + // which is always succesful. + file_delivery_complete = true; + } + _ => { + self.declare_fault(ConditionCode::FilestoreRejection); + // Treat this equivalent to a failed checksum procedure. + } + }, + }; + } + file_delivery_complete + } + + fn start_check_limit_handling(&mut self) { + self.step = TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling; + self.tparams.tstate.current_check_timer = Some( + self.check_timer_creator + .get_check_timer_provider(TimerContext::CheckLimit { + local_id: self.local_cfg.id, + remote_id: self.tparams.remote_cfg.unwrap().entity_id, + entity_type: EntityType::Receiving, + }), + ); + self.tparams.tstate.current_check_count = 0; + } + + fn check_limit_handling(&mut self) { + if self.tparams.tstate.current_check_timer.is_none() { + return; + } + let check_timer = self.tparams.tstate.current_check_timer.as_ref().unwrap(); + if check_timer.has_expired() { + if self.checksum_verify(self.tparams.tstate.checksum) { + self.file_transfer_complete_transition(); + return; + } + if self.tparams.tstate.current_check_count + 1 + >= self.tparams.remote_cfg.unwrap().check_limit + { + self.declare_fault(ConditionCode::CheckLimitReached); + } else { + self.tparams.tstate.current_check_count += 1; + self.tparams + .tstate + .current_check_timer + .as_mut() + .unwrap() + .reset(); + } + } + } + pub fn handle_prompt_pdu(&mut self, _raw_packet: &[u8]) -> Result<(), DestError> { todo!(); } - fn checksum_check(&mut self, expected_checksum: u32) -> Result { - let mut digest = CRC_32.digest(); - let file_to_check = File::open(&self.tparams.file_properties.dest_path_buf)?; - let mut buf_reader = BufReader::new(file_to_check); - loop { - let bytes_read = buf_reader.read(&mut self.tparams.cksum_buf)?; - if bytes_read == 0 { - break; - } - digest.update(&self.tparams.cksum_buf[0..bytes_read]); - } - if digest.finalize() == expected_checksum { - return Ok(true); - } - Ok(false) - } - - fn fsm_nacked(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { + fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { + let mut sent_packets = 0; if self.step == TransactionStep::TransactionStart { self.transaction_start(cfdp_user)?; } + if self.step == TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling { + self.check_limit_handling(); + } if self.step == TransactionStep::TransferCompletion { - self.transfer_completion(cfdp_user)?; + sent_packets += self.transfer_completion(cfdp_user)?; } if self.step == TransactionStep::SendingAckPdu { todo!("no support for acknowledged mode yet"); @@ -380,7 +580,7 @@ impl DestinationHandler { if self.step == TransactionStep::SendingFinishedPdu { self.reset(); } - Ok(()) + Ok(sent_packets) } /// Get the step, which denotes the exact step of a pending CFDP transaction when applicable. @@ -424,7 +624,7 @@ impl DestinationHandler { let metadata_recvd_params = MetadataReceivedParams { id, source_id, - file_size: self.tparams.tstate.metadata_params.file_size, + file_size: self.tparams.file_size(), src_file_name: src_name, dest_file_name: dest_name, msgs_to_user: &msgs_to_user[..num_msgs_to_user], @@ -432,101 +632,258 @@ impl DestinationHandler { self.tparams.tstate.transaction_id = Some(id); cfdp_user.metadata_recvd_indication(&metadata_recvd_params); - if dest_path.exists() { - let dest_metadata = metadata(dest_path)?; - if dest_metadata.is_dir() { - // Create new destination path by concatenating the last part of the source source - // name and the destination folder. For example, for a source file of /tmp/hello.txt - // and a destination name of /home/test, the resulting file name should be - // /home/test/hello.txt - let source_path = Path::new(from_utf8( - &self.tparams.file_properties.src_file_name - [..self.tparams.file_properties.src_file_name_len], - )?); - - let source_name = source_path.file_name(); - if source_name.is_none() { - return Err(DestError::PathConcatError); - } - let source_name = source_name.unwrap(); - self.tparams.file_properties.dest_path_buf.push(source_name); + // TODO: This is the only remaining function which uses std.. the easiest way would + // probably be to use a static pre-allocated dest path buffer to store any concatenated + // paths. + if dest_path.exists() && self.vfs.is_dir(dest_path.to_str().unwrap()) { + // Create new destination path by concatenating the last part of the source source + // name and the destination folder. For example, for a source file of /tmp/hello.txt + // and a destination name of /home/test, the resulting file name should be + // /home/test/hello.txt + let source_path = Path::new(from_utf8( + &self.tparams.file_properties.src_file_name + [..self.tparams.file_properties.src_file_name_len], + )?); + let source_name = source_path.file_name(); + if source_name.is_none() { + return Err(DestError::PathConcat); } + let source_name = source_name.unwrap(); + self.tparams.file_properties.dest_path_buf.push(source_name); } - // This function does exactly what we require: Create a new file if it does not exist yet - // and trucate an existing one. - File::create(&self.tparams.file_properties.dest_path_buf)?; + let dest_path_str = self.tparams.file_properties.dest_path_buf.to_str().unwrap(); + if self.vfs.exists(dest_path_str) { + self.vfs.truncate_file(dest_path_str)?; + } else { + self.vfs.create_file(dest_path_str)?; + } + self.tparams.tstate.file_status = FileStatus::Retained; self.step = TransactionStep::ReceivingFileDataPdus; Ok(()) } - fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { - let transaction_finished_params = TransactionFinishedParams { - id: self.tparams.tstate.transaction_id.unwrap(), - condition_code: self.tparams.tstate.condition_code, - delivery_code: self.tparams.tstate.delivery_code, - file_status: self.tparams.tstate.file_status, - }; - cfdp_user.transaction_finished_indication(&transaction_finished_params); - // This function should never be called with metadata parameters not set - if self.tparams.metadata_params().closure_requested { - self.prepare_finished_pdu()?; + fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { + let mut sent_packets = 0; + self.notice_of_completion(cfdp_user)?; + if self.tparams.transmission_mode() == TransmissionMode::Acknowledged + || self.tparams.metadata_params().closure_requested + { + sent_packets += self.send_finished_pdu()?; self.step = TransactionStep::SendingFinishedPdu; } else { self.reset(); - self.state = State::Idle; - self.step = TransactionStep::Idle; } + Ok(sent_packets) + } + + fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { + if self.tstate().completion_disposition == CompletionDisposition::Completed { + // TODO: Execute any filestore requests + } else if self + .tparams + .remote_cfg + .as_ref() + .unwrap() + .disposition_on_cancellation + && self.tstate().delivery_code == DeliveryCode::Incomplete + { + self.vfs + .remove_file(self.tparams.file_properties.dest_path_buf.to_str().unwrap())?; + self.tstate_mut().file_status = FileStatus::DiscardDeliberately; + } + let tstate = self.tstate(); + let transaction_finished_params = TransactionFinishedParams { + id: tstate.transaction_id.unwrap(), + condition_code: tstate.condition_code, + delivery_code: tstate.delivery_code, + file_status: tstate.file_status, + }; + cfdp_user.transaction_finished_indication(&transaction_finished_params); Ok(()) } + fn declare_fault(&mut self, condition_code: ConditionCode) -> FaultHandlerCode { + // Cache those, because they might be reset when abandoning the transaction. + let transaction_id = self.tstate().transaction_id.unwrap(); + let progress = self.tstate().progress; + let fh_code = self + .local_cfg + .default_fault_handler + .get_fault_handler(condition_code); + match fh_code { + FaultHandlerCode::NoticeOfCancellation => { + self.notice_of_cancellation(condition_code); + } + FaultHandlerCode::NoticeOfSuspension => self.notice_of_suspension(), + FaultHandlerCode::IgnoreError => (), + FaultHandlerCode::AbandonTransaction => self.abandon_transaction(), + } + self.local_cfg + .default_fault_handler + .report_fault(transaction_id, condition_code, progress) + } + + fn notice_of_cancellation(&mut self, condition_code: ConditionCode) { + self.step = TransactionStep::TransferCompletion; + self.tstate_mut().condition_code = condition_code; + self.tstate_mut().completion_disposition = CompletionDisposition::Cancelled; + } + + fn notice_of_suspension(&mut self) { + // TODO: Implement suspension handling. + } + fn abandon_transaction(&mut self) { + self.reset(); + } + fn reset(&mut self) { self.step = TransactionStep::Idle; self.state = State::Idle; - self.packets_to_send_ctx.packet_available = false; + // self.packets_to_send_ctx.packet_available = false; self.tparams.reset(); } - fn prepare_finished_pdu(&mut self) -> Result<(), DestError> { - self.packets_to_send_ctx.packet_available = true; - self.packets_to_send_ctx.directive = Some(FileDirectiveType::FinishedPdu); - self.step = TransactionStep::SendingFinishedPdu; - Ok(()) + fn send_finished_pdu(&mut self) -> Result { + let tstate = self.tstate(); + + let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0); + let finished_pdu = if tstate.condition_code == ConditionCode::NoError + || tstate.condition_code == ConditionCode::UnsupportedChecksumType + { + FinishedPduCreator::new_default(pdu_header, tstate.delivery_code, tstate.file_status) + } else { + // TODO: Are there cases where this ID is actually the source entity ID? + let entity_id = EntityIdTlv::new(self.local_cfg.id); + FinishedPduCreator::new_with_error( + pdu_header, + tstate.condition_code, + tstate.delivery_code, + tstate.file_status, + entity_id, + ) + }; + finished_pdu.write_to_bytes(&mut self.packet_buf)?; + self.packet_sender.send_pdu( + finished_pdu.pdu_type(), + finished_pdu.file_directive_type(), + &self.packet_buf[0..finished_pdu.len_written()], + )?; + Ok(1) + } + + fn tstate(&self) -> &TransferState { + &self.tparams.tstate + } + + fn tstate_mut(&mut self) -> &mut TransferState { + &mut self.tparams.tstate } } #[cfg(test)] mod tests { - use core::sync::atomic::{AtomicU8, Ordering}; + use core::{cell::Cell, sync::atomic::AtomicBool}; #[allow(unused_imports)] use std::println; - use std::{env::temp_dir, fs}; + use std::{fs, sync::Mutex}; - use alloc::{format, string::String}; + use alloc::{collections::VecDeque, string::String, sync::Arc, vec::Vec}; use rand::Rng; use spacepackets::{ - cfdp::{lv::Lv, ChecksumType}, + cfdp::{ + lv::Lv, + pdu::{finished::FinishedPduReader, metadata::MetadataPduCreator, WritablePduPacket}, + ChecksumType, TransmissionMode, + }, util::{UbfU16, UnsignedByteFieldU16}, }; + use crate::cfdp::{ + filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimer, CheckTimerCreator, + DefaultFaultHandler, IndicationConfig, RemoteEntityConfig, StdRemoteEntityConfigProvider, + UserFaultHandler, CRC_32, + }; + use super::*; const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2); - const SRC_NAME: &str = "__cfdp__source-file"; - const DEST_NAME: &str = "__cfdp__dest-file"; + pub struct FileSegmentRecvdParamsNoSegMetadata { + pub id: TransactionId, + pub offset: u64, + pub length: usize, + } - static ATOMIC_COUNTER: AtomicU8 = AtomicU8::new(0); + struct SentPdu { + pdu_type: PduType, + file_directive_type: Option, + raw_pdu: Vec, + } + type SharedPduPacketQueue = Arc>>; + #[derive(Default, Clone)] + struct TestCfdpSender { + packet_queue: SharedPduPacketQueue, + } + + impl CfdpPacketSender for TestCfdpSender { + fn send_pdu( + &mut self, + pdu_type: PduType, + file_directive_type: Option, + raw_pdu: &[u8], + ) -> Result<(), PduError> { + self.packet_queue.lock().unwrap().push_back(SentPdu { + pdu_type, + file_directive_type, + raw_pdu: raw_pdu.to_vec(), + }); + Ok(()) + } + } + + impl TestCfdpSender { + pub fn retrieve_next_pdu(&self) -> Option { + self.packet_queue.lock().unwrap().pop_front() + } + pub fn queue_empty(&self) -> bool { + self.packet_queue.lock().unwrap().is_empty() + } + } #[derive(Default)] struct TestCfdpUser { next_expected_seq_num: u64, expected_full_src_name: String, expected_full_dest_name: String, - expected_file_size: usize, + expected_file_size: u64, + transaction_indication_call_count: u32, + eof_recvd_call_count: u32, + finished_indic_queue: VecDeque, + metadata_recv_queue: VecDeque, + file_seg_recvd_queue: VecDeque, } impl TestCfdpUser { + fn new( + next_expected_seq_num: u64, + expected_full_src_name: String, + expected_full_dest_name: String, + expected_file_size: u64, + ) -> Self { + Self { + next_expected_seq_num, + expected_full_src_name, + expected_full_dest_name, + expected_file_size, + transaction_indication_call_count: 0, + eof_recvd_call_count: 0, + finished_indic_queue: VecDeque::new(), + metadata_recv_queue: VecDeque::new(), + file_seg_recvd_queue: VecDeque::new(), + } + } + fn generic_id_check(&self, id: &crate::cfdp::TransactionId) { assert_eq!(id.source_id, LOCAL_ID.into()); assert_eq!(id.seq_num().value(), self.next_expected_seq_num); @@ -536,6 +893,7 @@ mod tests { impl CfdpUser for TestCfdpUser { fn transaction_indication(&mut self, id: &crate::cfdp::TransactionId) { self.generic_id_check(id); + self.transaction_indication_call_count += 1; } fn eof_sent_indication(&mut self, id: &crate::cfdp::TransactionId) { @@ -547,6 +905,7 @@ mod tests { finished_params: &crate::cfdp::user::TransactionFinishedParams, ) { self.generic_id_check(&finished_params.id); + self.finished_indic_queue.push_back(*finished_params); } fn metadata_recvd_indication( @@ -564,13 +923,21 @@ mod tests { ); assert_eq!(md_recvd_params.msgs_to_user.len(), 0); assert_eq!(md_recvd_params.source_id, LOCAL_ID.into()); - assert_eq!(md_recvd_params.file_size as usize, self.expected_file_size); + assert_eq!(md_recvd_params.file_size, self.expected_file_size); + self.metadata_recv_queue.push_back(md_recvd_params.into()); } fn file_segment_recvd_indication( &mut self, - _segment_recvd_params: &crate::cfdp::user::FileSegmentRecvdParams, + segment_recvd_params: &crate::cfdp::user::FileSegmentRecvdParams, ) { + self.generic_id_check(&segment_recvd_params.id); + self.file_seg_recvd_queue + .push_back(FileSegmentRecvdParamsNoSegMetadata { + id: segment_recvd_params.id, + offset: segment_recvd_params.offset, + length: segment_recvd_params.length, + }) } fn report_indication(&mut self, _id: &crate::cfdp::TransactionId) {} @@ -605,31 +972,333 @@ mod tests { fn eof_recvd_indication(&mut self, id: &crate::cfdp::TransactionId) { self.generic_id_check(id); + self.eof_recvd_call_count += 1; } } - fn init_check(handler: &DestinationHandler) { - assert_eq!(handler.state(), State::Idle); - assert_eq!(handler.step(), TransactionStep::Idle); + #[derive(Default, Clone)] + struct TestFaultHandler { + notice_of_suspension_queue: Arc>>, + notice_of_cancellation_queue: Arc>>, + abandoned_queue: Arc>>, + ignored_queue: Arc>>, + } + + impl UserFaultHandler for TestFaultHandler { + fn notice_of_suspension_cb( + &mut self, + transaction_id: TransactionId, + cond: ConditionCode, + progress: u64, + ) { + self.notice_of_suspension_queue.lock().unwrap().push_back(( + transaction_id, + cond, + progress, + )) + } + + fn notice_of_cancellation_cb( + &mut self, + transaction_id: TransactionId, + cond: ConditionCode, + progress: u64, + ) { + self.notice_of_cancellation_queue + .lock() + .unwrap() + .push_back((transaction_id, cond, progress)) + } + + fn abandoned_cb( + &mut self, + transaction_id: TransactionId, + cond: ConditionCode, + progress: u64, + ) { + self.abandoned_queue + .lock() + .unwrap() + .push_back((transaction_id, cond, progress)) + } + + fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) { + self.ignored_queue + .lock() + .unwrap() + .push_back((transaction_id, cond, progress)) + } + } + + impl TestFaultHandler { + fn suspension_queue_empty(&self) -> bool { + self.notice_of_suspension_queue.lock().unwrap().is_empty() + } + fn cancellation_queue_empty(&self) -> bool { + self.notice_of_cancellation_queue.lock().unwrap().is_empty() + } + fn ignored_queue_empty(&self) -> bool { + self.ignored_queue.lock().unwrap().is_empty() + } + fn abandoned_queue_empty(&self) -> bool { + self.abandoned_queue.lock().unwrap().is_empty() + } + fn all_queues_empty(&self) -> bool { + self.suspension_queue_empty() + && self.cancellation_queue_empty() + && self.ignored_queue_empty() + && self.abandoned_queue_empty() + } + } + + #[derive(Debug)] + struct TestCheckTimer { + counter: Cell, + expired: Arc, + } + + impl CheckTimer for TestCheckTimer { + fn has_expired(&self) -> bool { + self.expired.load(core::sync::atomic::Ordering::Relaxed) + } + fn reset(&mut self) { + self.counter.set(0); + } + } + + impl TestCheckTimer { + pub fn new(expired_flag: Arc) -> Self { + Self { + counter: Cell::new(0), + expired: expired_flag, + } + } + } + + struct TestCheckTimerCreator { + check_limit_expired_flag: Arc, + } + + impl TestCheckTimerCreator { + pub fn new(expired_flag: Arc) -> Self { + Self { + check_limit_expired_flag: expired_flag, + } + } + } + + impl CheckTimerCreator for TestCheckTimerCreator { + fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box { + match timer_context { + TimerContext::CheckLimit { .. } => { + Box::new(TestCheckTimer::new(self.check_limit_expired_flag.clone())) + } + _ => { + panic!("invalid check timer creator, can only be used for check limit handling") + } + } + } + } + + struct DestHandlerTester { + check_timer_expired: Arc, + pdu_sender: TestCfdpSender, + handler: DestinationHandler, + src_path: PathBuf, + dest_path: PathBuf, + check_dest_file: bool, + check_handler_idle_at_drop: bool, + expected_file_size: u64, + closure_requested: bool, + pdu_header: PduHeader, + expected_full_data: Vec, + buf: [u8; 512], + } + + impl DestHandlerTester { + fn new(fault_handler: TestFaultHandler, closure_requested: bool) -> Self { + let check_timer_expired = Arc::new(AtomicBool::new(false)); + let test_sender = TestCfdpSender::default(); + let dest_handler = default_dest_handler( + fault_handler, + test_sender.clone(), + check_timer_expired.clone(), + ); + let (src_path, dest_path) = init_full_filenames(); + assert!(!Path::exists(&dest_path)); + let handler = Self { + check_timer_expired, + pdu_sender: test_sender, + handler: dest_handler, + src_path, + closure_requested, + dest_path, + check_dest_file: false, + check_handler_idle_at_drop: false, + expected_file_size: 0, + pdu_header: create_pdu_header(UbfU16::new(0)), + expected_full_data: Vec::new(), + buf: [0; 512], + }; + handler.state_check(State::Idle, TransactionStep::Idle); + handler + } + + fn dest_path(&self) -> &PathBuf { + &self.dest_path + } + + #[allow(dead_code)] + fn indication_cfg_mut(&mut self) -> &mut IndicationConfig { + &mut self.handler.local_cfg.indication_cfg + } + + fn indication_cfg(&mut self) -> &IndicationConfig { + &self.handler.local_cfg.indication_cfg + } + + fn set_check_timer_expired(&mut self) { + self.check_timer_expired + .store(true, core::sync::atomic::Ordering::Relaxed); + } + + fn test_user_from_cached_paths(&self, expected_file_size: u64) -> TestCfdpUser { + TestCfdpUser::new( + 0, + self.src_path.to_string_lossy().into(), + self.dest_path.to_string_lossy().into(), + expected_file_size, + ) + } + + fn generic_transfer_init( + &mut self, + user: &mut TestCfdpUser, + file_size: u64, + ) -> Result { + self.expected_file_size = file_size; + let metadata_pdu = create_metadata_pdu( + &self.pdu_header, + self.src_path.as_path(), + self.dest_path.as_path(), + file_size, + self.closure_requested, + ); + let packet_info = create_packet_info(&metadata_pdu, &mut self.buf); + self.handler.state_machine(user, Some(&packet_info))?; + assert_eq!(user.metadata_recv_queue.len(), 1); + assert_eq!( + self.handler.transmission_mode().unwrap(), + TransmissionMode::Unacknowledged + ); + Ok(self.handler.transaction_id().unwrap()) + } + + fn generic_file_data_insert( + &mut self, + user: &mut TestCfdpUser, + offset: u64, + file_data_chunk: &[u8], + ) -> Result { + let filedata_pdu = + FileDataPdu::new_no_seg_metadata(self.pdu_header, offset, file_data_chunk); + filedata_pdu + .write_to_bytes(&mut self.buf) + .expect("writing file data PDU failed"); + let packet_info = PacketInfo::new(&self.buf).expect("creating packet info failed"); + let result = self.handler.state_machine(user, Some(&packet_info)); + if self.indication_cfg().file_segment_recv { + assert!(!user.file_seg_recvd_queue.is_empty()); + assert_eq!(user.file_seg_recvd_queue.back().unwrap().offset, offset); + assert_eq!( + user.file_seg_recvd_queue.back().unwrap().length, + file_data_chunk.len() + ); + } + result + } + + fn generic_eof_no_error( + &mut self, + user: &mut TestCfdpUser, + expected_full_data: Vec, + ) -> Result { + self.expected_full_data = expected_full_data; + let eof_pdu = create_no_error_eof(&self.expected_full_data, &self.pdu_header); + let packet_info = create_packet_info(&eof_pdu, &mut self.buf); + self.check_handler_idle_at_drop = true; + self.check_dest_file = true; + let result = self.handler.state_machine(user, Some(&packet_info)); + if self.indication_cfg().eof_recv { + assert_eq!(user.eof_recvd_call_count, 1); + } + result + } + + fn state_check(&self, state: State, step: TransactionStep) { + assert_eq!(self.handler.state(), state); + assert_eq!(self.handler.step(), step); + } + } + + impl Drop for DestHandlerTester { + fn drop(&mut self) { + if self.check_handler_idle_at_drop { + self.state_check(State::Idle, TransactionStep::Idle); + } + if self.check_dest_file { + assert!(Path::exists(&self.dest_path)); + let read_content = fs::read(&self.dest_path).expect("reading back string failed"); + assert_eq!(read_content.len() as u64, self.expected_file_size); + assert_eq!(read_content, self.expected_full_data); + assert!(fs::remove_file(self.dest_path.as_path()).is_ok()); + } + } } fn init_full_filenames() -> (PathBuf, PathBuf) { - let mut file_path = temp_dir(); - let mut src_path = file_path.clone(); - // Atomic counter used to allow concurrent tests. - let unique_counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed); - // Create unique test filenames. - let src_name_unique = format!("{SRC_NAME}{}.txt", unique_counter); - let dest_name_unique = format!("{DEST_NAME}{}.txt", unique_counter); - src_path.push(src_name_unique); - file_path.push(dest_name_unique); - (src_path, file_path) + ( + tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf(), + tempfile::NamedTempFile::new() + .unwrap() + .into_temp_path() + .to_path_buf(), + ) } - #[test] - fn test_basic() { - let dest_handler = DestinationHandler::new(REMOTE_ID); - init_check(&dest_handler); + fn basic_remote_cfg_table() -> StdRemoteEntityConfigProvider { + let mut table = StdRemoteEntityConfigProvider::default(); + let remote_entity_cfg = RemoteEntityConfig::new_with_default_values( + UnsignedByteFieldU16::new(1).into(), + 1024, + 1024, + true, + true, + TransmissionMode::Unacknowledged, + ChecksumType::Crc32, + ); + table.add_config(&remote_entity_cfg); + table + } + + fn default_dest_handler( + test_fault_handler: TestFaultHandler, + test_packet_sender: TestCfdpSender, + check_timer_expired: Arc, + ) -> DestinationHandler { + let local_entity_cfg = LocalEntityConfig { + id: REMOTE_ID.into(), + indication_cfg: IndicationConfig::default(), + default_fault_handler: DefaultFaultHandler::new(Box::new(test_fault_handler)), + }; + DestinationHandler::new( + local_entity_cfg, + 2048, + Box::new(test_packet_sender), + Box::::default(), + Box::new(basic_remote_cfg_table()), + Box::new(TestCheckTimerCreator::new(check_timer_expired)), + ) } fn create_pdu_header(seq_num: impl Into) -> PduHeader { @@ -644,228 +1313,288 @@ mod tests { src_name: &'filename Path, dest_name: &'filename Path, file_size: u64, - ) -> MetadataPdu<'filename, 'filename, 'static> { - let metadata_params = MetadataGenericParams::new(false, ChecksumType::Crc32, file_size); - MetadataPdu::new( + closure_requested: bool, + ) -> MetadataPduCreator<'filename, 'filename, 'static> { + let checksum_type = if file_size == 0 { + ChecksumType::NullChecksum + } else { + ChecksumType::Crc32 + }; + let metadata_params = + MetadataGenericParams::new(closure_requested, checksum_type, file_size); + MetadataPduCreator::new_no_opts( *pdu_header, metadata_params, Lv::new_from_str(src_name.as_os_str().to_str().unwrap()).unwrap(), Lv::new_from_str(dest_name.as_os_str().to_str().unwrap()).unwrap(), - None, ) } - fn insert_metadata_pdu( - metadata_pdu: &MetadataPdu, - buf: &mut [u8], - dest_handler: &mut DestinationHandler, - ) { - let written_len = metadata_pdu + fn create_packet_info<'a>( + pdu: &'a impl WritablePduPacket, + buf: &'a mut [u8], + ) -> PacketInfo<'a> { + let written_len = pdu .write_to_bytes(buf) .expect("writing metadata PDU failed"); - let packet_info = - PacketInfo::new(&buf[..written_len]).expect("generating packet info failed"); - let insert_result = dest_handler.insert_packet(&packet_info); - if let Err(e) = insert_result { - panic!("insert result error: {e}"); - } + PacketInfo::new(&buf[..written_len]).expect("generating packet info failed") } - fn insert_eof_pdu( - file_data: &[u8], - pdu_header: &PduHeader, - buf: &mut [u8], - dest_handler: &mut DestinationHandler, - ) { - let mut digest = CRC_32.digest(); - digest.update(file_data); - let crc32 = digest.finalize(); - let eof_pdu = EofPdu::new_no_error(*pdu_header, crc32, file_data.len() as u64); - let result = eof_pdu.write_to_bytes(buf); - assert!(result.is_ok()); - let packet_info = PacketInfo::new(&buf).expect("generating packet info failed"); - let result = dest_handler.insert_packet(&packet_info); - assert!(result.is_ok()); - } - - #[test] - fn test_empty_file_transfer() { - let (src_name, dest_name) = init_full_filenames(); - assert!(!Path::exists(&dest_name)); - let mut buf: [u8; 512] = [0; 512]; - let mut test_user = TestCfdpUser { - next_expected_seq_num: 0, - expected_full_src_name: src_name.to_string_lossy().into(), - expected_full_dest_name: dest_name.to_string_lossy().into(), - expected_file_size: 0, + fn create_no_error_eof(file_data: &[u8], pdu_header: &PduHeader) -> EofPdu { + let crc32 = if !file_data.is_empty() { + let mut digest = CRC_32.digest(); + digest.update(file_data); + digest.finalize() + } else { + 0 }; - // We treat the destination handler like it is a remote entity. - let mut dest_handler = DestinationHandler::new(REMOTE_ID); - init_check(&dest_handler); - - let seq_num = UbfU16::new(0); - let pdu_header = create_pdu_header(seq_num); - let metadata_pdu = - create_metadata_pdu(&pdu_header, src_name.as_path(), dest_name.as_path(), 0); - insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); - if let Err(e) = result { - panic!("dest handler fsm error: {e}"); - } - assert_ne!(dest_handler.state(), State::Idle); - assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus); - - insert_eof_pdu(&[], &pdu_header, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); - assert!(result.is_ok()); - assert_eq!(dest_handler.state(), State::Idle); - assert_eq!(dest_handler.step(), TransactionStep::Idle); - assert!(Path::exists(&dest_name)); - let read_content = fs::read(&dest_name).expect("reading back string failed"); - assert_eq!(read_content.len(), 0); - assert!(fs::remove_file(dest_name).is_ok()); + EofPdu::new_no_error(*pdu_header, crc32, file_data.len() as u64) } #[test] - fn test_small_file_transfer() { - let (src_name, dest_name) = init_full_filenames(); - assert!(!Path::exists(&dest_name)); + fn test_basic() { + let fault_handler = TestFaultHandler::default(); + let test_sender = TestCfdpSender::default(); + let dest_handler = default_dest_handler(fault_handler.clone(), test_sender, Arc::default()); + assert!(dest_handler.transmission_mode().is_none()); + assert!(fault_handler.all_queues_empty()); + } + + #[test] + fn test_empty_file_transfer_not_acked_no_closure() { + let fault_handler = TestFaultHandler::default(); + let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = test_obj.test_user_from_cached_paths(0); + test_obj + .generic_transfer_init(&mut test_user, 0) + .expect("transfer init failed"); + test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + test_obj + .generic_eof_no_error(&mut test_user, Vec::new()) + .expect("EOF no error insertion failed"); + assert!(fault_handler.all_queues_empty()); + assert!(test_obj.pdu_sender.queue_empty()); + test_obj.state_check(State::Idle, TransactionStep::Idle); + } + + #[test] + fn test_small_file_transfer_not_acked() { let file_data_str = "Hello World!"; let file_data = file_data_str.as_bytes(); - let mut buf: [u8; 512] = [0; 512]; - let mut test_user = TestCfdpUser { - next_expected_seq_num: 0, - expected_full_src_name: src_name.to_string_lossy().into(), - expected_full_dest_name: dest_name.to_string_lossy().into(), - expected_file_size: file_data.len(), - }; - // We treat the destination handler like it is a remote entity. - let mut dest_handler = DestinationHandler::new(REMOTE_ID); - init_check(&dest_handler); + let file_size = file_data.len() as u64; + let fault_handler = TestFaultHandler::default(); - let seq_num = UbfU16::new(0); - let pdu_header = create_pdu_header(seq_num); - let metadata_pdu = create_metadata_pdu( - &pdu_header, - src_name.as_path(), - dest_name.as_path(), - file_data.len() as u64, - ); - insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); - if let Err(e) = result { - panic!("dest handler fsm error: {e}"); - } - assert_ne!(dest_handler.state(), State::Idle); - assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus); - - let offset = 0; - let filedata_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, offset, file_data); - filedata_pdu - .write_to_bytes(&mut buf) - .expect("writing file data PDU failed"); - let packet_info = PacketInfo::new(&buf).expect("creating packet info failed"); - let result = dest_handler.insert_packet(&packet_info); - if let Err(e) = result { - panic!("destination handler packet insertion error: {e}"); - } - let result = dest_handler.state_machine(&mut test_user); - assert!(result.is_ok()); - - insert_eof_pdu(file_data, &pdu_header, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); - assert!(result.is_ok()); - assert_eq!(dest_handler.state(), State::Idle); - assert_eq!(dest_handler.step(), TransactionStep::Idle); - - assert!(Path::exists(&dest_name)); - let read_content = fs::read_to_string(&dest_name).expect("reading back string failed"); - assert_eq!(read_content, file_data_str); - assert!(fs::remove_file(dest_name).is_ok()); + let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = test_obj.test_user_from_cached_paths(file_size); + test_obj + .generic_transfer_init(&mut test_user, file_size) + .expect("transfer init failed"); + test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + test_obj + .generic_file_data_insert(&mut test_user, 0, file_data) + .expect("file data insertion failed"); + test_obj + .generic_eof_no_error(&mut test_user, file_data.to_vec()) + .expect("EOF no error insertion failed"); + assert!(fault_handler.all_queues_empty()); + assert!(test_obj.pdu_sender.queue_empty()); + test_obj.state_check(State::Idle, TransactionStep::Idle); } #[test] - fn test_segmented_file_transfer() { - let (src_name, dest_name) = init_full_filenames(); - assert!(!Path::exists(&dest_name)); + fn test_segmented_file_transfer_not_acked() { let mut rng = rand::thread_rng(); let mut random_data = [0u8; 512]; rng.fill(&mut random_data); - let mut buf: [u8; 512] = [0; 512]; - let mut test_user = TestCfdpUser { - next_expected_seq_num: 0, - expected_full_src_name: src_name.to_string_lossy().into(), - expected_full_dest_name: dest_name.to_string_lossy().into(), - expected_file_size: random_data.len(), - }; - - // We treat the destination handler like it is a remote entity. - let mut dest_handler = DestinationHandler::new(REMOTE_ID); - init_check(&dest_handler); - - let seq_num = UbfU16::new(0); - let pdu_header = create_pdu_header(seq_num); - let metadata_pdu = create_metadata_pdu( - &pdu_header, - src_name.as_path(), - dest_name.as_path(), - random_data.len() as u64, - ); - insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); - if let Err(e) = result { - panic!("dest handler fsm error: {e}"); - } - assert_ne!(dest_handler.state(), State::Idle); - assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus); - - // First file data PDU - let mut offset: usize = 0; + let file_size = random_data.len() as u64; let segment_len = 256; - let filedata_pdu = FileDataPdu::new_no_seg_metadata( - pdu_header, - offset as u64, - &random_data[0..segment_len], + let fault_handler = TestFaultHandler::default(); + + let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = test_obj.test_user_from_cached_paths(file_size); + test_obj + .generic_transfer_init(&mut test_user, file_size) + .expect("transfer init failed"); + test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + test_obj + .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) + .expect("file data insertion failed"); + test_obj + .generic_file_data_insert( + &mut test_user, + segment_len as u64, + &random_data[segment_len..], + ) + .expect("file data insertion failed"); + test_obj + .generic_eof_no_error(&mut test_user, random_data.to_vec()) + .expect("EOF no error insertion failed"); + assert!(fault_handler.all_queues_empty()); + assert!(test_obj.pdu_sender.queue_empty()); + test_obj.state_check(State::Idle, TransactionStep::Idle); + } + + #[test] + fn test_check_limit_handling_transfer_success() { + let mut rng = rand::thread_rng(); + let mut random_data = [0u8; 512]; + rng.fill(&mut random_data); + let file_size = random_data.len() as u64; + let segment_len = 256; + let fault_handler = TestFaultHandler::default(); + + let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = test_obj.test_user_from_cached_paths(file_size); + let transaction_id = test_obj + .generic_transfer_init(&mut test_user, file_size) + .expect("transfer init failed"); + + test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + test_obj + .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) + .expect("file data insertion 0 failed"); + test_obj + .generic_eof_no_error(&mut test_user, random_data.to_vec()) + .expect("EOF no error insertion failed"); + test_obj.state_check( + State::Busy, + TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, ); - filedata_pdu - .write_to_bytes(&mut buf) - .expect("writing file data PDU failed"); - let packet_info = PacketInfo::new(&buf).expect("creating packet info failed"); - let result = dest_handler.insert_packet(&packet_info); - if let Err(e) = result { - panic!("destination handler packet insertion error: {e}"); - } - let result = dest_handler.state_machine(&mut test_user); - assert!(result.is_ok()); + test_obj + .generic_file_data_insert( + &mut test_user, + segment_len as u64, + &random_data[segment_len..], + ) + .expect("file data insertion 1 failed"); + test_obj.set_check_timer_expired(); + test_obj + .handler + .state_machine(&mut test_user, None) + .expect("fsm failure"); - // Second file data PDU - offset += segment_len; - let filedata_pdu = FileDataPdu::new_no_seg_metadata( - pdu_header, - offset as u64, - &random_data[segment_len..], + let ignored_queue = fault_handler.ignored_queue.lock().unwrap(); + assert_eq!(ignored_queue.len(), 1); + let cancelled = *ignored_queue.front().unwrap(); + assert_eq!(cancelled.0, transaction_id); + assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure); + assert_eq!(cancelled.2, segment_len as u64); + assert!(test_obj.pdu_sender.queue_empty()); + test_obj.state_check(State::Idle, TransactionStep::Idle); + } + + #[test] + fn test_check_limit_handling_limit_reached() { + let mut rng = rand::thread_rng(); + let mut random_data = [0u8; 512]; + rng.fill(&mut random_data); + let file_size = random_data.len() as u64; + let segment_len = 256; + + let fault_handler = TestFaultHandler::default(); + let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = test_obj.test_user_from_cached_paths(file_size); + let transaction_id = test_obj + .generic_transfer_init(&mut test_user, file_size) + .expect("transfer init failed"); + + test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + test_obj + .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) + .expect("file data insertion 0 failed"); + test_obj + .generic_eof_no_error(&mut test_user, random_data.to_vec()) + .expect("EOF no error insertion failed"); + test_obj.state_check( + State::Busy, + TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, ); - filedata_pdu - .write_to_bytes(&mut buf) - .expect("writing file data PDU failed"); - let packet_info = PacketInfo::new(&buf).expect("creating packet info failed"); - let result = dest_handler.insert_packet(&packet_info); - if let Err(e) = result { - panic!("destination handler packet insertion error: {e}"); - } - let result = dest_handler.state_machine(&mut test_user); - assert!(result.is_ok()); + test_obj.set_check_timer_expired(); + test_obj + .handler + .state_machine(&mut test_user, None) + .expect("fsm error"); + test_obj.state_check( + State::Busy, + TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, + ); + test_obj.set_check_timer_expired(); + test_obj + .handler + .state_machine(&mut test_user, None) + .expect("fsm error"); + test_obj.state_check(State::Idle, TransactionStep::Idle); - insert_eof_pdu(&random_data, &pdu_header, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); - assert!(result.is_ok()); - assert_eq!(dest_handler.state(), State::Idle); - assert_eq!(dest_handler.step(), TransactionStep::Idle); + assert!(fault_handler + .notice_of_suspension_queue + .lock() + .unwrap() + .is_empty()); - // Clean up - assert!(Path::exists(&dest_name)); - let read_content = fs::read(&dest_name).expect("reading back string failed"); - assert_eq!(read_content, random_data); - assert!(fs::remove_file(dest_name).is_ok()); + let ignored_queue = fault_handler.ignored_queue.lock().unwrap(); + assert_eq!(ignored_queue.len(), 1); + let cancelled = *ignored_queue.front().unwrap(); + assert_eq!(cancelled.0, transaction_id); + assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure); + assert_eq!(cancelled.2, segment_len as u64); + + let cancelled_queue = fault_handler.notice_of_cancellation_queue.lock().unwrap(); + assert_eq!(cancelled_queue.len(), 1); + let cancelled = *cancelled_queue.front().unwrap(); + assert_eq!(cancelled.0, transaction_id); + assert_eq!(cancelled.1, ConditionCode::CheckLimitReached); + assert_eq!(cancelled.2, segment_len as u64); + + drop(cancelled_queue); + + assert!(test_obj.pdu_sender.queue_empty()); + + // Check that the broken file exists. + test_obj.check_dest_file = false; + assert!(Path::exists(test_obj.dest_path())); + let read_content = fs::read(test_obj.dest_path()).expect("reading back string failed"); + assert_eq!(read_content.len(), segment_len); + assert_eq!(read_content, &random_data[0..segment_len]); + assert!(fs::remove_file(test_obj.dest_path().as_path()).is_ok()); + } + + fn check_finished_pdu_success(sent_pdu: &SentPdu) { + assert_eq!(sent_pdu.pdu_type, PduType::FileDirective); + assert_eq!( + sent_pdu.file_directive_type, + Some(FileDirectiveType::FinishedPdu) + ); + let finished_pdu = FinishedPduReader::from_bytes(&sent_pdu.raw_pdu).unwrap(); + assert_eq!(finished_pdu.file_status(), FileStatus::Retained); + assert_eq!(finished_pdu.condition_code(), ConditionCode::NoError); + assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete); + assert!(finished_pdu.fault_location().is_none()); + assert_eq!(finished_pdu.fs_responses_raw(), &[]); + } + + #[test] + fn test_file_transfer_with_closure() { + let fault_handler = TestFaultHandler::default(); + let mut test_obj = DestHandlerTester::new(fault_handler.clone(), true); + let mut test_user = test_obj.test_user_from_cached_paths(0); + test_obj + .generic_transfer_init(&mut test_user, 0) + .expect("transfer init failed"); + test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + let sent_packets = test_obj + .generic_eof_no_error(&mut test_user, Vec::new()) + .expect("EOF no error insertion failed"); + assert_eq!(sent_packets, 1); + assert!(fault_handler.all_queues_empty()); + // The Finished PDU was sent, so the state machine is done. + test_obj.state_check(State::Idle, TransactionStep::Idle); + assert!(!test_obj.pdu_sender.queue_empty()); + let sent_pdu = test_obj.pdu_sender.retrieve_next_pdu().unwrap(); + check_finished_pdu_success(&sent_pdu); + } + + #[test] + fn test_file_transfer_with_closure_check_limit_reached() { + // TODO: Implement test. } } diff --git a/satrs-core/src/cfdp/filestore.rs b/satrs-core/src/cfdp/filestore.rs new file mode 100644 index 0000000..72fdb4e --- /dev/null +++ b/satrs-core/src/cfdp/filestore.rs @@ -0,0 +1,370 @@ +use alloc::string::{String, ToString}; +use core::fmt::Display; +use crc::{Crc, CRC_32_CKSUM}; +use spacepackets::cfdp::ChecksumType; +use spacepackets::ByteConversionError; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(feature = "std")] +pub use stdmod::*; + +pub const CRC_32: Crc = Crc::::new(&CRC_32_CKSUM); + +#[derive(Debug, Clone)] +pub enum FilestoreError { + FileDoesNotExist, + FileAlreadyExists, + DirDoesNotExist, + Permission, + IsNotFile, + IsNotDirectory, + ByteConversion(ByteConversionError), + Io { + raw_errno: Option, + string: String, + }, + ChecksumTypeNotImplemented(ChecksumType), +} + +impl From for FilestoreError { + fn from(value: ByteConversionError) -> Self { + Self::ByteConversion(value) + } +} + +impl Display for FilestoreError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + FilestoreError::FileDoesNotExist => { + write!(f, "file does not exist") + } + FilestoreError::FileAlreadyExists => { + write!(f, "file already exists") + } + FilestoreError::DirDoesNotExist => { + write!(f, "directory does not exist") + } + FilestoreError::Permission => { + write!(f, "permission error") + } + FilestoreError::IsNotFile => { + write!(f, "is not a file") + } + FilestoreError::IsNotDirectory => { + write!(f, "is not a directory") + } + FilestoreError::ByteConversion(e) => { + write!(f, "filestore error: {e}") + } + FilestoreError::Io { raw_errno, string } => { + write!( + f, + "filestore generic IO error with raw errno {:?}: {}", + raw_errno, string + ) + } + FilestoreError::ChecksumTypeNotImplemented(checksum_type) => { + write!(f, "checksum {:?} not implemented", checksum_type) + } + } + } +} + +impl Error for FilestoreError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + FilestoreError::ByteConversion(e) => Some(e), + _ => None, + } + } +} + +#[cfg(feature = "std")] +impl From for FilestoreError { + fn from(value: std::io::Error) -> Self { + Self::Io { + raw_errno: value.raw_os_error(), + string: value.to_string(), + } + } +} + +pub trait VirtualFilestore { + fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>; + + fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>; + + /// Truncating a file means deleting all its data so the resulting file is empty. + /// This can be more efficient than removing and re-creating a file. + fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>; + + fn remove_dir(&self, file_path: &str, all: bool) -> Result<(), FilestoreError>; + + fn read_data( + &self, + file_path: &str, + offset: u64, + read_len: u64, + buf: &mut [u8], + ) -> Result<(), FilestoreError>; + + fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>; + + fn filename_from_full_path<'a>(&self, path: &'a str) -> Option<&'a str>; + + fn is_file(&self, path: &str) -> bool; + + fn is_dir(&self, path: &str) -> bool { + !self.is_file(path) + } + + fn exists(&self, path: &str) -> bool; + + /// This special function is the CFDP specific abstraction to verify the checksum of a file. + /// This allows to keep OS specific details like reading the whole file in the most efficient + /// manner inside the file system abstraction. + fn checksum_verify( + &self, + file_path: &str, + checksum_type: ChecksumType, + expected_checksum: u32, + verification_buf: &mut [u8], + ) -> Result; +} + +#[cfg(feature = "std")] +pub mod stdmod { + use super::*; + use std::{ + fs::{self, File, OpenOptions}, + io::{BufReader, Read, Seek, SeekFrom, Write}, + path::Path, + }; + + #[derive(Default)] + pub struct NativeFilestore {} + + impl VirtualFilestore for NativeFilestore { + fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> { + if self.exists(file_path) { + return Err(FilestoreError::FileAlreadyExists); + } + File::create(file_path)?; + Ok(()) + } + + fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> { + if !self.exists(file_path) { + return Ok(()); + } + if !self.is_file(file_path) { + return Err(FilestoreError::IsNotFile); + } + fs::remove_file(file_path)?; + Ok(()) + } + + fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> { + if !self.exists(file_path) { + return Ok(()); + } + if !self.is_file(file_path) { + return Err(FilestoreError::IsNotFile); + } + OpenOptions::new() + .write(true) + .truncate(true) + .open(file_path)?; + Ok(()) + } + + fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> { + if !self.exists(dir_path) { + return Err(FilestoreError::DirDoesNotExist); + } + if !self.is_dir(dir_path) { + return Err(FilestoreError::IsNotDirectory); + } + if !all { + fs::remove_dir(dir_path)?; + return Ok(()); + } + fs::remove_dir_all(dir_path)?; + Ok(()) + } + + fn read_data( + &self, + file_name: &str, + offset: u64, + read_len: u64, + buf: &mut [u8], + ) -> Result<(), FilestoreError> { + if buf.len() < read_len as usize { + return Err(ByteConversionError::ToSliceTooSmall { + found: buf.len(), + expected: read_len as usize, + } + .into()); + } + if !self.exists(file_name) { + return Err(FilestoreError::FileDoesNotExist); + } + if !self.is_file(file_name) { + return Err(FilestoreError::IsNotFile); + } + let mut file = File::open(file_name)?; + file.seek(SeekFrom::Start(offset))?; + file.read_exact(&mut buf[0..read_len as usize])?; + Ok(()) + } + + fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> { + if !self.exists(file) { + return Err(FilestoreError::FileDoesNotExist); + } + if !self.is_file(file) { + return Err(FilestoreError::IsNotFile); + } + let mut file = OpenOptions::new().write(true).open(file)?; + file.seek(SeekFrom::Start(offset))?; + file.write_all(buf)?; + Ok(()) + } + + fn filename_from_full_path<'a>(&self, path: &'a str) -> Option<&'a str> { + // Convert the path string to a Path + let path = Path::new(path); + + // Extract the file name using the file_name() method + path.file_name().and_then(|name| name.to_str()) + } + + fn is_file(&self, path: &str) -> bool { + let path = Path::new(path); + path.is_file() + } + + fn is_dir(&self, path: &str) -> bool { + let path = Path::new(path); + path.is_dir() + } + + fn exists(&self, path: &str) -> bool { + let path = Path::new(path); + if !path.exists() { + return false; + } + true + } + + fn checksum_verify( + &self, + file_path: &str, + checksum_type: ChecksumType, + expected_checksum: u32, + verification_buf: &mut [u8], + ) -> Result { + match checksum_type { + ChecksumType::Modular => { + todo!(); + } + ChecksumType::Crc32 => { + let mut digest = CRC_32.digest(); + let file_to_check = File::open(file_path)?; + let mut buf_reader = BufReader::new(file_to_check); + loop { + let bytes_read = buf_reader.read(verification_buf)?; + if bytes_read == 0 { + break; + } + digest.update(&verification_buf[0..bytes_read]); + } + if digest.finalize() == expected_checksum { + return Ok(true); + } + Ok(false) + } + ChecksumType::NullChecksum => Ok(true), + _ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)), + } + } + } +} + +#[cfg(test)] +mod tests { + use std::{fs, path::Path, println}; + + use super::*; + use tempfile::tempdir; + + const NATIVE_FS: NativeFilestore = NativeFilestore {}; + + #[test] + fn test_basic_native_filestore_create() { + let tmpdir = tempdir().expect("creating tmpdir failed"); + let file_path = tmpdir.path().join("test.txt"); + let result = + NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed")); + assert!(result.is_ok()); + let path = Path::new(&file_path); + assert!(path.exists()); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); + fs::remove_dir_all(tmpdir).expect("clearing tmpdir failed"); + } + + #[test] + fn test_basic_native_fs_exists() { + let tmpdir = tempdir().expect("creating tmpdir failed"); + let file_path = tmpdir.path().join("test.txt"); + assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); + NATIVE_FS + .create_file(file_path.to_str().expect("getting str for file failed")) + .unwrap(); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); + fs::remove_dir_all(tmpdir).expect("clearing tmpdir failed"); + } + + #[test] + fn test_basic_native_fs_write() { + let tmpdir = tempdir().expect("creating tmpdir failed"); + let file_path = tmpdir.path().join("test.txt"); + assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); + NATIVE_FS + .create_file(file_path.to_str().expect("getting str for file failed")) + .unwrap(); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); + println!("{}", file_path.to_str().unwrap()); + let write_data = "hello world\n"; + NATIVE_FS + .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes()) + .expect("writing to file failed"); + let read_back = fs::read_to_string(file_path).expect("reading back data failed"); + assert_eq!(read_back, write_data); + fs::remove_dir_all(tmpdir).expect("clearing tmpdir failed"); + } + + #[test] + fn test_basic_native_fs_read() { + let tmpdir = tempdir().expect("creating tmpdir failed"); + let file_path = tmpdir.path().join("test.txt"); + assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); + NATIVE_FS + .create_file(file_path.to_str().expect("getting str for file failed")) + .unwrap(); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); + println!("{}", file_path.to_str().unwrap()); + let write_data = "hello world\n"; + NATIVE_FS + .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes()) + .expect("writing to file failed"); + let read_back = fs::read_to_string(file_path).expect("reading back data failed"); + assert_eq!(read_back, write_data); + fs::remove_dir_all(tmpdir).expect("clearing tmpdir failed"); + } +} diff --git a/satrs-core/src/cfdp/mod.rs b/satrs-core/src/cfdp/mod.rs index dc6e87a..c3bda16 100644 --- a/satrs-core/src/cfdp/mod.rs +++ b/satrs-core/src/cfdp/mod.rs @@ -1,8 +1,13 @@ +//! This module contains the implementation of the CFDP high level classes as specified in the +//! CCSDS 727.0-B-5. +use core::{cell::RefCell, fmt::Debug, hash::Hash}; + use crc::{Crc, CRC_32_CKSUM}; +use hashbrown::HashMap; use spacepackets::{ cfdp::{ pdu::{FileDirectiveType, PduError, PduHeader}, - ChecksumType, PduType, TransmissionMode, + ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode, }, util::UnsignedByteField, }; @@ -14,6 +19,8 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] pub mod dest; +#[cfg(feature = "alloc")] +pub mod filestore; #[cfg(feature = "std")] pub mod source; pub mod user; @@ -24,7 +31,27 @@ pub enum EntityType { Receiving, } -/// Generic abstraction for a check timer which has different functionality depending on whether +pub enum TimerContext { + CheckLimit { + local_id: UnsignedByteField, + remote_id: UnsignedByteField, + entity_type: EntityType, + }, + NakActivity { + expiry_time_seconds: f32, + }, + PositiveAck { + expiry_time_seconds: f32, + }, +} + +/// Generic abstraction for a check timer which is used by 3 mechanisms of the CFDP protocol. +/// +/// ## 1. Check limit handling +/// +/// The first mechanism is the check limit handling for unacknowledged transfers as specified +/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard. +/// For this mechanism, the timer has different functionality depending on whether /// the using entity is the sending entity or the receiving entity for the unacknowledged /// transmission mode. /// @@ -35,30 +62,40 @@ pub enum EntityType { /// For the receiving entity, this timer determines the expiry period for incrementing a check /// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order /// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. -pub trait CheckTimerProvider { +/// +/// ## 2. NAK activity limit +/// +/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP +/// standard. The expiration period will be provided by the NAK timer expiration limit of the +/// remote entity configuration. +/// +/// ## 3. Positive ACK procedures +/// +/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in +/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer +/// interval of the remote entity configuration. +pub trait CheckTimer: Debug { fn has_expired(&self) -> bool; + fn reset(&mut self); } /// A generic trait which allows CFDP entities to create check timers which are required to /// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2 -/// and 4.6.3.3. The [CheckTimerProvider] provides more information about the purpose of the -/// check timer. +/// and 4.6.3.3. The [CheckTimer] documentation provides more information about the purpose of the +/// check timer in the context of CFDP. /// -/// This trait also allows the creation of different check timers depending on -/// the ID of the local entity, the ID of the remote entity for a given transaction, and the -/// type of entity. +/// This trait also allows the creation of different check timers depending on context and purpose +/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or +/// other factors. #[cfg(feature = "alloc")] pub trait CheckTimerCreator { - fn get_check_timer_provider( - local_id: &UnsignedByteField, - remote_id: &UnsignedByteField, - entity_type: EntityType, - ) -> Box; + fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box; } -/// Simple implementation of the [CheckTimerProvider] trait assuming a standard runtime. +/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime. /// It also assumes that a second accuracy of the check timer period is sufficient. #[cfg(feature = "std")] +#[derive(Debug)] pub struct StdCheckTimer { expiry_time_seconds: u64, start_time: std::time::Instant, @@ -75,7 +112,7 @@ impl StdCheckTimer { } #[cfg(feature = "std")] -impl CheckTimerProvider for StdCheckTimer { +impl CheckTimer for StdCheckTimer { fn has_expired(&self) -> bool { let elapsed_time = self.start_time.elapsed(); if elapsed_time.as_secs() > self.expiry_time_seconds { @@ -83,24 +120,322 @@ impl CheckTimerProvider for StdCheckTimer { } false } + + fn reset(&mut self) { + self.start_time = std::time::Instant::now(); + } } -#[derive(Debug)] +/// This structure models the remote entity configuration information as specified in chapter 8.3 +/// of the CFDP standard. + +/// Some of the fields which were not considered necessary for the Rust implementation +/// were omitted. Some other fields which are not contained inside the standard but are considered +/// necessary for the Rust implementation are included. +/// +/// ## Notes on Positive Acknowledgment Procedures +/// +/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will +/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending +/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU). +/// Once the expected ACK response has not been received for that interval, as counter will be +/// incremented and the timer will be reset. Once the counter exceeds the +/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared. +/// +/// ## Notes on Deferred Lost Segment Procedures +/// +/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After +/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is +/// reset when missing segments or missing metadata is received. The timer will be deactivated if +/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a +/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared. +/// +/// ## Fields +/// +/// * `entity_id` - The ID of the remote entity. +/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition +/// to the `max_file_segment_len` attribute which also determines the size of file data PDUs. +/// * `max_file_segment_len` The maximum file segment length which determines the maximum size +/// of file data PDUs in addition to the `max_packet_len` attribute. If this field is set +/// to None, the maximum file segment length will be derived from the maximum packet length. +/// If this has some value which is smaller than the segment value derived from +/// `max_packet_len`, this value will be picked. +/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of +/// the Put Request, it will be determined from this field in the remote configuration. +/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put +/// Request, it will be determined from this field in the remote configuration. +/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the +/// Put Request, it will be determined from this field in the remote configuration. +/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on +/// transaction cancellation. Defaults to False. +/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to +/// this remote entity. +/// * `check_limit` - This timer determines the expiry period for incrementing a check counter +/// after an EOF PDU is received for an incomplete file transfer. This allows out-of-order +/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to +/// 2, so the check limit timer may expire twice. +/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment +/// Procedures inside the class documentation. Expected as floating point seconds. Defaults to +/// 10 seconds. +/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment +/// Procedures inside the class documentation. Defaults to 2, so the timer may expire twice. +/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a +/// file data gap or lost metadata is detected in the acknowledged mode. Defaults to True. +/// * `nak_timer_interval_seconds` - See the notes on the Deferred Lost Segment Procedure inside +/// the class documentation. Expected as floating point seconds. Defaults to 10 seconds. +/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside +/// the class documentation. Defaults to 2, so the timer may expire two times. +#[derive(Debug, Copy, Clone)] pub struct RemoteEntityConfig { pub entity_id: UnsignedByteField, + pub max_packet_len: usize, pub max_file_segment_len: usize, - pub closure_requeted_by_default: bool, + pub closure_requested_by_default: bool, pub crc_on_transmission_by_default: bool, pub default_transmission_mode: TransmissionMode, pub default_crc_type: ChecksumType, + pub positive_ack_timer_interval_seconds: f32, + pub positive_ack_timer_expiration_limit: u32, pub check_limit: u32, + pub disposition_on_cancellation: bool, + pub immediate_nak_mode: bool, + pub nak_timer_interval_seconds: f32, + pub nak_timer_expiration_limit: u32, +} + +impl RemoteEntityConfig { + pub fn new_with_default_values( + entity_id: UnsignedByteField, + max_file_segment_len: usize, + max_packet_len: usize, + closure_requested_by_default: bool, + crc_on_transmission_by_default: bool, + default_transmission_mode: TransmissionMode, + default_crc_type: ChecksumType, + ) -> Self { + Self { + entity_id, + max_file_segment_len, + max_packet_len, + closure_requested_by_default, + crc_on_transmission_by_default, + default_transmission_mode, + default_crc_type, + check_limit: 2, + positive_ack_timer_interval_seconds: 10.0, + positive_ack_timer_expiration_limit: 2, + disposition_on_cancellation: false, + immediate_nak_mode: true, + nak_timer_interval_seconds: 10.0, + nak_timer_expiration_limit: 2, + } + } } pub trait RemoteEntityConfigProvider { - fn get_remote_config(&self, remote_id: &UnsignedByteField) -> Option<&RemoteEntityConfig>; + /// Retrieve the remote entity configuration for the given remote ID. + fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig>; + fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>; + /// Add a new remote configuration. Return [true] if the configuration was + /// inserted successfully, and [false] if a configuration already exists. + fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool; + /// Remote a configuration. Returns [true] if the configuration was removed successfully, + /// and [false] if no configuration exists for the given remote ID. + fn remove_config(&mut self, remote_id: u64) -> bool; } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg(feature = "std")] +#[derive(Default)] +pub struct StdRemoteEntityConfigProvider { + remote_cfg_table: HashMap, +} + +#[cfg(feature = "std")] +impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider { + fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> { + self.remote_cfg_table.get(&remote_id) + } + fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> { + self.remote_cfg_table.get_mut(&remote_id) + } + fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool { + self.remote_cfg_table + .insert(cfg.entity_id.value(), *cfg) + .is_some() + } + fn remove_config(&mut self, remote_id: u64) -> bool { + self.remote_cfg_table.remove(&remote_id).is_some() + } +} + +/// This trait introduces some callbacks which will be called when a particular CFDP fault +/// handler is called. +/// +/// It is passed into the CFDP handlers as part of the [DefaultFaultHandler] and the local entity +/// configuration and provides a way to specify custom user error handlers. This allows to +/// implement some CFDP features like fault handler logging, which would not be possible +/// generically otherwise. +/// +/// For each error reported by the [DefaultFaultHandler], the appropriate fault handler callback +/// will be called depending on the [FaultHandlerCode]. +pub trait UserFaultHandler { + fn notice_of_suspension_cb( + &mut self, + transaction_id: TransactionId, + cond: ConditionCode, + progress: u64, + ); + + fn notice_of_cancellation_cb( + &mut self, + transaction_id: TransactionId, + cond: ConditionCode, + progress: u64, + ); + + fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64); + + fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64); +} + +/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP +/// standard. +/// +/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler +/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used +/// to select the error handling inside the CFDP handler itself in addition to dispatching to a +/// user-provided callback function provided by the [UserFaultHandler]. +/// +/// Some note on the provided default settings: +/// +/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers, +/// cancelling the transfer immediately would interfere with the check limit mechanism specified +/// in chapter 4.6.3.3. +/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is +/// not supported the file transfer might still have worked properly. +/// +/// For all other faults, the default fault handling operation will be to cancel the transaction. +/// These defaults can be overriden by using the [Self::set_fault_handler] method. +/// Please note that in any case, fault handler overrides can be specified by the sending CFDP +/// entity. +pub struct DefaultFaultHandler { + handler_array: [FaultHandlerCode; 10], + // Could also change the user fault handler trait to have non mutable methods, but that limits + // flexbility on the user side.. + user_fault_handler: RefCell>, +} + +impl DefaultFaultHandler { + fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option { + Some(match conditon_code { + ConditionCode::PositiveAckLimitReached => 0, + ConditionCode::KeepAliveLimitReached => 1, + ConditionCode::InvalidTransmissionMode => 2, + ConditionCode::FilestoreRejection => 3, + ConditionCode::FileChecksumFailure => 4, + ConditionCode::FileSizeError => 5, + ConditionCode::NakLimitReached => 6, + ConditionCode::InactivityDetected => 7, + ConditionCode::CheckLimitReached => 8, + ConditionCode::UnsupportedChecksumType => 9, + _ => return None, + }) + } + + pub fn set_fault_handler( + &mut self, + condition_code: ConditionCode, + fault_handler: FaultHandlerCode, + ) { + let array_idx = Self::condition_code_to_array_index(condition_code); + if array_idx.is_none() { + return; + } + self.handler_array[array_idx.unwrap()] = fault_handler; + } + + pub fn new(user_fault_handler: Box) -> Self { + let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10]; + init_array + [Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] = + FaultHandlerCode::IgnoreError; + init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType) + .unwrap()] = FaultHandlerCode::IgnoreError; + Self { + handler_array: init_array, + user_fault_handler: RefCell::new(user_fault_handler), + } + } + + pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode { + let array_idx = Self::condition_code_to_array_index(condition_code); + if array_idx.is_none() { + return FaultHandlerCode::IgnoreError; + } + self.handler_array[array_idx.unwrap()] + } + + pub fn report_fault( + &self, + transaction_id: TransactionId, + condition: ConditionCode, + progress: u64, + ) -> FaultHandlerCode { + let array_idx = Self::condition_code_to_array_index(condition); + if array_idx.is_none() { + return FaultHandlerCode::IgnoreError; + } + let fh_code = self.handler_array[array_idx.unwrap()]; + let mut handler_mut = self.user_fault_handler.borrow_mut(); + match fh_code { + FaultHandlerCode::NoticeOfCancellation => { + handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress); + } + FaultHandlerCode::NoticeOfSuspension => { + handler_mut.notice_of_suspension_cb(transaction_id, condition, progress); + } + FaultHandlerCode::IgnoreError => { + handler_mut.ignore_cb(transaction_id, condition, progress); + } + FaultHandlerCode::AbandonTransaction => { + handler_mut.abandoned_cb(transaction_id, condition, progress); + } + } + fh_code + } +} + +pub struct IndicationConfig { + pub eof_sent: bool, + pub eof_recv: bool, + pub file_segment_recv: bool, + pub transaction_finished: bool, + pub suspended: bool, + pub resumed: bool, +} + +impl Default for IndicationConfig { + fn default() -> Self { + Self { + eof_sent: true, + eof_recv: true, + file_segment_recv: true, + transaction_finished: true, + suspended: true, + resumed: true, + } + } +} + +pub struct LocalEntityConfig { + pub id: UnsignedByteField, + pub indication_cfg: IndicationConfig, + pub default_fault_handler: DefaultFaultHandler, +} + +/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence +/// number of that transfer which is also determined by the CFDP source entity. +#[derive(Debug, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TransactionId { source_id: UnsignedByteField, @@ -121,23 +456,38 @@ impl TransactionId { } } +impl Hash for TransactionId { + fn hash(&self, state: &mut H) { + self.source_id.value().hash(state); + self.seq_num.value().hash(state); + } +} + +impl PartialEq for TransactionId { + fn eq(&self, other: &Self) -> bool { + self.source_id.value() == other.source_id.value() + && self.seq_num.value() == other.seq_num.value() + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TransactionStep { Idle = 0, TransactionStart = 1, ReceivingFileDataPdus = 2, - SendingAckPdu = 3, - TransferCompletion = 4, - SendingFinishedPdu = 5, + ReceivingFileDataPdusWithCheckLimitHandling = 3, + SendingAckPdu = 4, + TransferCompletion = 5, + SendingFinishedPdu = 6, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum State { Idle = 0, - BusyClass1Nacked = 2, - BusyClass2Acked = 3, + Busy = 1, + Suspended = 2, } pub const CRC_32: Crc = Crc::::new(&CRC_32_CKSUM); @@ -248,8 +598,8 @@ mod tests { pdu::{ eof::EofPdu, file_data::FileDataPdu, - metadata::{MetadataGenericParams, MetadataPdu}, - CommonPduConfig, FileDirectiveType, PduHeader, + metadata::{MetadataGenericParams, MetadataPduCreator}, + CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket, }, PduType, }; @@ -272,7 +622,8 @@ mod tests { let dest_file_name = "hello-dest.txt"; let src_lv = Lv::new_from_str(src_file_name).unwrap(); let dest_lv = Lv::new_from_str(dest_file_name).unwrap(); - let metadata_pdu = MetadataPdu::new(pdu_header, metadata_params, src_lv, dest_lv, None); + let metadata_pdu = + MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv); metadata_pdu .write_to_bytes(&mut buf) .expect("writing metadata PDU failed"); diff --git a/satrs-core/src/cfdp/user.rs b/satrs-core/src/cfdp/user.rs index 9047bbd..65ce909 100644 --- a/satrs-core/src/cfdp/user.rs +++ b/satrs-core/src/cfdp/user.rs @@ -1,10 +1,10 @@ use spacepackets::{ cfdp::{ pdu::{ - file_data::RecordContinuationState, + file_data::SegmentMetadata, finished::{DeliveryCode, FileStatus}, }, - tlv::msg_to_user::MsgToUserTlv, + tlv::{msg_to_user::MsgToUserTlv, WritableTlv}, ConditionCode, }, util::UnsignedByteField, @@ -30,13 +30,44 @@ pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> { pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>], } +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct OwnedMetadataRecvdParams { + pub id: TransactionId, + pub source_id: UnsignedByteField, + pub file_size: u64, + pub src_file_name: alloc::string::String, + pub dest_file_name: alloc::string::String, + pub msgs_to_user: alloc::vec::Vec>, +} + +#[cfg(feature = "alloc")] +impl From> for OwnedMetadataRecvdParams { + fn from(value: MetadataReceivedParams) -> Self { + Self::from(&value) + } +} + +#[cfg(feature = "alloc")] +impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams { + fn from(value: &MetadataReceivedParams) -> Self { + Self { + id: value.id, + source_id: value.source_id, + file_size: value.file_size, + src_file_name: value.src_file_name.into(), + dest_file_name: value.dest_file_name.into(), + msgs_to_user: value.msgs_to_user.iter().map(|tlv| tlv.to_vec()).collect(), + } + } +} + #[derive(Debug)] pub struct FileSegmentRecvdParams<'seg_meta> { pub id: TransactionId, pub offset: u64, pub length: usize, - pub rec_cont_state: Option, - pub segment_metadata: Option<&'seg_meta [u8]>, + pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>, } pub trait CfdpUser { diff --git a/satrs-core/src/encoding/ccsds.rs b/satrs-core/src/encoding/ccsds.rs index ecd4ff5..37694d7 100644 --- a/satrs-core/src/encoding/ccsds.rs +++ b/satrs-core/src/encoding/ccsds.rs @@ -113,7 +113,7 @@ pub fn parse_buffer_for_ccsds_space_packets( #[cfg(test)] mod tests { use spacepackets::{ - ecss::{tc::PusTcCreator, SerializablePusPacket}, + ecss::{tc::PusTcCreator, WritablePusPacket}, PacketId, SpHeader, }; diff --git a/satrs-core/src/hal/std/tcp_spacepackets_server.rs b/satrs-core/src/hal/std/tcp_spacepackets_server.rs index c124a47..6ade0d1 100644 --- a/satrs-core/src/hal/std/tcp_spacepackets_server.rs +++ b/satrs-core/src/hal/std/tcp_spacepackets_server.rs @@ -173,7 +173,7 @@ mod tests { use alloc::{boxed::Box, sync::Arc}; use hashbrown::HashSet; use spacepackets::{ - ecss::{tc::PusTcCreator, SerializablePusPacket}, + ecss::{tc::PusTcCreator, WritablePusPacket}, PacketId, SpHeader, }; diff --git a/satrs-core/src/hal/std/udp_server.rs b/satrs-core/src/hal/std/udp_server.rs index 28e0328..6f2cb5b 100644 --- a/satrs-core/src/hal/std/udp_server.rs +++ b/satrs-core/src/hal/std/udp_server.rs @@ -19,7 +19,7 @@ use std::vec::Vec; /// /// ``` /// use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; -/// use spacepackets::ecss::SerializablePusPacket; +/// use spacepackets::ecss::WritablePusPacket; /// use satrs_core::hal::std::udp_server::UdpTcServer; /// use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore}; /// use spacepackets::SpHeader; @@ -144,7 +144,7 @@ mod tests { use crate::hal::std::udp_server::{ReceiveResult, UdpTcServer}; use crate::tmtc::ReceivesTcCore; use spacepackets::ecss::tc::PusTcCreator; - use spacepackets::ecss::SerializablePusPacket; + use spacepackets::ecss::WritablePusPacket; use spacepackets::SpHeader; use std::boxed::Box; use std::collections::VecDeque; diff --git a/satrs-core/src/lib.rs b/satrs-core/src/lib.rs index 22699e0..061f728 100644 --- a/satrs-core/src/lib.rs +++ b/satrs-core/src/lib.rs @@ -20,6 +20,8 @@ extern crate downcast_rs; #[cfg(any(feature = "std", test))] extern crate std; +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod cfdp; pub mod encoding; pub mod error; diff --git a/satrs-core/src/pus/event.rs b/satrs-core/src/pus/event.rs index 165493d..9424b24 100644 --- a/satrs-core/src/pus/event.rs +++ b/satrs-core/src/pus/event.rs @@ -147,7 +147,7 @@ impl EventReporterBase { Ok(PusTmCreator::new( &mut sp_header, sec_header, - Some(&buf[0..current_idx]), + &buf[0..current_idx], true, )) } diff --git a/satrs-core/src/pus/scheduler.rs b/satrs-core/src/pus/scheduler.rs index ce98095..392eb87 100644 --- a/satrs-core/src/pus/scheduler.rs +++ b/satrs-core/src/pus/scheduler.rs @@ -626,7 +626,7 @@ mod tests { use crate::pool::{LocalPool, PoolCfg, PoolProvider, StoreAddr, StoreError}; use alloc::collections::btree_map::Range; use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader}; - use spacepackets::ecss::SerializablePusPacket; + use spacepackets::ecss::WritablePusPacket; use spacepackets::time::{cds, TimeWriter, UnixTimestamp}; use spacepackets::SpHeader; use std::time::Duration; @@ -857,7 +857,7 @@ mod tests { let mut sp_header = SpHeader::tc_unseg(apid_to_set, 105, 0).unwrap(); let mut sec_header = PusTcSecondaryHeader::new_simple(17, 1); sec_header.source_id = src_id_to_set; - let ping_tc = PusTcCreator::new(&mut sp_header, sec_header, None, true); + let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true); let req_id = RequestId::from_tc(&ping_tc); assert_eq!(req_id.source_id(), src_id_to_set); assert_eq!(req_id.apid(), apid_to_set); diff --git a/satrs-core/src/pus/test.rs b/satrs-core/src/pus/test.rs index fd5c05a..c205178 100644 --- a/satrs-core/src/pus/test.rs +++ b/satrs-core/src/pus/test.rs @@ -72,7 +72,7 @@ impl PusServiceHandler for PusService17TestHandler { // Sequence count will be handled centrally in TM funnel. let mut reply_header = SpHeader::tm_unseg(self.psb.tm_apid, 0, 0).unwrap(); let tc_header = PusTmSecondaryHeader::new_simple(17, 2, &time_stamp); - let ping_reply = PusTmCreator::new(&mut reply_header, tc_header, None, true); + let ping_reply = PusTmCreator::new(&mut reply_header, tc_header, &[], true); let result = self .psb .tm_sender @@ -118,7 +118,7 @@ mod tests { use crate::tmtc::tm_helper::SharedTmStore; use spacepackets::ecss::tc::{PusTcCreator, PusTcSecondaryHeader}; use spacepackets::ecss::tm::PusTmReader; - use spacepackets::ecss::{PusPacket, SerializablePusPacket}; + use spacepackets::ecss::{PusPacket, WritablePusPacket}; use spacepackets::{SequenceFlags, SpHeader}; use std::boxed::Box; use std::sync::{mpsc, RwLock}; @@ -154,7 +154,7 @@ mod tests { // Create a ping TC, verify acceptance. let mut sp_header = SpHeader::tc(TEST_APID, SequenceFlags::Unsegmented, 0, 0).unwrap(); let sec_header = PusTcSecondaryHeader::new_simple(17, 1); - let ping_tc = PusTcCreator::new(&mut sp_header, sec_header, None, true); + let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true); let token = verification_handler.add_tc(&ping_tc); let token = verification_handler .acceptance_success(token, None) diff --git a/satrs-core/src/pus/verification.rs b/satrs-core/src/pus/verification.rs index 47c2689..ff38b89 100644 --- a/satrs-core/src/pus/verification.rs +++ b/satrs-core/src/pus/verification.rs @@ -39,7 +39,7 @@ //! //! let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap(); //! let tc_header = PusTcSecondaryHeader::new_simple(17, 1); -//! let pus_tc_0 = PusTcCreator::new(&mut sph, tc_header, None, true); +//! let pus_tc_0 = PusTcCreator::new_no_app_data(&mut sph, tc_header, true); //! let init_token = reporter.add_tc(&pus_tc_0); //! //! // Complete success sequence for a telecommand @@ -87,7 +87,7 @@ use delegate::delegate; use serde::{Deserialize, Serialize}; use spacepackets::ecss::tc::IsPusTelecommand; use spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader}; -use spacepackets::ecss::{EcssEnumeration, PusError, SerializablePusPacket}; +use spacepackets::ecss::{EcssEnumeration, PusError, WritablePusPacket}; use spacepackets::{CcsdsPacket, PacketId, PacketSequenceCtrl}; use spacepackets::{SpHeader, MAX_APID}; @@ -353,7 +353,7 @@ impl<'src_data, State, SuccessOrFailure> VerificationSendable<'src_data, State, } pub fn len_packed(&self) -> usize { - self.pus_tm.as_ref().unwrap().len_packed() + self.pus_tm.as_ref().unwrap().len_written() } pub fn pus_tm(&self) -> &PusTmCreator<'src_data> { @@ -877,7 +877,7 @@ impl VerificationReporterCore { PusTmCreator::new( sp_header, tm_sec_header, - Some(&src_data_buf[0..source_data_len]), + &src_data_buf[0..source_data_len], true, ) } @@ -1438,6 +1438,7 @@ mod tests { fn base_tc_init(app_data: Option<&[u8]>) -> (PusTcCreator, RequestId) { let mut sph = SpHeader::tc_unseg(TEST_APID, 0x34, 0).unwrap(); let tc_header = PusTcSecondaryHeader::new_simple(17, 1); + let app_data = app_data.unwrap_or(&[]); let pus_tc = PusTcCreator::new(&mut sph, tc_header, app_data, true); let req_id = RequestId::new(&pus_tc); (pus_tc, req_id) @@ -2162,7 +2163,7 @@ mod tests { let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap(); let tc_header = PusTcSecondaryHeader::new_simple(17, 1); - let pus_tc_0 = PusTcCreator::new(&mut sph, tc_header, None, true); + let pus_tc_0 = PusTcCreator::new_no_app_data(&mut sph, tc_header, true); let init_token = reporter.add_tc(&pus_tc_0); // Complete success sequence for a telecommand diff --git a/satrs-core/src/tmtc/ccsds_distrib.rs b/satrs-core/src/tmtc/ccsds_distrib.rs index 58fb8c6..ea9bf30 100644 --- a/satrs-core/src/tmtc/ccsds_distrib.rs +++ b/satrs-core/src/tmtc/ccsds_distrib.rs @@ -21,7 +21,7 @@ //! use satrs_core::tmtc::ccsds_distrib::{CcsdsPacketHandler, CcsdsDistributor}; //! use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore}; //! use spacepackets::{CcsdsPacket, SpHeader}; -//! use spacepackets::ecss::SerializablePusPacket; +//! use spacepackets::ecss::WritablePusPacket; //! use spacepackets::ecss::tc::{PusTc, PusTcCreator}; //! //! #[derive (Default)] @@ -226,7 +226,7 @@ pub(crate) mod tests { use super::*; use crate::tmtc::ccsds_distrib::{CcsdsDistributor, CcsdsPacketHandler}; use spacepackets::ecss::tc::PusTcCreator; - use spacepackets::ecss::SerializablePusPacket; + use spacepackets::ecss::WritablePusPacket; use spacepackets::CcsdsPacket; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; @@ -244,9 +244,10 @@ pub(crate) mod tests { &buf[0..size] } + type SharedPacketQueue = Arc)>>>; pub struct BasicApidHandlerSharedQueue { - pub known_packet_queue: Arc)>>>, - pub unknown_packet_queue: Arc)>>>, + pub known_packet_queue: SharedPacketQueue, + pub unknown_packet_queue: SharedPacketQueue, } #[derive(Default)] @@ -268,11 +269,11 @@ pub(crate) mod tests { ) -> Result<(), Self::Error> { let mut vec = Vec::new(); vec.extend_from_slice(tc_raw); - Ok(self - .known_packet_queue + self.known_packet_queue .lock() .unwrap() - .push_back((sp_header.apid(), vec))) + .push_back((sp_header.apid(), vec)); + Ok(()) } fn handle_unknown_apid( @@ -282,11 +283,11 @@ pub(crate) mod tests { ) -> Result<(), Self::Error> { let mut vec = Vec::new(); vec.extend_from_slice(tc_raw); - Ok(self - .unknown_packet_queue + self.unknown_packet_queue .lock() .unwrap() - .push_back((sp_header.apid(), vec))) + .push_back((sp_header.apid(), vec)); + Ok(()) } } diff --git a/satrs-core/src/tmtc/pus_distrib.rs b/satrs-core/src/tmtc/pus_distrib.rs index a400484..e4eefb1 100644 --- a/satrs-core/src/tmtc/pus_distrib.rs +++ b/satrs-core/src/tmtc/pus_distrib.rs @@ -18,7 +18,7 @@ //! # Example //! //! ```rust -//! use spacepackets::ecss::SerializablePusPacket; +//! use spacepackets::ecss::WritablePusPacket; //! use satrs_core::tmtc::pus_distrib::{PusDistributor, PusServiceProvider}; //! use satrs_core::tmtc::{ReceivesTc, ReceivesTcCore}; //! use spacepackets::SpHeader; diff --git a/satrs-core/src/tmtc/tm_helper.rs b/satrs-core/src/tmtc/tm_helper.rs index 06ba532..6dca7ee 100644 --- a/satrs-core/src/tmtc/tm_helper.rs +++ b/satrs-core/src/tmtc/tm_helper.rs @@ -11,7 +11,7 @@ pub mod std_mod { use crate::pool::{ShareablePoolProvider, SharedPool, StoreAddr}; use crate::pus::EcssTmtcError; use spacepackets::ecss::tm::PusTmCreator; - use spacepackets::ecss::SerializablePusPacket; + use spacepackets::ecss::WritablePusPacket; use std::sync::{Arc, RwLock}; #[derive(Clone)] @@ -32,7 +32,7 @@ pub mod std_mod { pub fn add_pus_tm(&self, pus_tm: &PusTmCreator) -> Result { let mut pg = self.pool.write().map_err(|_| EcssTmtcError::StoreLock)?; - let (addr, buf) = pg.free_element(pus_tm.len_packed())?; + let (addr, buf) = pg.free_element(pus_tm.len_written())?; pus_tm .write_to_bytes(buf) .expect("writing PUS TM to store failed"); @@ -59,7 +59,7 @@ impl PusTmWithCdsShortHelper { &'a mut self, service: u8, subservice: u8, - source_data: Option<&'a [u8]>, + source_data: &'a [u8], seq_count: u16, ) -> PusTmCreator { let time_stamp = TimeProvider::from_now_with_u16_days().unwrap(); @@ -71,7 +71,7 @@ impl PusTmWithCdsShortHelper { &'a mut self, service: u8, subservice: u8, - source_data: Option<&'a [u8]>, + source_data: &'a [u8], stamper: &TimeProvider, seq_count: u16, ) -> PusTmCreator { @@ -83,7 +83,7 @@ impl PusTmWithCdsShortHelper { &'a self, service: u8, subservice: u8, - source_data: Option<&'a [u8]>, + source_data: &'a [u8], seq_count: u16, ) -> PusTmCreator { let mut reply_header = SpHeader::tm_unseg(self.apid, seq_count, 0).unwrap(); diff --git a/satrs-core/tests/hk_helpers.rs b/satrs-core/tests/hk_helpers.rs index 07264ef..8791b1e 100644 --- a/satrs-core/tests/hk_helpers.rs +++ b/satrs-core/tests/hk_helpers.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use core::mem::size_of; use serde::{Deserialize, Serialize}; -use spacepackets::ecss::{Ptc, RealPfc, UnsignedPfc}; +use spacepackets::ecss::{PfcReal, PfcUnsigned, Ptc}; use spacepackets::time::cds::TimeProvider; use spacepackets::time::{CcsdsTimeProvider, TimeWriter}; @@ -64,7 +64,7 @@ impl TestMgmHkWithIndividualValidity { curr_idx += 1; buf[curr_idx] = Ptc::Real as u8; curr_idx += 1; - buf[curr_idx] = RealPfc::Float as u8; + buf[curr_idx] = PfcReal::Float as u8; curr_idx += 1; buf[curr_idx..curr_idx + size_of::()].copy_from_slice(&self.temp.val.to_be_bytes()); curr_idx += size_of::(); @@ -75,7 +75,7 @@ impl TestMgmHkWithIndividualValidity { curr_idx += 1; buf[curr_idx] = Ptc::UnsignedInt as u8; curr_idx += 1; - buf[curr_idx] = UnsignedPfc::TwoBytes as u8; + buf[curr_idx] = PfcUnsigned::TwoBytes as u8; curr_idx += 1; buf[curr_idx] = 3; curr_idx += 1; @@ -100,7 +100,7 @@ impl TestMgmHkWithGroupValidity { curr_idx += 1; buf[curr_idx] = Ptc::Real as u8; curr_idx += 1; - buf[curr_idx] = RealPfc::Float as u8; + buf[curr_idx] = PfcReal::Float as u8; curr_idx += 1; buf[curr_idx..curr_idx + size_of::()].copy_from_slice(&self.temp.to_be_bytes()); curr_idx += size_of::(); @@ -109,7 +109,7 @@ impl TestMgmHkWithGroupValidity { curr_idx += 1; buf[curr_idx] = Ptc::UnsignedInt as u8; curr_idx += 1; - buf[curr_idx] = UnsignedPfc::TwoBytes as u8; + buf[curr_idx] = PfcUnsigned::TwoBytes as u8; curr_idx += 1; buf[curr_idx] = 3; for val in self.mgm_vals { diff --git a/satrs-core/tests/pus_verification.rs b/satrs-core/tests/pus_verification.rs index 097eba8..9c9fc63 100644 --- a/satrs-core/tests/pus_verification.rs +++ b/satrs-core/tests/pus_verification.rs @@ -9,7 +9,7 @@ pub mod crossbeam_test { use satrs_core::tmtc::tm_helper::SharedTmStore; use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader}; use spacepackets::ecss::tm::PusTmReader; - use spacepackets::ecss::{EcssEnumU16, EcssEnumU8, PusPacket, SerializablePusPacket}; + use spacepackets::ecss::{EcssEnumU16, EcssEnumU8, PusPacket, WritablePusPacket}; use spacepackets::SpHeader; use std::sync::{Arc, RwLock}; use std::thread; @@ -54,17 +54,17 @@ pub mod crossbeam_test { let mut tc_guard = shared_tc_pool_0.write().unwrap(); let mut sph = SpHeader::tc_unseg(TEST_APID, 0, 0).unwrap(); let tc_header = PusTcSecondaryHeader::new_simple(17, 1); - let pus_tc_0 = PusTcCreator::new(&mut sph, tc_header, None, true); + let pus_tc_0 = PusTcCreator::new_no_app_data(&mut sph, tc_header, true); req_id_0 = RequestId::new(&pus_tc_0); - let (addr, mut buf) = tc_guard.free_element(pus_tc_0.len_packed()).unwrap(); - pus_tc_0.write_to_bytes(&mut buf).unwrap(); + let (addr, buf) = tc_guard.free_element(pus_tc_0.len_written()).unwrap(); + pus_tc_0.write_to_bytes(buf).unwrap(); tx_tc_0.send(addr).unwrap(); let mut sph = SpHeader::tc_unseg(TEST_APID, 1, 0).unwrap(); let tc_header = PusTcSecondaryHeader::new_simple(5, 1); - let pus_tc_1 = PusTcCreator::new(&mut sph, tc_header, None, true); + let pus_tc_1 = PusTcCreator::new_no_app_data(&mut sph, tc_header, true); req_id_1 = RequestId::new(&pus_tc_1); - let (addr, mut buf) = tc_guard.free_element(pus_tc_0.len_packed()).unwrap(); - pus_tc_1.write_to_bytes(&mut buf).unwrap(); + let (addr, buf) = tc_guard.free_element(pus_tc_0.len_written()).unwrap(); + pus_tc_1.write_to_bytes(buf).unwrap(); tx_tc_1.send(addr).unwrap(); } let verif_sender_0 = thread::spawn(move || { @@ -81,16 +81,14 @@ pub mod crossbeam_test { tc_buf[0..tc_len].copy_from_slice(buf); } let (_tc, _) = PusTcReader::new(&tc_buf[0..tc_len]).unwrap(); - let accepted_token; let token = reporter_with_sender_0.add_tc_with_req_id(req_id_0); - accepted_token = reporter_with_sender_0 + let accepted_token = reporter_with_sender_0 .acceptance_success(token, Some(&FIXED_STAMP)) .expect("Acceptance success failed"); // Do some start handling here - let started_token; - started_token = reporter_with_sender_0 + let started_token = reporter_with_sender_0 .start_success(accepted_token, Some(&FIXED_STAMP)) .expect("Start success failed"); // Do some step handling here @@ -158,8 +156,7 @@ pub mod crossbeam_test { RequestId::from_bytes(&pus_tm.source_data()[0..RequestId::SIZE_AS_BYTES]) .expect("reading request ID from PUS TM source data failed"); if !verif_map.contains_key(&req_id) { - let mut content = Vec::new(); - content.push(pus_tm.subservice()); + let content = vec![pus_tm.subservice()]; verif_map.insert(req_id, content); } else { let content = verif_map.get_mut(&req_id).unwrap(); diff --git a/satrs-core/tests/tcp_servers.rs b/satrs-core/tests/tcp_servers.rs index 251eead..e5297c3 100644 --- a/satrs-core/tests/tcp_servers.rs +++ b/satrs-core/tests/tcp_servers.rs @@ -28,7 +28,7 @@ use satrs_core::{ tmtc::{ReceivesTcCore, TmPacketSourceCore}, }; use spacepackets::{ - ecss::{tc::PusTcCreator, SerializablePusPacket}, + ecss::{tc::PusTcCreator, WritablePusPacket}, PacketId, SpHeader, }; use std::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; diff --git a/satrs-example/Cargo.toml b/satrs-example/Cargo.toml index 3d61254..1f96d1a 100644 --- a/satrs-example/Cargo.toml +++ b/satrs-example/Cargo.toml @@ -23,5 +23,5 @@ thiserror = "1" path = "../satrs-core" [dependencies.satrs-mib] -version = "0.1.0-alpha.1" -# path = "../satrs-mib" +# version = "0.1.0-alpha.1" +path = "../satrs-mib" diff --git a/satrs-example/src/bin/simpleclient.rs b/satrs-example/src/bin/simpleclient.rs index 6f6de79..4d0dab7 100644 --- a/satrs-example/src/bin/simpleclient.rs +++ b/satrs-example/src/bin/simpleclient.rs @@ -2,7 +2,7 @@ use satrs_core::pus::verification::RequestId; use satrs_core::spacepackets::ecss::tc::PusTcCreator; use satrs_core::spacepackets::ecss::tm::PusTmReader; use satrs_core::{ - spacepackets::ecss::{PusPacket, SerializablePusPacket}, + spacepackets::ecss::{PusPacket, WritablePusPacket}, spacepackets::SpHeader, }; use satrs_example::{OBSW_SERVER_ADDR, SERVER_PORT}; diff --git a/satrs-example/src/main.rs b/satrs-example/src/main.rs index c537ab2..2a692a3 100644 --- a/satrs-example/src/main.rs +++ b/satrs-example/src/main.rs @@ -466,7 +466,7 @@ fn main() { let pus_tm = PusTmCreator::new( &mut sp_header, sec_header, - Some(&buf), + &buf, true, ); let addr = aocs_tm_store diff --git a/satrs-mib/Cargo.toml b/satrs-mib/Cargo.toml index e901ebe..b64813c 100644 --- a/satrs-mib/Cargo.toml +++ b/satrs-mib/Cargo.toml @@ -23,7 +23,8 @@ version = "1" optional = true [dependencies.satrs-core] -version = "0.1.0-alpha.1" +path = "../satrs-core" +# version = "0.1.0-alpha.1" # git = "https://egit.irs.uni-stuttgart.de/rust/sat-rs.git" # branch = "main" # rev = "35e1f7a983f6535c5571186e361fe101d4306b89" diff --git a/satrs-mib/codegen/Cargo.toml b/satrs-mib/codegen/Cargo.toml index 9a3887c..1ad3810 100644 --- a/satrs-mib/codegen/Cargo.toml +++ b/satrs-mib/codegen/Cargo.toml @@ -20,7 +20,8 @@ quote = "1" proc-macro2 = "1" [dependencies.satrs-core] -version = "0.1.0-alpha.1" +path = "../../satrs-core" +# version = "0.1.0-alpha.1" # git = "https://egit.irs.uni-stuttgart.de/rust/sat-rs.git" # branch = "main" # rev = "35e1f7a983f6535c5571186e361fe101d4306b89"