Skip to main content

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}