7 Commits
v0.3.0 ... HEAD

Author SHA1 Message Date
748142b0a4 Merge pull request 'more docs' (#16) from more-docs into main
Reviewed-on: #16
2025-11-27 15:35:37 +01:00
Robin Mueller
52b21db579 more docs 2025-11-27 15:35:05 +01:00
f4d66bb482 Merge pull request 'bump spacepackets' (#15) from bump-spacepackets into main
Reviewed-on: #15
2025-11-27 13:55:10 +01:00
Robin Mueller
98096fdea8 bump spacepackets 2025-11-27 13:54:01 +01:00
9494e29781 Merge pull request 'removed doc_auto_cfg feature' (#14) from remove-doc-auto-cfg-feature into main
Reviewed-on: #14
2025-10-02 09:47:33 +02:00
Robin Mueller
0d876c5525 removed doc_auto_cfg feature 2025-10-01 00:32:34 +02:00
44c31c935a use released cfdp-py in interop example 2025-09-25 14:06:08 +02:00
12 changed files with 297 additions and 139 deletions

View File

@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.86.0
- uses: dtolnay/rust-toolchain@1.87
- run: cargo check --release
cross-check:
@@ -63,7 +63,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt"
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt" --no-deps
clippy:
name: Clippy

View File

@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
- Bumped `spacepackets` to v0.17
# [v0.3.0] 2025-09-25
- Bumped `spacepackets` to v0.16

View File

@@ -2,7 +2,7 @@
name = "cfdp-rs"
version = "0.3.0"
edition = "2024"
rust-version = "1.86.0"
rust-version = "1.87"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
description = "High level CCSDS File Delivery Protocol components"
homepage = "https://egit.irs.uni-stuttgart.de/rust/cfdp"
@@ -20,7 +20,7 @@ crc = "3"
smallvec = "1"
derive-new = ">=0.6, <=0.7"
hashbrown = { version = ">=0.14, <=0.15", optional = true }
spacepackets = { version = "0.16", default-features = false }
spacepackets = { version = "0.17", default-features = false }
thiserror = { version = "2", default-features = false }
heapless = "0.9"
serde = { version = "1", optional = true }

View File

@@ -28,7 +28,7 @@ use spacepackets::{
ChecksumType, ConditionCode, TransmissionMode,
pdu::{PduError, file_data::FileDataPdu, metadata::MetadataPduReader},
},
util::{UnsignedByteFieldU16, UnsignedEnum},
util::UnsignedByteFieldU16,
};
static KILL_APP: AtomicBool = AtomicBool::new(false);
@@ -258,17 +258,17 @@ impl UdpServer {
fn pdu_printout(pdu: &PduOwnedWithInfo) {
match pdu.pdu_type() {
spacepackets::cfdp::PduType::FileDirective => match pdu.file_directive_type().unwrap() {
spacepackets::cfdp::pdu::FileDirectiveType::EofPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::FinishedPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::AckPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::MetadataPdu => {
spacepackets::cfdp::pdu::FileDirectiveType::Eof => (),
spacepackets::cfdp::pdu::FileDirectiveType::Finished => (),
spacepackets::cfdp::pdu::FileDirectiveType::Ack => (),
spacepackets::cfdp::pdu::FileDirectiveType::Metadata => {
let meta_pdu =
MetadataPduReader::new(pdu.raw_pdu()).expect("creating metadata pdu failed");
debug!("Metadata PDU: {:?}", meta_pdu)
}
spacepackets::cfdp::pdu::FileDirectiveType::NakPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::PromptPdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::KeepAlivePdu => (),
spacepackets::cfdp::pdu::FileDirectiveType::Nak => (),
spacepackets::cfdp::pdu::FileDirectiveType::Prompt => (),
spacepackets::cfdp::pdu::FileDirectiveType::KeepAlive => (),
},
spacepackets::cfdp::PduType::FileData => {
let fd_pdu =

View File

@@ -1 +1 @@
cfdp-py @ git+https://github.com/us-irs/cfdp-py.git@main
cfdp-py == 0.6.0

View File

@@ -1,9 +1,12 @@
all: check build embedded clippy fmt docs test coverage
all: check build embedded clippy check-fmt docs test coverage
clippy:
cargo clippy -- -D warnings
fmt:
cargo fmt --all
check-fmt:
cargo fmt --all -- --check
check:
@@ -20,12 +23,10 @@ embedded:
cargo build --target thumbv7em-none-eabihf --no-default-features --features "defmt, packet-buf-1k"
docs:
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --features "serde, defmt"
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt" --no-deps
docs-html:
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --features "serde, defmt" --open
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc --features "serde, defmt" --open
coverage:
cargo llvm-cov nextest

View File

@@ -62,7 +62,7 @@ use spacepackets::{
},
tlv::{EntityIdTlv, GenericTlv, ReadableTlv, TlvType, msg_to_user::MsgToUserTlv},
},
util::{UnsignedByteField, UnsignedEnum},
util::UnsignedByteField,
};
#[derive(Debug)]
@@ -608,33 +608,31 @@ impl<
) -> Result<u32, DestError> {
let mut sent_packets = 0;
match pdu_directive {
FileDirectiveType::EofPdu => {
FileDirectiveType::Eof => {
let eof_pdu = EofPdu::from_bytes(raw_packet)?;
sent_packets += self.handle_eof_pdu(cfdp_user, eof_pdu)?
}
FileDirectiveType::FinishedPdu
| FileDirectiveType::NakPdu
| FileDirectiveType::KeepAlivePdu => {
FileDirectiveType::Finished | FileDirectiveType::Nak | FileDirectiveType::KeepAlive => {
return Err(DestError::CantProcessPacketType {
pdu_type: PduType::FileDirective,
directive_type: Some(pdu_directive),
});
}
FileDirectiveType::AckPdu => {
FileDirectiveType::Ack => {
let ack_pdu = AckPdu::from_bytes(raw_packet)?;
self.handle_ack_pdu(ack_pdu)?;
}
FileDirectiveType::MetadataPdu => {
FileDirectiveType::Metadata => {
let metadata_pdu = MetadataPduReader::from_bytes(raw_packet)?;
self.handle_metadata_pdu(metadata_pdu)?
}
FileDirectiveType::PromptPdu => self.handle_prompt_pdu(raw_packet)?,
FileDirectiveType::Prompt => self.handle_prompt_pdu(raw_packet)?,
};
Ok(sent_packets)
}
fn handle_ack_pdu(&mut self, ack_pdu: AckPdu) -> Result<(), DestError> {
if ack_pdu.directive_code_of_acked_pdu() != FileDirectiveType::FinishedPdu {
if ack_pdu.directive_code_of_acked_pdu() != FileDirectiveType::Finished {
self.transaction_params
.anomaly_tracker
.increment_invalid_ack_directive_code();
@@ -827,7 +825,7 @@ impl<
.finish(0, self.transaction_params.progress)
.map_err(PduError::from)?;
self.pdu_sender.send_file_directive_pdu(
FileDirectiveType::NakPdu,
FileDirectiveType::Nak,
&self.pdu_and_cksum_buffer.borrow()[0..written_size],
)?;
packets_sent += 1;
@@ -954,7 +952,7 @@ impl<
};
let written_len = nak_pdu.write_to_bytes(self.pdu_and_cksum_buffer.get_mut())?;
self.pdu_sender.send_file_directive_pdu(
FileDirectiveType::NakPdu,
FileDirectiveType::Nak,
&self.pdu_and_cksum_buffer.borrow()[0..written_len],
)?;
sent_packets += 1;
@@ -998,7 +996,7 @@ impl<
if self.transmission_mode().unwrap() == TransmissionMode::Unacknowledged {
return Err(DestError::WrongStepForPdu {
pdu_type: PduType::FileDirective,
file_directive_type: Some(FileDirectiveType::EofPdu),
file_directive_type: Some(FileDirectiveType::Eof),
step: self.step(),
});
}
@@ -1116,7 +1114,7 @@ impl<
);
let written_len = ack_pdu.write_to_bytes(self.pdu_and_cksum_buffer.get_mut())?;
self.pdu_sender.send_file_directive_pdu(
FileDirectiveType::AckPdu,
FileDirectiveType::Ack,
&self.pdu_and_cksum_buffer.borrow()[0..written_len],
)?;
Ok(())
@@ -1280,7 +1278,7 @@ impl<
.finish(0, self.transaction_params.file_size)
.map_err(PduError::from)?;
self.pdu_sender.send_file_directive_pdu(
FileDirectiveType::NakPdu,
FileDirectiveType::Nak,
&self.pdu_and_cksum_buffer.borrow()[0..written_len],
)?;
sent_packets += 1;
@@ -1336,7 +1334,7 @@ impl<
.finish(current_start_of_scope, current_end_of_scope)
.map_err(PduError::from)?;
self.pdu_sender.send_file_directive_pdu(
FileDirectiveType::NakPdu,
FileDirectiveType::Nak,
&self.pdu_and_cksum_buffer.borrow()[..written_len],
)?;
sent_packets += 1;
@@ -1382,7 +1380,7 @@ impl<
.finish(current_start_of_scope, current_end_of_scope)
.map_err(PduError::from)?;
self.pdu_sender.send_file_directive_pdu(
FileDirectiveType::NakPdu,
FileDirectiveType::Nak,
&self.pdu_and_cksum_buffer.borrow()[..written_len],
)?;
sent_packets += 1;
@@ -1898,7 +1896,7 @@ impl<
};
finished_pdu.write_to_bytes(self.pdu_and_cksum_buffer.get_mut())?;
self.pdu_sender.send_file_directive_pdu(
FileDirectiveType::FinishedPdu,
FileDirectiveType::Finished,
&self.pdu_and_cksum_buffer.borrow()[0..finished_pdu.len_written()],
)?;
Ok(1)
@@ -1926,7 +1924,7 @@ mod tests {
nak::NakPduReader,
},
},
util::UnsignedByteFieldU8,
util::{UnsignedByteFieldU8, UnsignedEnum},
};
use crate::{
@@ -2053,7 +2051,7 @@ mod tests {
fn remote_cfg_mut(&mut self) -> &mut RemoteEntityConfig {
self.handler
.remote_cfg_table
.get_mut(LOCAL_ID.value())
.get_mut(LOCAL_ID.value_raw())
.unwrap()
}
@@ -2214,13 +2212,13 @@ mod tests {
assert!(!self.pdu_queue_empty());
let pdu = self.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::AckPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Ack);
let ack_pdu = AckPdu::from_bytes(&pdu.raw_pdu).unwrap();
assert_eq!(ack_pdu.condition_code(), cond_code);
assert_eq!(ack_pdu.transaction_status(), TransactionStatus::Active);
assert_eq!(
ack_pdu.directive_code_of_acked_pdu(),
FileDirectiveType::EofPdu
FileDirectiveType::Eof
);
}
@@ -2229,7 +2227,7 @@ mod tests {
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(
pdu.file_directive_type.unwrap(),
FileDirectiveType::FinishedPdu
FileDirectiveType::Finished
);
let finished_pdu = FinishedPduReader::from_bytes(&pdu.raw_pdu).unwrap();
assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete);
@@ -2248,7 +2246,7 @@ mod tests {
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(
pdu.file_directive_type.unwrap(),
FileDirectiveType::FinishedPdu
FileDirectiveType::Finished
);
let finished_pdu = FinishedPduReader::from_bytes(&pdu.raw_pdu).unwrap();
assert_eq!(finished_pdu.delivery_code(), delivery_code);
@@ -2764,7 +2762,7 @@ mod tests {
assert_eq!(sent_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
sent_pdu.file_directive_type,
Some(FileDirectiveType::FinishedPdu)
Some(FileDirectiveType::Finished)
);
let finished_pdu = FinishedPduReader::from_bytes(&sent_pdu.raw_pdu).unwrap();
assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
@@ -2826,7 +2824,7 @@ mod tests {
} = error
{
assert_eq!(pdu_type, PduType::FileDirective);
assert_eq!(directive_type, Some(FileDirectiveType::FinishedPdu));
assert_eq!(directive_type, Some(FileDirectiveType::Finished));
}
}
@@ -2938,7 +2936,7 @@ mod tests {
assert_eq!(sent_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
sent_pdu.file_directive_type,
Some(FileDirectiveType::FinishedPdu)
Some(FileDirectiveType::Finished)
);
let finished_pdu = FinishedPduReader::from_bytes(&sent_pdu.raw_pdu).unwrap();
assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
@@ -3083,7 +3081,7 @@ mod tests {
assert_eq!(finished_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
finished_pdu.file_directive_type.unwrap(),
FileDirectiveType::FinishedPdu
FileDirectiveType::Finished
);
let finished_pdu = FinishedPduReader::from_bytes(&finished_pdu.raw_pdu).unwrap();
assert_eq!(
@@ -3197,7 +3195,7 @@ mod tests {
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
next_pdu.file_directive_type.unwrap(),
FileDirectiveType::FinishedPdu
FileDirectiveType::Finished
);
let finished_pdu =
FinishedPduReader::new(&next_pdu.raw_pdu).expect("finished pdu read failed");
@@ -3249,7 +3247,7 @@ mod tests {
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
next_pdu.file_directive_type.unwrap(),
FileDirectiveType::FinishedPdu
FileDirectiveType::Finished
);
let finished_pdu =
FinishedPduReader::new(&next_pdu.raw_pdu).expect("finished pdu read failed");
@@ -3284,7 +3282,7 @@ mod tests {
let remote_cfg_mut = tb
.handler
.remote_cfg_table
.get_mut(LOCAL_ID.value())
.get_mut(LOCAL_ID.value_raw())
.unwrap();
remote_cfg_mut.disposition_on_cancellation = true;
let mut user = tb.test_user_from_cached_paths(0);
@@ -3322,7 +3320,7 @@ mod tests {
let remote_cfg_mut = tb
.handler
.remote_cfg_table
.get_mut(LOCAL_ID.value())
.get_mut(LOCAL_ID.value_raw())
.unwrap();
remote_cfg_mut.disposition_on_cancellation = true;
let mut user = tb.test_user_from_cached_paths(file_size);
@@ -3378,7 +3376,7 @@ mod tests {
assert_eq!(tb.pdu_queue_len(), 1);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.pdu_header().common_pdu_conf().file_flag, file_flag);
assert_eq!(nak_pdu.start_of_scope(), 0);
@@ -3444,7 +3442,7 @@ mod tests {
assert_eq!(tb.pdu_queue_len(), 1);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), file_size);
@@ -3494,7 +3492,7 @@ mod tests {
assert_eq!(tb.pdu_queue_len(), 1);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), file_size);
@@ -3552,7 +3550,7 @@ mod tests {
tb.check_eof_ack_pdu(ConditionCode::NoError);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), file_size);
@@ -3614,7 +3612,7 @@ mod tests {
assert_eq!(tb.pdu_queue_len(), 1);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), file_size);
@@ -3628,7 +3626,7 @@ mod tests {
assert_eq!(tb.pdu_queue_len(), 1);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), file_size);
@@ -3842,7 +3840,7 @@ mod tests {
assert_eq!(tb.pdu_queue_len(), 1);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), file_size);
@@ -3926,7 +3924,7 @@ mod tests {
tb.check_eof_ack_pdu(ConditionCode::NoError);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), 10);
@@ -3935,7 +3933,7 @@ mod tests {
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 10);
assert_eq!(nak_pdu.end_of_scope(), file_size);
@@ -4036,7 +4034,7 @@ mod tests {
tb.check_eof_ack_pdu(ConditionCode::NoError);
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 0);
assert_eq!(nak_pdu.end_of_scope(), 14);
@@ -4045,7 +4043,7 @@ mod tests {
let pdu = tb.get_next_pdu().unwrap();
assert_eq!(pdu.pdu_type, PduType::FileDirective);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::NakPdu);
assert_eq!(pdu.file_directive_type.unwrap(), FileDirectiveType::Nak);
let nak_pdu = NakPduReader::new(&pdu.raw_pdu).unwrap();
assert_eq!(nak_pdu.start_of_scope(), 14);
assert_eq!(nak_pdu.end_of_scope(), file_size);

View File

@@ -109,7 +109,7 @@
//! [threadpool](https://docs.rs/threadpool/latest/threadpool/).
#![no_std]
// Show which crate feature enables conditionally compiled APIs in documentation.
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(any(feature = "std", test))]
@@ -138,7 +138,7 @@ use spacepackets::{
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
pdu::{FileDirectiveType, PduError, PduHeader},
},
util::{UnsignedByteField, UnsignedEnum},
util::UnsignedByteField,
};
#[cfg(feature = "std")]
pub use std_mod::*;
@@ -942,26 +942,26 @@ pub fn determine_packet_target(raw_pdu: &[u8]) -> Result<PacketTarget, PduError>
let packet_target = match file_directive_type {
// Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
// the source handler
FileDirectiveType::NakPdu
| FileDirectiveType::FinishedPdu
| FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
FileDirectiveType::Nak | FileDirectiveType::Finished | FileDirectiveType::KeepAlive => {
PacketTarget::SourceEntity
}
// Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
// the destination handler
FileDirectiveType::MetadataPdu
| FileDirectiveType::EofPdu
| FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
FileDirectiveType::Metadata | FileDirectiveType::Eof | FileDirectiveType::Prompt => {
PacketTarget::DestEntity
}
// Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
// extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
// the source handler, for a Finished PDU, it is passed to the destination handler.
FileDirectiveType::AckPdu => {
FileDirectiveType::Ack => {
let acked_directive = FileDirectiveType::try_from(raw_pdu[header_len + 1] >> 4)
.map_err(|_| PduError::InvalidDirectiveType {
found: (raw_pdu[header_len + 1] >> 4),
expected: None,
})?;
if acked_directive == FileDirectiveType::EofPdu {
if acked_directive == FileDirectiveType::Eof {
PacketTarget::SourceEntity
} else if acked_directive == FileDirectiveType::FinishedPdu {
} else if acked_directive == FileDirectiveType::Finished {
PacketTarget::DestEntity
} else {
// TODO: Maybe a better error? This might be confusing..
@@ -1576,7 +1576,7 @@ pub(crate) mod tests {
assert!(packet_info.file_directive_type().is_some());
assert_eq!(
packet_info.file_directive_type().unwrap(),
FileDirectiveType::MetadataPdu
FileDirectiveType::Metadata
);
assert_eq!(
packet_info.raw_packet(),
@@ -1623,7 +1623,7 @@ pub(crate) mod tests {
assert_eq!(packet_info.raw_packet(), &buf[0..eof_pdu.len_written()]);
assert_eq!(
packet_info.file_directive_type().unwrap(),
FileDirectiveType::EofPdu
FileDirectiveType::Eof
);
}
@@ -1659,7 +1659,7 @@ pub(crate) mod tests {
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value().into()).unwrap();
assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
assert_eq!(remote_entity_retrieved.max_packet_len, 1024);
assert!(remote_entity_retrieved.closure_requested_by_default);
@@ -1668,7 +1668,7 @@ pub(crate) mod tests {
remote_entity_retrieved.default_crc_type,
ChecksumType::Crc32
);
let remote_entity_mut = remote_entity_cfg.get_mut(REMOTE_ID.value()).unwrap();
let remote_entity_mut = remote_entity_cfg.get_mut(REMOTE_ID.value_raw()).unwrap();
assert_eq!(remote_entity_mut.entity_id, REMOTE_ID.into());
let dummy = RemoteEntityConfig::new_with_default_values(
LOCAL_ID.into(),
@@ -1682,11 +1682,11 @@ pub(crate) mod tests {
remote_entity_cfg.add_config(&dummy).unwrap_err(),
RemoteConfigStoreError::Full
);
let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value_raw()).unwrap();
assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
// Does not exist.
assert!(remote_entity_cfg.get(LOCAL_ID.value()).is_none());
assert!(remote_entity_cfg.get_mut(LOCAL_ID.value()).is_none());
assert!(remote_entity_cfg.get(LOCAL_ID.value_raw()).is_none());
assert!(remote_entity_cfg.get_mut(LOCAL_ID.value_raw()).is_none());
}
#[test]
@@ -1711,20 +1711,20 @@ pub(crate) mod tests {
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value_raw()).unwrap();
assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
remote_cfg_provider
.add_config(&remote_entity_cfg_2)
.unwrap();
assert_eq!(remote_cfg_provider.0.len(), 2);
let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value_raw()).unwrap();
assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert!(remote_cfg_provider.remove_config(REMOTE_ID.value_raw()));
assert_eq!(remote_cfg_provider.0.len(), 1);
let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value_raw()).unwrap();
cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value_raw()));
assert!(remote_cfg_provider.get_mut(REMOTE_ID.value_raw()).is_none());
}
#[test]
@@ -1749,7 +1749,7 @@ pub(crate) mod tests {
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value_raw()).unwrap();
assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
assert!(
remote_cfg_provider
@@ -1757,14 +1757,14 @@ pub(crate) mod tests {
.unwrap()
);
assert_eq!(remote_cfg_provider.0.len(), 2);
let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value_raw()).unwrap();
assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert!(remote_cfg_provider.remove_config(REMOTE_ID.value_raw()));
assert_eq!(remote_cfg_provider.0.len(), 1);
let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value_raw()).unwrap();
cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value_raw()));
assert!(remote_cfg_provider.get_mut(REMOTE_ID.value_raw()).is_none());
}
#[test]

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
@@ -442,12 +492,9 @@ impl<
return Err(PutRequestError::AlreadyBusy);
}
self.put_request_cacher.set(put_request)?;
let remote_cfg = self.remote_cfg_table.get(
self.put_request_cacher
.static_fields
.destination_id
.value_const(),
);
let remote_cfg = self
.remote_cfg_table
.get(self.put_request_cacher.static_fields.destination_id.value());
if remote_cfg.is_none() {
return Err(PutRequestError::NoRemoteCfgFound(
self.put_request_cacher.static_fields.destination_id,
@@ -495,7 +542,7 @@ impl<
);
let create_id = |cached_id: &UnsignedByteField| {
if larger_entity_width != cached_id.size() {
UnsignedByteField::new(larger_entity_width, cached_id.value_const())
UnsignedByteField::new(larger_entity_width, cached_id.value())
} else {
*cached_id
}
@@ -558,22 +605,20 @@ impl<
.file_directive_type()
.expect("PDU directive type unexpectedly not set")
{
FileDirectiveType::FinishedPdu => {
FileDirectiveType::Finished => {
let finished_pdu = FinishedPduReader::new(packet_to_insert.raw_pdu())?;
self.handle_finished_pdu(&finished_pdu)?
}
FileDirectiveType::NakPdu => {
FileDirectiveType::Nak => {
let nak_pdu = NakPduReader::new(packet_to_insert.raw_pdu())?;
sent_packets += self.handle_nak_pdu(&nak_pdu)?;
}
FileDirectiveType::KeepAlivePdu => self.handle_keep_alive_pdu(),
FileDirectiveType::AckPdu => {
FileDirectiveType::KeepAlive => self.handle_keep_alive_pdu(),
FileDirectiveType::Ack => {
let ack_pdu = AckPdu::from_bytes(packet_to_insert.raw_pdu())?;
self.handle_ack_pdu(&ack_pdu)?
}
FileDirectiveType::EofPdu
| FileDirectiveType::PromptPdu
| FileDirectiveType::MetadataPdu => {
FileDirectiveType::Eof | FileDirectiveType::Prompt | FileDirectiveType::Metadata => {
return Err(SourceError::CantProcessPacketType {
pdu_type: packet_to_insert.pdu_type(),
directive_type: packet_to_insert.file_directive_type(),
@@ -898,7 +943,7 @@ impl<
) -> Result<(), SourceError> {
let ack_pdu = AckPdu::new(
PduHeader::new_for_file_directive(self.transaction_params.pdu_conf, 0),
FileDirectiveType::FinishedPdu,
FileDirectiveType::Finished,
condition_code,
transaction_status,
)
@@ -1093,7 +1138,7 @@ impl<
if self.step() != TransactionStep::WaitingForFinished {
return Err(SourceError::UnexpectedPdu {
pdu_type: PduType::FileDirective,
directive_type: Some(FileDirectiveType::FinishedPdu),
directive_type: Some(FileDirectiveType::Finished),
});
}
// Unwrapping should be fine here, the transfer state is valid when we are not in IDLE
@@ -1122,10 +1167,10 @@ impl<
// Drop the packet, wrong state to handle it..
return Err(SourceError::UnexpectedPdu {
pdu_type: PduType::FileDirective,
directive_type: Some(FileDirectiveType::AckPdu),
directive_type: Some(FileDirectiveType::Ack),
});
}
if ack_pdu.directive_code_of_acked_pdu() == FileDirectiveType::EofPdu {
if ack_pdu.directive_code_of_acked_pdu() == FileDirectiveType::Eof {
self.set_step(TransactionStep::WaitingForFinished);
} else {
self.anomalies.invalid_ack_directive_code =
@@ -1134,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,
@@ -1179,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.
@@ -1550,7 +1604,7 @@ mod tests {
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
next_pdu.file_directive_type,
Some(FileDirectiveType::MetadataPdu)
Some(FileDirectiveType::Metadata)
);
let metadata_pdu =
MetadataPduReader::new(&next_pdu.raw_pdu).expect("invalid metadata PDU format");
@@ -1599,7 +1653,7 @@ mod tests {
) {
let ack_pdu = AckPdu::new(
transaction_info.pdu_header,
FileDirectiveType::EofPdu,
FileDirectiveType::Eof,
ConditionCode::NoError,
TransactionStatus::Active,
)
@@ -1616,16 +1670,13 @@ mod tests {
let next_pdu = self.get_next_sent_pdu().unwrap();
assert!(self.pdu_queue_empty());
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
next_pdu.file_directive_type,
Some(FileDirectiveType::AckPdu)
);
assert_eq!(next_pdu.file_directive_type, Some(FileDirectiveType::Ack));
let ack_pdu = AckPdu::from_bytes(&next_pdu.raw_pdu).unwrap();
self.common_pdu_check_for_file_transfer(ack_pdu.pdu_header(), CrcFlag::NoCrc);
assert_eq!(ack_pdu.condition_code(), ConditionCode::NoError);
assert_eq!(
ack_pdu.directive_code_of_acked_pdu(),
FileDirectiveType::FinishedPdu
FileDirectiveType::Finished
);
assert_eq!(ack_pdu.transaction_status(), TransactionStatus::Active);
}
@@ -1639,10 +1690,7 @@ mod tests {
) {
let next_pdu = self.get_next_sent_pdu().unwrap();
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
assert_eq!(
next_pdu.file_directive_type,
Some(FileDirectiveType::EofPdu)
);
assert_eq!(next_pdu.file_directive_type, Some(FileDirectiveType::Eof));
let eof_pdu = EofPdu::from_bytes(&next_pdu.raw_pdu).expect("invalid EOF PDU format");
self.common_pdu_check_for_file_transfer(eof_pdu.pdu_header(), CrcFlag::NoCrc);
assert_eq!(eof_pdu.condition_code(), eof_params.condition_code);
@@ -1653,7 +1701,7 @@ mod tests {
.pdu_header()
.common_pdu_conf()
.transaction_seq_num
.value_const(),
.value(),
0
);
if self.transmission_mode == TransmissionMode::Unacknowledged {
@@ -2031,10 +2079,7 @@ mod tests {
let eof_pdu = tb
.get_next_sent_pdu()
.expect("no EOF PDU generated like expected");
assert_eq!(
eof_pdu.file_directive_type.unwrap(),
FileDirectiveType::EofPdu
);
assert_eq!(eof_pdu.file_directive_type.unwrap(), FileDirectiveType::Eof);
let eof_pdu = EofPdu::from_bytes(&eof_pdu.raw_pdu).unwrap();
assert_eq!(
eof_pdu.condition_code(),
@@ -2096,7 +2141,7 @@ mod tests {
assert_eq!(next_packet.pdu_type, PduType::FileDirective);
assert_eq!(
next_packet.file_directive_type.unwrap(),
FileDirectiveType::EofPdu
FileDirectiveType::Eof
);
// As specified in 4.11.2.2 of the standard, the file size will be the progress of the
// file copy operation so far, and the checksum is calculated for that progress.

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);
}