leodos_libcfs/cfe/es/cds.rs
1//! Safe, idiomatic wrappers for the CFE Critical Data Store (CDS) API.
2//!
3//! This module provides a generic, type-safe `CdsBlock<T>` for persisting
4//! application data across resets.
5
6use crate::cstring;
7use crate::error::Result;
8use crate::error::{CfsError, EsError, OsalError};
9use crate::ffi;
10use crate::status;
11use crate::status::check;
12use core::marker::PhantomData;
13use core::mem;
14use core::mem::MaybeUninit;
15use heapless::String;
16
17/// A type-safe, zero-cost wrapper for a cFE Critical Data Store handle.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(transparent)]
20pub struct CdsHandle(pub ffi::CFE_ES_CDSHandle_t);
21
22impl CdsHandle {
23 /// Retrieves the full name ("AppName.CDSName") for this CDS handle.
24 pub fn name(&self) -> Result<String<{ ffi::CFE_MISSION_ES_CDS_MAX_FULL_NAME_LEN as usize }>> {
25 let mut buffer = [0u8; ffi::CFE_MISSION_ES_CDS_MAX_FULL_NAME_LEN as usize];
26 check(unsafe {
27 ffi::CFE_ES_GetCDSBlockName(
28 buffer.as_mut_ptr() as *mut libc::c_char,
29 self.0,
30 buffer.len(),
31 )
32 })?;
33 let len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
34 let vec = heapless::Vec::from_slice(&buffer[..len])
35 .map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
36 let str = String::from_utf8(vec).map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
37 Ok(str)
38 }
39}
40
41/// Information about the status of a CDS block upon registration.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum CdsInfo {
44 /// The CDS block was newly created and its contents are uninitialized.
45 Created,
46 /// The CDS block already existed and its contents have been restored.
47 Restored,
48}
49
50/// A handle to a block of data in the cFE Critical Data Store.
51///
52/// This struct is generic over the data type `T` to be stored. The underlying
53/// CDS block is registered when `new` is called and persists for the life of the
54/// cFE instance (across resets).
55///
56/// The type `T` must be `Copy` and `Sized`, as the underlying CFE API performs
57/// a raw byte copy of the data.
58#[derive(Debug)]
59pub struct CdsBlock<T: Copy + Sized> {
60 handle: ffi::CFE_ES_CDSHandle_t,
61 _phantom: PhantomData<T>,
62}
63
64impl<T: Copy + Sized> CdsBlock<T> {
65 /// Registers a new CDS block with the given name or retrieves an existing one.
66 ///
67 /// This function will attempt to create a CDS block of `size_of::<T>()`.
68 /// The block contents are NOT cleared or initialized on creation.
69 ///
70 /// If a block with this name already existed but with a different size,
71 /// it is replaced. The new block contains uninitialized data and
72 /// `CdsInfo::Created` is returned (not `Restored`).
73 ///
74 /// # Return Value
75 ///
76 /// On success, returns `Ok((CdsBlock, CdsInfo))`. The `CdsInfo` indicates
77 /// whether the block was newly created or was restored from a previous run.
78 ///
79 /// - If `CdsInfo::Created`, the application is responsible for initializing
80 /// the data by calling `store()`.
81 /// - If `CdsInfo::Restored`, the application can immediately call `restore()`
82 /// to retrieve the previous state.
83 ///
84 /// # Arguments
85 /// * `name`: A unique, application-local name for the CDS block.
86 pub fn new(name: &str) -> Result<(Self, CdsInfo)> {
87 let c_name = cstring::<{ ffi::CFE_MISSION_ES_CDS_MAX_NAME_LENGTH as usize }>(name)
88 .map_err(|_| CfsError::Es(EsError::CdsInvalidName))?;
89
90 let mut handle = MaybeUninit::uninit();
91 let status = unsafe {
92 ffi::CFE_ES_RegisterCDS(handle.as_mut_ptr(), mem::size_of::<T>(), c_name.as_ptr())
93 };
94
95 // check() handles true errors. We need to handle the special success cases.
96 match check(status) {
97 Ok(status::Status::EsCdsAlreadyExists) => Ok((
98 Self {
99 handle: unsafe { handle.assume_init() },
100 _phantom: PhantomData,
101 },
102 CdsInfo::Restored,
103 )),
104 Ok(_) => {
105 // Any other Ok status, including Status::Success, means it was created.
106 Ok((
107 Self {
108 handle: unsafe { handle.assume_init() },
109 _phantom: PhantomData,
110 },
111 CdsInfo::Created,
112 ))
113 }
114 Err(e) => Err(e),
115 }
116 }
117
118 /// Stores a copy of `data` into the CDS block.
119 ///
120 /// This should be called after `new` reports `CdsInfo::Created`, or any
121 /// time the application wishes to update the persistent state.
122 pub fn store(&self, data: &T) -> Result<()> {
123 check(unsafe { ffi::CFE_ES_CopyToCDS(self.handle, data as *const T as *const _) })?;
124 Ok(())
125 }
126
127 /// Creates a CDS block and restores previous state, or uses
128 /// `T::default()` if the block is new or corrupted.
129 pub fn restore_or_default(name: &str) -> Result<(Self, T)>
130 where
131 T: Default,
132 {
133 let (cds, info) = Self::new(name)?;
134 let state = match info {
135 CdsInfo::Restored => cds.restore().unwrap_or_default(),
136 CdsInfo::Created => T::default(),
137 };
138 Ok((cds, state))
139 }
140
141 /// Restores the contents of the CDS block into a new instance of `T`.
142 ///
143 /// This will perform a raw byte copy from the CDS into the returned struct.
144 /// It is safe because `T` is constrained to be `Copy`.
145 ///
146 /// Returns `Err` if the data in the CDS has been corrupted
147 /// (e.g. CRC mismatch). The corrupted data is not returned.
148 pub fn restore(&self) -> Result<T> {
149 let mut data = MaybeUninit::<T>::uninit();
150 let status =
151 unsafe { ffi::CFE_ES_RestoreFromCDS(data.as_mut_ptr() as *mut _, self.handle) };
152 check(status)?;
153 Ok(unsafe { data.assume_init() })
154 }
155
156 /// Returns the underlying CDS handle.
157 pub fn handle(&self) -> CdsHandle {
158 CdsHandle(self.handle)
159 }
160
161 /// Finds an existing CDS Block ID by its full name ("AppName.CDSName").
162 pub fn get_id_by_name(name: &str) -> Result<CdsHandle> {
163 let c_name = cstring::<{ ffi::CFE_MISSION_ES_CDS_MAX_FULL_NAME_LEN as usize }>(name)?;
164
165 let mut handle = MaybeUninit::uninit();
166 check(unsafe { ffi::CFE_ES_GetCDSBlockIDByName(handle.as_mut_ptr(), c_name.as_ptr()) })?;
167 Ok(CdsHandle(unsafe { handle.assume_init() }))
168 }
169}