Merge pull request 'more docs' (#16) from more-docs into main

Reviewed-on: #16
This commit was merged in pull request #16.
This commit is contained in:
2025-11-27 15:35:37 +01:00
4 changed files with 185 additions and 14 deletions

View File

@@ -1,3 +1,5 @@
//! # Request module
#![deny(missing_docs)]
use core::str::Utf8Error;
use spacepackets::{
@@ -12,6 +14,9 @@ use spacepackets::{
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
/// File path is too large.
///
/// The file path length is limited to 255 bytes.
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -20,36 +25,56 @@ pub struct FilePathTooLarge(pub usize);
/// This trait is an abstraction for different Put Request structures which can be used
/// by Put Request consumers.
pub trait ReadablePutRequest {
/// Destination entity ID.
fn destination_id(&self) -> UnsignedByteField;
/// Source file path.
fn source_file(&self) -> Option<&str>;
/// Destination file path.
fn dest_file(&self) -> Option<&str>;
/// Transmission mode if explicitely specified.
fn trans_mode(&self) -> Option<TransmissionMode>;
/// Closure is requested for unacknowledged file transfer.
fn closure_requested(&self) -> Option<bool>;
/// Segmentation control.
fn seg_ctrl(&self) -> Option<SegmentationControl>;
/// Iterator over Messages to User TLVs, if any are supplied.
fn msgs_to_user(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
/// Iterator over fault handler override TLVs, if any are supplied.
fn fault_handler_overrides(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
/// Flow label TLV, if it is supplied.
fn flow_label(&self) -> Option<Tlv<'_>>;
/// Iterator over filestore request TLVs, if any are supplied.
fn fs_requests(&self) -> Option<impl Iterator<Item = Tlv<'_>>>;
}
/// Put request structure.
#[derive(Debug, PartialEq, Eq)]
pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> {
/// Destination entity ID.
pub destination_id: UnsignedByteField,
source_file: Option<&'src_file str>,
dest_file: Option<&'dest_file str>,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>,
/// Messages to user TLVs.
pub msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>,
/// Fault handler override TLVs.
pub fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>,
/// Flow label TLV.
pub flow_label: Option<Tlv<'flow_label>>,
/// Filestore request TLVs.
pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>,
}
impl<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests>
PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests>
{
/// Create a new put request with all possible fields.
#[allow(clippy::too_many_arguments)]
pub fn new(
destination_id: UnsignedByteField,
@@ -130,6 +155,9 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> {
}
}
/// Generic path checks.
///
/// This only checks the length of the paths.
pub fn generic_path_checks(
source_file: Option<&str>,
dest_file: Option<&str>,
@@ -148,6 +176,7 @@ pub fn generic_path_checks(
}
impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, 'static, 'static> {
/// New regular put request with no additional TLVs.
pub fn new_regular_request(
dest_id: UnsignedByteField,
source_file: &'src_file str,
@@ -171,12 +200,14 @@ impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static,
}
}
/// TLV has invalid type.
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TlvWithInvalidType(pub(crate) ());
impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> {
/// New put request which only contains messages to the user TLVs.
pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField,
msgs_to_user: &'msgs_to_user [Tlv<'msgs_to_user>],
@@ -212,6 +243,7 @@ impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static
}
}
/// Generic check of the TLV list type.
pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
opt_tlvs: Option<&[TlvProvider]>,
tlv_type: TlvType,
@@ -229,7 +261,9 @@ pub fn generic_tlv_list_type_check<TlvProvider: GenericTlv>(
true
}
/// Structure for all static put request fields.
pub struct StaticPutRequestFields {
/// Destination entity ID.
pub destination_id: UnsignedByteField,
/// Static buffer to store source file path.
pub source_file_buf: [u8; u8::MAX as usize],
@@ -239,8 +273,11 @@ pub struct StaticPutRequestFields {
pub dest_file_buf: [u8; u8::MAX as usize],
/// Current destination path length.
pub dest_file_len: usize,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>,
}
@@ -260,6 +297,7 @@ impl Default for StaticPutRequestFields {
}
impl StaticPutRequestFields {
/// Clears and resets the fields.
pub fn clear(&mut self) {
self.destination_id = UnsignedByteField::new(0, 0);
self.source_file_len = 0;
@@ -271,9 +309,11 @@ impl StaticPutRequestFields {
}
/// This is a put request cache structure which can be used to cache [ReadablePutRequest]s
/// without requiring run-time allocation. The user must specify the static buffer sizes used
/// to store TLVs or list of TLVs.
/// without requiring run-time allocation.
///
/// The user must specify the static buffer sizes used to store TLVs or list of TLVs.
pub struct StaticPutRequestCacher<const BUF_SIZE: usize> {
/// Static fields.
pub static_fields: StaticPutRequestFields,
opts_buf: [u8; BUF_SIZE],
opts_len: usize,
@@ -286,6 +326,7 @@ impl<const BUF_SIZE: usize> Default for StaticPutRequestCacher<BUF_SIZE> {
}
impl<const BUF_SIZE: usize> StaticPutRequestCacher<BUF_SIZE> {
/// Constructor.
pub fn new() -> Self {
Self {
static_fields: StaticPutRequestFields::default(),
@@ -294,6 +335,7 @@ impl<const BUF_SIZE: usize> StaticPutRequestCacher<BUF_SIZE> {
}
}
/// Set and update with using any generic [ReadablePutRequest].
pub fn set(
&mut self,
put_request: &impl ReadablePutRequest,
@@ -352,28 +394,34 @@ impl<const BUF_SIZE: usize> StaticPutRequestCacher<BUF_SIZE> {
Ok(())
}
/// Does the put request have a source file?
pub fn has_source_file(&self) -> bool {
self.static_fields.source_file_len > 0
}
/// Does the put request have a destination file?
pub fn has_dest_file(&self) -> bool {
self.static_fields.dest_file_len > 0
}
/// Source file path.
pub fn source_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(
&self.static_fields.source_file_buf[0..self.static_fields.source_file_len],
)
}
/// Destination file path.
pub fn dest_file(&self) -> Result<&str, Utf8Error> {
core::str::from_utf8(&self.static_fields.dest_file_buf[0..self.static_fields.dest_file_len])
}
/// Length of stored options TLVs.
pub fn opts_len(&self) -> usize {
self.opts_len
}
/// Raw options slice.
pub fn opts_slice(&self) -> &[u8] {
&self.opts_buf[0..self.opts_len]
}
@@ -388,6 +436,7 @@ impl<const BUF_SIZE: usize> StaticPutRequestCacher<BUF_SIZE> {
}
}
/// [alloc] support module.
#[cfg(feature = "alloc")]
pub mod alloc_mod {
@@ -400,19 +449,28 @@ pub mod alloc_mod {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PutRequestOwned {
/// Destination entity ID.
pub destination_id: UnsignedByteField,
source_file: Option<alloc::string::String>,
dest_file: Option<alloc::string::String>,
/// Transmission mode.
pub trans_mode: Option<TransmissionMode>,
/// Closure requested flag for unacknowledged file transfer.
pub closure_requested: Option<bool>,
/// Segmentation control.
pub seg_ctrl: Option<SegmentationControl>,
/// Messages to user TLVs.
pub msgs_to_user: Option<alloc::vec::Vec<TlvOwned>>,
/// Fault handler override TLVs.
pub fault_handler_overrides: Option<alloc::vec::Vec<TlvOwned>>,
/// Flow label TLV.
pub flow_label: Option<TlvOwned>,
/// Filestore request TLVs.
pub fs_requests: Option<alloc::vec::Vec<TlvOwned>>,
}
impl PutRequestOwned {
/// New regular put request with no additional TLVs.
pub fn new_regular_request(
dest_id: UnsignedByteField,
source_file: &str,
@@ -440,6 +498,7 @@ pub mod alloc_mod {
})
}
/// New put request which only contains messages to the user TLVs.
pub fn new_msgs_to_user_only(
dest_id: UnsignedByteField,
msgs_to_user: &[MsgToUserTlv<'_>],

View File

@@ -36,6 +36,7 @@
//! 6. A finished PDU ACK packet will be generated to be sent to the remote CFDP entity.
//! The [spacepackets::cfdp::pdu::finished::FinishedPduReader] can be used to inspect the
//! generated PDU.
#![deny(missing_docs)]
use core::{
cell::{Cell, RefCell},
ops::ControlFlow,
@@ -84,29 +85,42 @@ use super::{
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TransactionStep {
/// Idle state, nothing to do.
Idle = 0,
/// Transaction has started.
TransactionStart = 1,
/// Sending Metadata PDU.
SendingMetadata = 3,
/// Sending file data PDUs.
SendingFileData = 4,
/// Re-transmitting missing packets in acknowledged mode
Retransmitting = 5,
/// Sending an EOF PDU.
SendingEof = 6,
/// Waiting for the acknowledgement of the EOF PDU.
WaitingForEofAck = 7,
/// Waiting for the Finished PDU from the receiver.
WaitingForFinished = 8,
/// Performing the notice of completion.
NoticeOfCompletion = 10,
}
/// Parameter related to the file transfer.
#[derive(Default, Debug, Copy, Clone)]
pub struct FileParams {
pub progress: u64,
pub segment_len: u64,
pub crc32: u32,
pub metadata_only: bool,
pub file_size: u64,
pub empty_file: bool,
struct FileParams {
/// Progress of the file transfer.
progress: u64,
/// Segment length for a single file segment which is limited by various factors.
segment_len: u64,
/// Metadata only flag.
metadata_only: bool,
/// File size.
file_size: u64,
/// Empty file flag.
empty_file: bool,
/// The checksum is cached to avoid expensive re-calculation when the EOF PDU needs to be
/// re-sent.
pub checksum_completed_file: Option<u32>,
checksum_completed_file: Option<u32>,
}
// Explicit choice to put all simple internal fields into Cells.
@@ -137,6 +151,7 @@ impl StateHelper {
}
}
/// Parameters related to the Finished PDU.
#[derive(Debug, Copy, Clone)]
pub struct FinishedParams {
condition_code: ConditionCode,
@@ -144,68 +159,98 @@ pub struct FinishedParams {
file_status: FileStatus,
}
/// Source handler errors.
#[derive(Debug, thiserror::Error)]
pub enum SourceError {
/// Can not process the passed packet type.
#[error("can not process packet type {pdu_type:?} with directive type {directive_type:?}")]
CantProcessPacketType {
/// PDU type.
pdu_type: PduType,
/// Directive type, if applicable.
directive_type: Option<FileDirectiveType>,
},
/// Unexpected PDU for current state.
#[error("unexpected PDU")]
UnexpectedPdu {
/// PDU type.
pdu_type: PduType,
/// Directive type, if applicable.
directive_type: Option<FileDirectiveType>,
},
/// Put request is already active.
#[error("source handler is already busy with put request")]
PutRequestAlreadyActive,
/// Error during the caching process of a put request.
#[error("error caching put request")]
PutRequestCaching(ByteConversionError),
/// Generic filestore error.
#[error("filestore error: {0}")]
FilestoreError(#[from] FilestoreError),
/// Source file name is not valid UTF-8.
#[error("source file does not have valid UTF8 format: {0}")]
SourceFileNotValidUtf8(Utf8Error),
/// Destination file name is not valid UTF-8.
#[error("destination file does not have valid UTF8 format: {0}")]
DestFileNotValidUtf8(Utf8Error),
/// Invalid NAK PDU error.
#[error("invalid NAK PDU received")]
InvalidNakPdu,
/// PDU creation error.
#[error("error related to PDU creation: {0}")]
Pdu(#[from] PduError),
/// Feature not implemented error.
#[error("cfdp feature not implemented")]
NotImplemented,
/// Generic send error.
#[error("issue sending PDU: {0}")]
SendError(#[from] GenericSendError),
}
/// Put request errors.
#[derive(Debug, thiserror::Error)]
pub enum PutRequestError {
/// Storage error.
#[error("error caching put request: {0}")]
Storage(#[from] ByteConversionError),
/// Already busy with a put request.
#[error("already busy with put request")]
AlreadyBusy,
/// No remote entity configuration was found for destination ID.
#[error("no remote entity configuration found for {0:?}")]
NoRemoteCfgFound(UnsignedByteField),
/// Source file name is not valid UTF-8.
#[error("source file does not have valid UTF8 format: {0}")]
SourceFileNotValidUtf8(#[from] Utf8Error),
/// File does not exist.
#[error("source file does not exist")]
FileDoesNotExist,
/// Generic filestore error.
#[error("filestore error: {0}")]
FilestoreError(#[from] FilestoreError),
}
/// Anomaly tracker for the source handler.
///
/// Anomalies are unexpected events which are not severe errors.
#[derive(Debug, Default, Clone, Copy)]
pub struct AnomalyTracker {
invalid_ack_directive_code: u8,
}
/// Finite state-machine context.
#[derive(Debug, Default, PartialEq, Eq)]
pub enum FsmContext {
enum FsmContext {
/// None
#[default]
None,
/// The FSM should be reset when possible.
ResetWhenPossible,
}
/// Transaction parameters.
#[derive(Debug)]
pub struct TransactionParams<CountdownInstance: Countdown> {
struct TransactionParams<CountdownInstance: Countdown> {
transaction_id: Option<TransactionId>,
remote_cfg: Option<RemoteEntityConfig>,
transmission_mode: Option<super::TransmissionMode>,
@@ -399,6 +444,9 @@ impl<
}
}
/// Transcation ID for the currently active transaction.
///
/// Returns [None] if no transaction is active.
#[inline]
pub fn transaction_id(&self) -> Option<TransactionId> {
self.transaction_params.transaction_id
@@ -417,11 +465,13 @@ impl<
self.state_helper.step.get()
}
/// Current state of the source handler.
#[inline]
pub fn state(&self) -> State {
self.state_helper.state.get()
}
/// Local configuration of the source handler.
#[inline]
pub fn local_cfg(&self) -> &LocalEntityConfig<UserFaultHookInstance> {
&self.local_cfg
@@ -1129,6 +1179,9 @@ impl<
Ok(())
}
/// Manually trigger a notice of cancellation.
///
/// This cancels any currently active transaction.
pub fn notice_of_cancellation(
&mut self,
user: &mut impl CfdpUser,
@@ -1174,12 +1227,18 @@ impl<
}
}
/// Manually trigger a notice of suspension.
///
/// Please note that proper susopension handling is not implemented yet.
pub fn notice_of_suspension(&mut self) {
self.notice_of_suspension_internal();
}
fn notice_of_suspension_internal(&self) {}
fn notice_of_suspension_internal(&self) {
// TODO: Implement.
}
/// Manually abandon the currently active transaction.
pub fn abandon_transaction(&mut self) {
// I guess an abandoned transaction just stops whatever the handler is doing and resets
// it to a clean state.. The implementation for this is quite easy.

View File

@@ -1,7 +1,11 @@
//! # Time support module.
#![deny(missing_docs)]
use core::fmt::Debug;
/// Generic abstraction for a check/countdown timer. Should also be cheap to copy and clone.
pub trait Countdown: Debug {
/// The countdown has expired.
fn has_expired(&self) -> bool;
/// Reset the countdown to its initial state.
fn reset(&mut self);
}

View File

@@ -1,3 +1,5 @@
//! # User support and hooks module
#![deny(missing_docs)]
#[cfg(feature = "alloc")]
use spacepackets::cfdp::tlv::WritableTlv;
use spacepackets::{
@@ -14,34 +16,53 @@ use spacepackets::{
use super::TransactionId;
/// Parameters related to a finished transfer.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransactionFinishedParams {
/// ID of the transfer.
pub id: TransactionId,
/// Condition code.
pub condition_code: ConditionCode,
/// Delivery code.
pub delivery_code: DeliveryCode,
/// File status.
pub file_status: FileStatus,
}
/// Parameters related to the reception of a metadata PDU, which might start file reception.
#[derive(Debug)]
pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> {
/// ID of the transfer.
pub id: TransactionId,
/// Source entity ID.
pub source_id: UnsignedByteField,
/// File size.
pub file_size: u64,
/// Source file name.
pub src_file_name: &'src_file str,
/// Destination file name.
pub dest_file_name: &'dest_file str,
/// Messages to user TLVs.
pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>],
}
/// Owned variant of [MetadataReceivedParams].
#[cfg(feature = "alloc")]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct OwnedMetadataRecvdParams {
/// ID of the transfer.
pub id: TransactionId,
/// Source entity ID.
pub source_id: UnsignedByteField,
/// File size.
pub file_size: u64,
/// Source file name.
pub src_file_name: alloc::string::String,
/// Destination file name.
pub dest_file_name: alloc::string::String,
/// Messages to user TLVs.
pub msgs_to_user: alloc::vec::Vec<alloc::vec::Vec<u8>>,
}
@@ -66,35 +87,63 @@ impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams {
}
}
/// Parameters related to the reception of a file segment PDU.
#[derive(Debug)]
pub struct FileSegmentRecvdParams<'seg_meta> {
/// ID of the transfer.
pub id: TransactionId,
/// Offset of the segment.
pub offset: u64,
/// Length of the segment.
pub length: usize,
/// Segment metadata, if present.
pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>,
}
/// Generic CFDP user as specified in the CFDP standard.
///
/// This trait declares all indications which are possible.
pub trait CfdpUser {
/// Indication that a new transaction has started.
fn transaction_indication(&mut self, id: &TransactionId);
/// Indication that an EOF PDU has been sent.
fn eof_sent_indication(&mut self, id: &TransactionId);
/// Indication that a transaction has finished.
fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams);
/// Indication that metadata has been received.
fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams);
/// Indication that a file segment has been received.
fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams);
// TODO: The standard does not strictly specify how the report information looks..
/// Report information indication.
fn report_indication(&mut self, id: &TransactionId);
/// Indication that a transfer has been suspended.
fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode);
/// Indication that a transfer has been resumed.
fn resumed_indication(&mut self, id: &TransactionId, progress: u64);
/// Indication that a fault has occured.
fn fault_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
/// Indication that a transfer has been abandoned.
fn abandoned_indication(
&mut self,
id: &TransactionId,
condition_code: ConditionCode,
progress: u64,
);
/// Indication that an EOF PDU has been received.
fn eof_recvd_indication(&mut self, id: &TransactionId);
}