diff --git a/Cargo.lock b/Cargo.lock index 49a4bb1..f69ca73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,6 +95,15 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "ccsds_spacepacket" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82251e34d2bbc33273c38d7720b65373e55e311fa0c4134b60b03601dbc92cf2" +dependencies = [ + "deku 0.12.6", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -161,14 +170,38 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + [[package]] name = "darling" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.14.1", + "darling_macro 0.14.1", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", ] [[package]] @@ -185,17 +218,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn", +] + [[package]] name = "darling_macro" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ - "darling_core", + "darling_core 0.14.1", "quote", "syn", ] +[[package]] +name = "deku" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe18ce09f5d512cad19bbac22e395aa36cfa12cf699a3bcb3787af62dde4b13a" +dependencies = [ + "bitvec", + "deku_derive 0.12.6", +] + [[package]] name = "deku" version = "0.13.1" @@ -203,7 +257,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "868ccf23869582b2d79279e402db457fe341da24a0c5a8927c6154a5b4733dc3" dependencies = [ "bitvec", - "deku_derive", + "deku_derive 0.13.1", +] + +[[package]] +name = "deku_derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691ea0b164aad4a4517f5b47130e15868af1f02fed614c45a1967cbfe2c318d7" +dependencies = [ + "darling 0.13.4", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -212,7 +279,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dfb4274ccd1c87a598e98b398b4b630aecb2e8a32fd7c88ffa190fb73870f72" dependencies = [ - "darling", + "darling 0.14.1", "proc-macro-crate", "proc-macro2", "quote", @@ -284,7 +351,8 @@ name = "launchpad" version = "0.1.0" dependencies = [ "bus", - "deku", + "ccsds_spacepacket", + "deku 0.13.1", "heapless", "num", "postcard", diff --git a/Cargo.toml b/Cargo.toml index 014da40..e7002c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ thiserror = "1.0" bus = "2.2.3" num = "0.4" heapless = "0.7.13" -postcard = "0.7.3" +postcard = { version = "0.7.3", features = ["use-std"] } serde = "1.0.137" deku = "0.13" +ccsds_spacepacket = "0.2.0" diff --git a/src/core/pool.rs b/src/core/pool.rs index 67a4ad0..ea3047a 100644 --- a/src/core/pool.rs +++ b/src/core/pool.rs @@ -43,7 +43,7 @@ impl StoreAddr { #[derive(Debug, Clone, PartialEq)] pub enum StoreIdError { InvalidSubpool(u16), - InvalidPacketIdx(u16) + InvalidPacketIdx(u16), } #[derive(Debug, Clone, PartialEq)] @@ -129,10 +129,16 @@ impl LocalPool { fn addr_check(&self, addr: &StoreAddr) -> Result { let pool_idx = addr.pool_idx as usize; if pool_idx as usize >= self.pool_cfg.cfg.len() { - return Err(StoreError::InvalidStoreId(StoreIdError::InvalidSubpool(addr.pool_idx), Some(*addr))); + return Err(StoreError::InvalidStoreId( + StoreIdError::InvalidSubpool(addr.pool_idx), + Some(*addr), + )); } if addr.packet_idx >= self.pool_cfg.cfg[addr.pool_idx as usize].0 { - return Err(StoreError::InvalidStoreId(StoreIdError::InvalidPacketIdx(addr.packet_idx), Some(*addr))); + return Err(StoreError::InvalidStoreId( + StoreIdError::InvalidPacketIdx(addr.packet_idx), + Some(*addr), + )); } let size_list = self.sizes_lists.get(pool_idx).unwrap(); let curr_size = size_list[addr.packet_idx as usize]; @@ -190,7 +196,10 @@ impl LocalPool { } } } else { - return Err(StoreError::InvalidStoreId(StoreIdError::InvalidSubpool(subpool),None)); + return Err(StoreError::InvalidStoreId( + StoreIdError::InvalidSubpool(subpool), + None, + )); } Err(StoreError::StoreFull(subpool)) } @@ -230,12 +239,15 @@ mod tests { let pool_cfg = PoolCfg::new(vec![(4, 4), (2, 8), (1, 16)]); let mut local_pool = LocalPool::new(pool_cfg); // Try to access data which does not exist - let res = local_pool.read(&StoreAddr{ + let res = local_pool.read(&StoreAddr { packet_idx: 0, - pool_idx: 0 + pool_idx: 0, }); assert!(res.is_err()); - assert!(matches!(res.unwrap_err(), StoreError::DataDoesNotExist { .. })); + assert!(matches!( + res.unwrap_err(), + StoreError::DataDoesNotExist { .. } + )); let mut test_buf: [u8; 16] = [0; 16]; for (i, val) in test_buf.iter_mut().enumerate() { *val = i as u8; @@ -244,16 +256,19 @@ mod tests { assert!(res.is_ok()); let addr = res.unwrap(); // Only the second subpool has enough storage and only one bucket - assert_eq!(addr, StoreAddr { - pool_idx: 2, - packet_idx: 0 - }); + assert_eq!( + addr, + StoreAddr { + pool_idx: 2, + packet_idx: 0 + } + ); // The subpool is now full and the call should fail accordingly let res = local_pool.add(test_buf.as_slice()); assert!(res.is_err()); let err = res.unwrap_err(); - assert!(matches!(err, StoreError::StoreFull {..})); + assert!(matches!(err, StoreError::StoreFull { .. })); if let StoreError::StoreFull(subpool) = err { assert_eq!(subpool, 2); } @@ -264,7 +279,7 @@ mod tests { let buf_read_back = res.unwrap(); assert_eq!(buf_read_back.len(), 16); for (i, &val) in buf_read_back.iter().enumerate() { - assert_eq!(val , i as u8); + assert_eq!(val, i as u8); } // Delete the data @@ -276,7 +291,13 @@ mod tests { let res = local_pool.free_element(12); assert!(res.is_ok()); let (addr, buf_ref) = res.unwrap(); - assert_eq!(addr, StoreAddr {pool_idx: 2, packet_idx:0}); + assert_eq!( + addr, + StoreAddr { + pool_idx: 2, + packet_idx: 0 + } + ); assert_eq!(buf_ref.len(), 12); assert_eq!(buf_ref, [0; 12]); buf_ref[0] = 5; @@ -300,20 +321,32 @@ mod tests { } { - let addr = StoreAddr{pool_idx: 3, packet_idx: 0}; + let addr = StoreAddr { + pool_idx: 3, + packet_idx: 0, + }; let res = local_pool.read(&addr); assert!(res.is_err()); let err = res.unwrap_err(); - assert!(matches!(err, StoreError::InvalidStoreId(StoreIdError::InvalidSubpool(3), Some(_)))); + assert!(matches!( + err, + StoreError::InvalidStoreId(StoreIdError::InvalidSubpool(3), Some(_)) + )); } { - let addr = StoreAddr{pool_idx: 2, packet_idx: 1}; + let addr = StoreAddr { + pool_idx: 2, + packet_idx: 1, + }; assert_eq!(addr.raw(), 0x00020001); let res = local_pool.read(&addr); assert!(res.is_err()); let err = res.unwrap_err(); - assert!(matches!(err, StoreError::InvalidStoreId(StoreIdError::InvalidPacketIdx(1), Some(_)))); + assert!(matches!( + err, + StoreError::InvalidStoreId(StoreIdError::InvalidPacketIdx(1), Some(_)) + )); let data_too_large = [0; 20]; let res = local_pool.add(data_too_large.as_slice()); @@ -323,7 +356,10 @@ mod tests { let res = local_pool.free_element(LocalPool::MAX_SIZE + 1); assert!(res.is_err()); - assert_eq!(res.unwrap_err(), StoreError::DataTooLarge(LocalPool::MAX_SIZE + 1)); + assert_eq!( + res.unwrap_err(), + StoreError::DataTooLarge(LocalPool::MAX_SIZE + 1) + ); } { diff --git a/src/lib.rs b/src/lib.rs index 737935d..9ebb096 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,2 @@ pub mod core; -pub mod sp; \ No newline at end of file +pub mod sp; diff --git a/src/main.rs b/src/main.rs index 9313ef0..7e28bde 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use core::ops::Deref; -use serde::{Serialize, Deserialize}; -use postcard::{from_bytes, to_vec}; use heapless::Vec; +use postcard::{from_bytes, to_vec}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] struct RefStruct<'a> { @@ -9,14 +9,14 @@ struct RefStruct<'a> { str_s: &'a str, } - fn main() { let message = "hElLo"; let bytes = [0x01, 0x10, 0x02, 0x20]; let output: Vec = to_vec(&RefStruct { bytes: &bytes, str_s: message, - }).unwrap(); + }) + .unwrap(); assert_eq!( &[0x04, 0x01, 0x10, 0x02, 0x20, 0x05, b'h', b'E', b'l', b'L', b'o',], diff --git a/src/sp.rs b/src/sp.rs index cab55a4..0a7e3c5 100644 --- a/src/sp.rs +++ b/src/sp.rs @@ -1,16 +1,212 @@ //! # Space related components including CCSDS and ECSS packet standards -use serde::{Serialize, Deserialize}; +use ccsds_spacepacket::PrimaryHeader; +pub use ccsds_spacepacket::PrimaryHeader as DekuSpHeader; +use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] pub enum PacketType { Tm = 0, - Tc = 1 + Tc = 1, } -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] -struct SpHeader { +impl TryFrom for PacketType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + x if x == PacketType::Tm as u8 => Ok(PacketType::Tm), + x if x == PacketType::Tc as u8 => Ok(PacketType::Tc), + _ => Err(()), + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] +pub enum SequenceFlags { + ContinuationSegment = 0b00, + FirstSegment = 0b01, + LastSegment = 0b10, + Unsegmented = 0b11, +} + +impl TryFrom for SequenceFlags { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + x if x == SequenceFlags::ContinuationSegment as u8 => { + Ok(SequenceFlags::ContinuationSegment) + } + x if x == SequenceFlags::FirstSegment as u8 => Ok(SequenceFlags::FirstSegment), + x if x == SequenceFlags::LastSegment as u8 => Ok(SequenceFlags::LastSegment), + x if x == SequenceFlags::Unsegmented as u8 => Ok(SequenceFlags::Unsegmented), + _ => Err(()), + } + } +} + +/// Space Packet Primary Header according to CCSDS 133.0-B-2 +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct SpHeader { pub version: u8, pub ptype: PacketType, pub apid: u16, - pub secondary_header_flag: u8, + pub secondary_header_flag: bool, + pub sequence_flags: SequenceFlags, pub ssc: u16, -} \ No newline at end of file + pub packet_data_len: u16, +} + +impl Default for SpHeader { + fn default() -> Self { + SpHeader { + version: 0, + ptype: PacketType::Tm, + apid: 0, + secondary_header_flag: true, + sequence_flags: SequenceFlags::Unsegmented, + ssc: 0, + packet_data_len: 0, + } + } +} +impl SpHeader { + pub fn new(apid: u16, ptype: PacketType, ssc: u16) -> Option { + if ssc > num::pow(2, 14) || apid > num::pow(2, 11) { + return None; + } + let mut header = SpHeader::default(); + header.ptype = ptype; + header.apid = apid; + header.ssc = ssc; + Some(header) + } + + pub fn tm(apid: u16, ssc: u16) -> Option { + Self::new(apid, PacketType::Tm, ssc) + } + + pub fn tc(apid: u16, ssc: u16) -> Option { + Self::new(apid, PacketType::Tc, ssc) + } + + /// Function to retrieve the packet sequence control field + #[inline] + pub fn psc(&self) -> u16 { + ((self.sequence_flags as u16) << 14) | self.secondary_header_flag as u16 + } + + /// Retrieve Packet Identification composite field + #[inline] + pub fn packet_id(&self) -> u16 { + ((self.ptype as u16) << 13) | ((self.secondary_header_flag as u16) << 12) | self.apid + } + + #[inline] + pub fn is_tm(&self) -> bool { + self.ptype == PacketType::Tm + } + + #[inline] + pub fn is_tc(&self) -> bool { + self.ptype == PacketType::Tc + } +} + +/// The [DekuSpHeader] is very useful to deserialize a packed raw space packet header with 6 bytes. +/// This function allows converting it to the [SpHeader] which is compatible to the [serde] +/// framework +impl TryFrom for SpHeader { + type Error = (); + + fn try_from(header: PrimaryHeader) -> Result { + let seq_num = SequenceFlags::try_from(header.sequence_flags as u8)?; + let packet_type = PacketType::try_from(header.packet_type as u8)?; + let sec_header_flag = header.sec_header_flag as u8 != 0; + Ok(SpHeader { + version: header.version, + sequence_flags: seq_num, + packet_data_len: header.data_length, + ssc: header.sequence_count, + ptype: packet_type, + apid: header.app_proc_id, + secondary_header_flag: sec_header_flag, + }) + } +} + +/// It is possible to convert the [serde] compatible [SpHeader] back into a [DekuSpHeader] +/// to allow for packed binary serialization +impl TryFrom for DekuSpHeader { + type Error = (); + + fn try_from(value: SpHeader) -> Result { + use ccsds_spacepacket::types::PacketType as DekuPacketType; + use ccsds_spacepacket::types::SecondaryHeaderFlag as DekuSecHeaderFlag; + use ccsds_spacepacket::types::SeqFlag as DekuSeqFlag; + let sequence_flags = match value.sequence_flags as u8 { + x if x == SequenceFlags::Unsegmented as u8 => DekuSeqFlag::Unsegmented, + x if x == SequenceFlags::FirstSegment as u8 => DekuSeqFlag::FirstSegment, + x if x == SequenceFlags::LastSegment as u8 => DekuSeqFlag::LastSegment, + x if x == SequenceFlags::ContinuationSegment as u8 => DekuSeqFlag::Continuation, + _ => return Err(()), + }; + let packet_type = match value.ptype as u8 { + x if x == PacketType::Tm as u8 => DekuPacketType::Data, + x if x == PacketType::Tc as u8 => DekuPacketType::Command, + _ => return Err(()), + }; + let sec_header_flag = match value.secondary_header_flag as bool { + true => DekuSecHeaderFlag::Present, + false => DekuSecHeaderFlag::NotPresent, + }; + Ok(DekuSpHeader { + version: value.version, + packet_type, + sec_header_flag, + app_proc_id: value.apid, + sequence_flags, + data_length: value.packet_data_len, + sequence_count: value.ssc, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::sp::{DekuSpHeader, PacketType, SequenceFlags, SpHeader}; + use deku::prelude::*; + use postcard::{from_bytes, to_stdvec}; + + #[test] + fn test_deser_internally() { + let sp_header = SpHeader::tc(0x42, 12).expect("Error creating SP header"); + assert_eq!(sp_header.version, 0b000); + assert_eq!(sp_header.secondary_header_flag, true); + assert_eq!(sp_header.ptype, PacketType::Tc); + assert_eq!(sp_header.ssc, 12); + assert_eq!(sp_header.apid, 0x42); + assert_eq!(sp_header.sequence_flags, SequenceFlags::Unsegmented); + assert_eq!(sp_header.packet_data_len, 0); + let output = to_stdvec(&sp_header).unwrap(); + println!("Output: {:?} with length {}", output, output.len()); + let sp_header: SpHeader = from_bytes(&output).unwrap(); + assert_eq!(sp_header.version, 0b000); + assert_eq!(sp_header.secondary_header_flag, true); + assert_eq!(sp_header.ptype, PacketType::Tc); + assert_eq!(sp_header.ssc, 12); + assert_eq!(sp_header.apid, 0x42); + assert_eq!(sp_header.sequence_flags, SequenceFlags::Unsegmented); + assert_eq!(sp_header.packet_data_len, 0); + } + + #[test] + fn test_deser_to_raw_packed_deku() { + let sp_header = SpHeader::tc(0x42, 12).expect("Error creating SP header"); + // TODO: Wait with these tests until KubOS merged + // https://github.com/KubOS-Preservation-Group/ccsds-spacepacket/pull/14 + let _deku_header = + DekuSpHeader::try_from(sp_header).expect("Error creating Deku Sp Header"); + // deku_header.to_bytes().unwrap(); + } +}