Skip to main content

leodos_libcfs/cfe/
tbl.rs

1//! TBL (Table Services) interface.
2
3use crate::cfe::sb::msg::MsgId;
4use crate::cfe::time::SysTime;
5use crate::cstring;
6use crate::error::{CfsError, Result, TblError};
7use crate::status::check;
8use crate::{ffi, status};
9use core::ffi::c_void;
10use core::marker::PhantomData;
11use core::mem::{size_of, MaybeUninit};
12use core::ops::{Deref, Drop};
13
14/// A type alias for the raw callback function used to validate table loads.
15pub type ValidationFn = ffi::CFE_TBL_CallbackFuncPtr_t;
16
17/// Table validation trait. Implement on your config struct
18/// to reject invalid ground loads.
19///
20/// The default implementation accepts all loads.
21pub trait Validate {
22    /// Returns `true` if this configuration is valid.
23    fn validate(&self) -> bool {
24        true
25    }
26}
27
28extern "C" fn validate_trampoline<T: Validate>(ptr: *mut c_void) -> i32 {
29    let t = unsafe { &*(ptr as *const T) };
30    if t.validate() { 0 } else { -1 }
31}
32
33/// A handle to a cFE table.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[repr(transparent)]
36pub struct TableHandle(pub ffi::CFE_TBL_Handle_t);
37
38/// A handle to a cFE table, generic over the table's data type `T`.
39///
40/// This struct handles registration and unregistration of the table, providing
41/// a safe, RAII-based interface.
42#[derive(Debug)]
43pub struct Table<T: Sized> {
44    handle: TableHandle,
45    is_owner: bool,
46    _phantom: PhantomData<T>,
47}
48
49use bitflags::bitflags;
50
51bitflags! {
52    /// Options for table registration.
53    pub struct TableOptions: u16 {
54        /// Default table options (single-buffered, load/dump enabled).
55        const DEFAULT         = ffi::CFE_TBL_OPT_DEFAULT as u16;
56        /// The table will use a single buffer. Updates are copied from a shared working buffer.
57        const SINGLE_BUFFERED = ffi::CFE_TBL_OPT_SNGL_BUFFER as u16;
58        /// The table will have two dedicated buffers (active and inactive) for faster updates.
59        const DOUBLE_BUFFERED = ffi::CFE_TBL_OPT_DBL_BUFFER as u16;
60        /// The table's contents can be dumped but not loaded via Table Services commands.
61        const DUMP_ONLY       = ffi::CFE_TBL_OPT_DUMP_ONLY as u16;
62        /// The table is critical and its contents will be preserved in the Critical Data Store (CDS).
63        const CRITICAL        = ffi::CFE_TBL_OPT_CRITICAL as u16;
64    }
65}
66
67/// Information about a cFE table.
68pub struct TableInfo(pub(crate) ffi::CFE_TBL_Info_t);
69
70impl TableInfo {
71    /// Returns the size of the table in bytes.
72    pub fn size(&self) -> usize {
73        self.0.Size as usize
74    }
75
76    /// Returns the number of applications with access to the table.
77    pub fn num_users(&self) -> u32 {
78        self.0.NumUsers
79    }
80
81    /// Returns the file creation time from the last file loaded into the table.
82    #[cfg(not(nos3_cfe))]
83    pub fn file_time(&self) -> SysTime {
84        SysTime(self.0.FileTime)
85    }
86
87    /// Returns the most recently calculated CRC of the table contents.
88    pub fn crc(&self) -> u32 {
89        self.0.Crc
90    }
91
92    /// Returns the time when the table was last updated.
93    pub fn time_of_last_update(&self) -> SysTime {
94        SysTime(self.0.TimeOfLastUpdate)
95    }
96
97    /// Returns whether the table has been loaded at least once.
98    pub fn table_loaded_once(&self) -> bool {
99        self.0.TableLoadedOnce
100    }
101
102    /// Returns whether the table is marked as Dump Only.
103    pub fn dump_only(&self) -> bool {
104        self.0.DumpOnly
105    }
106
107    /// Returns whether the table is double-buffered.
108    pub fn double_buffered(&self) -> bool {
109        self.0.DoubleBuffered
110    }
111
112    /// Returns whether the table address was defined by the owner application.
113    pub fn user_def_addr(&self) -> bool {
114        self.0.UserDefAddr
115    }
116
117    /// Returns whether the table is critical (backed by the CDS).
118    pub fn critical(&self) -> bool {
119        self.0.Critical
120    }
121
122    /// Returns the filename of the last file loaded into the table.
123    pub fn last_file_loaded(&self) -> &str {
124        let len = self
125            .0
126            .LastFileLoaded
127            .iter()
128            .position(|&c| c == 0)
129            .unwrap_or(self.0.LastFileLoaded.len());
130        let bytes = &self.0.LastFileLoaded[..len];
131        let u8slice = unsafe { core::slice::from_raw_parts(bytes.as_ptr() as *const u8, len) };
132        core::str::from_utf8(u8slice).unwrap_or("")
133    }
134}
135
136impl<T: Sized> Table<T> {
137    /// Registers a new table with cFE Table Services.
138    ///
139    /// If `T` implements [`Validate`] with custom logic,
140    /// cFE will call it before activating any ground load.
141    /// The default `Validate` impl accepts all loads.
142    pub fn new(name: &str, options: TableOptions) -> Result<Self>
143    where
144        T: Default + Validate,
145    {
146        let mut handle = MaybeUninit::uninit();
147        let c_name = cstring::<{ ffi::CFE_MISSION_TBL_MAX_NAME_LENGTH as usize }>(name)
148            .map_err(|_| CfsError::Tbl(TblError::InvalidName))?;
149
150        let status = unsafe {
151            ffi::CFE_TBL_Register(
152                handle.as_mut_ptr(),
153                c_name.as_ptr(),
154                size_of::<T>(),
155                options.bits(),
156                Some(validate_trampoline::<T> as _),
157            )
158        };
159        check(status)?;
160
161        let table = Self {
162            handle: TableHandle(unsafe { handle.assume_init() }),
163            is_owner: true,
164            _phantom: PhantomData,
165        };
166
167        let default = T::default();
168        table.load_from_slice(core::slice::from_ref(&default))?;
169
170        Ok(table)
171    }
172
173    /// Obtains a handle to a table registered by another application.
174    ///
175    /// This does not take ownership of the table. When this `Table` instance is dropped,
176    /// it only releases the shared handle; it does not unregister the table itself.
177    ///
178    /// # Arguments
179    /// * `name`: The full name of the table, in the format "AppName.TableName".
180    pub fn share(name: &str) -> Result<Self> {
181        let mut handle = MaybeUninit::uninit();
182        let c_name = cstring::<{ ffi::CFE_MISSION_TBL_MAX_FULL_NAME_LEN as usize }>(name)
183            .map_err(|_| CfsError::Tbl(TblError::InvalidName))?;
184
185        let status = unsafe { ffi::CFE_TBL_Share(handle.as_mut_ptr(), c_name.as_ptr()) };
186        check(status)?;
187
188        Ok(Self {
189            handle: TableHandle(unsafe { handle.assume_init() }),
190            is_owner: false,
191            _phantom: PhantomData,
192        })
193    }
194
195    /// Loads data into the table from a file.
196    ///
197    /// This call can block. Must not be called from ISR context.
198    pub fn load_from_file(&self, filename: &str) -> Result<()> {
199        let c_filename = cstring::<{ ffi::OS_MAX_PATH_LEN as usize }>(filename)
200            .map_err(|_| CfsError::Tbl(TblError::FilenameTooLong))?;
201        let status = unsafe {
202            ffi::CFE_TBL_Load(
203                self.handle.0,
204                ffi::CFE_TBL_SrcEnum_CFE_TBL_SRC_FILE,
205                c_filename.as_ptr() as *const _,
206            )
207        };
208        check(status)?;
209        Ok(())
210    }
211
212    /// Loads data into the table from a memory slice.
213    ///
214    /// This call can block. Must not be called from ISR context.
215    pub fn load_from_slice(&self, data: &[T]) -> Result<()> {
216        let status = unsafe {
217            ffi::CFE_TBL_Load(
218                self.handle.0,
219                ffi::CFE_TBL_SrcEnum_CFE_TBL_SRC_ADDRESS,
220                data.as_ptr() as *const _,
221            )
222        };
223        check(status)?;
224        Ok(())
225    }
226
227    /// Performs periodic processing for the table (update, validate, dump).
228    /// This should be called once per application cycle for each owned table.
229    pub fn manage(&self) -> Result<()> {
230        check(unsafe { ffi::CFE_TBL_Manage(self.handle.0) })?;
231        Ok(())
232    }
233
234    /// Notifies Table Services that the application has modified the table's contents.
235    /// This is important for critical tables backed by the CDS.
236    pub fn modified(&self) -> Result<()> {
237        check(unsafe { ffi::CFE_TBL_Modified(self.handle.0) })?;
238        Ok(())
239    }
240
241    /// Gets a read-only accessor to the table's data.
242    /// The accessor locks the table and automatically releases it when dropped.
243    pub fn get(&self) -> Result<TableAccessor<'_, T>> {
244        TableAccessor::new(self.handle)
245    }
246
247    /// Returns the table data, or `T::default()` if the
248    /// table is not currently accessible.
249    pub fn get_or_default(&self) -> T
250    where
251        T: Default + Copy,
252    {
253        self.get().map(|a| *a).unwrap_or_default()
254    }
255
256    /// Returns the underlying `TableHandle`.
257    pub fn handle(&self) -> TableHandle {
258        self.handle
259    }
260
261    /// Obtains characteristics and information about a specified table by name.
262    ///
263    /// # Arguments
264    /// * `name`: The full name of the table, in the format "AppName.TableName".
265    pub fn get_info(name: &str) -> Result<TableInfo> {
266        let c_name = cstring::<{ ffi::CFE_MISSION_TBL_MAX_FULL_NAME_LEN as usize }>(name)
267            .map_err(|_| CfsError::Tbl(TblError::InvalidName))?;
268
269        let mut tbl_info_uninit = MaybeUninit::uninit();
270
271        check(unsafe { ffi::CFE_TBL_GetInfo(tbl_info_uninit.as_mut_ptr(), c_name.as_ptr()) })?;
272
273        Ok(TableInfo(unsafe { tbl_info_uninit.assume_init() }))
274    }
275
276    /// Obtains the current status of pending actions for a table.
277    pub fn status(&self) -> Result<status::Status> {
278        check(unsafe { ffi::CFE_TBL_GetStatus(self.handle.0) })
279    }
280
281    /// Updates the contents of the table if an update is pending.
282    pub fn update(&self) -> Result<()> {
283        check(unsafe { ffi::CFE_TBL_Update(self.handle.0) })?;
284        Ok(())
285    }
286
287    /// Validates the contents of a table if a validation is pending.
288    pub fn validate(&self) -> Result<()> {
289        check(unsafe { ffi::CFE_TBL_Validate(self.handle.0) })?;
290        Ok(())
291    }
292
293    /// Copies the contents of a Dump Only Table to a shared buffer.
294    ///
295    /// This should only be called by the table owner in response to a dump request,
296    /// typically after `manage()` returns `Ok(TblInfoDumpPending)`.
297    pub fn dump_to_buffer(&self) -> Result<()> {
298        check(unsafe { ffi::CFE_TBL_DumpToBuffer(self.handle.0) })?;
299        Ok(())
300    }
301
302    /// Instructs Table Services to send a message when this table requires management.
303    ///
304    /// This allows an application to be event-driven for table maintenance instead of
305    /// polling with `manage()`.
306    ///
307    /// # Arguments
308    /// * `msg_id`: Message ID to be used in the notification message.
309    /// * `command_code`: Command code to be placed in the secondary header.
310    /// * `parameter`: Application-defined value to be passed as a parameter in the message.
311    pub fn notify_by_message(
312        &self,
313        msg_id: MsgId,
314        command_code: u16,
315        parameter: u32,
316    ) -> Result<()> {
317        check(unsafe {
318            ffi::CFE_TBL_NotifyByMessage(self.handle.0, msg_id.0, command_code, parameter)
319        })?;
320        Ok(())
321    }
322
323    /// Gets read-only accessors for multiple tables at once.
324    ///
325    /// # Safety
326    /// The caller must ensure that the types `U` in the returned accessors match
327    /// the actual types of the tables identified by the handles.
328    pub unsafe fn get_accessors<const N: usize>(
329        handles: [TableHandle; N],
330    ) -> Result<[TableAccessor<'static, ()>; N]> {
331        let mut ptrs: [*mut c_void; N] = [core::ptr::null_mut(); N];
332        let mut ptr_ptrs: [*mut *mut c_void; N] = [core::ptr::null_mut(); N];
333        for i in 0..N {
334            ptr_ptrs[i] = &raw mut ptrs[i];
335        }
336        check(ffi::CFE_TBL_GetAddresses(
337            ptr_ptrs.as_mut_ptr(),
338            N as u16,
339            handles.as_ptr() as *const _,
340        ))?;
341
342        let mut accessors: [MaybeUninit<TableAccessor<'static, ()>>; N] =
343            MaybeUninit::uninit().assume_init();
344
345        for i in 0..N {
346            accessors[i].write(TableAccessor {
347                ptr: ptrs[i] as *const (),
348                handle: handles[i],
349                _phantom: PhantomData,
350            });
351        }
352
353        Ok(accessors.map(|a| a.assume_init()))
354    }
355}
356
357impl<T: Sized> Drop for Table<T> {
358    /// Unregisters the table if this instance is the owner.
359    fn drop(&mut self) {
360        // Only the original registrant should unregister the table.
361        // Shared handles are simply released without unregistering.
362        if self.is_owner {
363            let _ = unsafe { ffi::CFE_TBL_Unregister(self.handle.0) };
364        }
365    }
366}
367
368/// A safe RAII wrapper for accessing a CFE table's memory.
369///
370/// It acquires the table pointer on creation and automatically releases it when dropped.
371#[derive(Debug)]
372pub struct TableAccessor<'a, T: 'a> {
373    ptr: *const T,
374    handle: TableHandle,
375    _phantom: PhantomData<&'a T>,
376}
377
378impl<'a, T> TableAccessor<'a, T> {
379    /// Acquires a pointer to the table data.
380    ///
381    /// Can block on shared single-buffered tables. The address must
382    /// be released (by dropping the accessor) before calling
383    /// [`Table::update`] or any blocking call (e.g. pending on SB).
384    ///
385    /// Returns a zeroed table pointer if the table has never been
386    /// loaded.
387    pub fn new(handle: TableHandle) -> Result<Self> {
388        let mut ptr = core::ptr::null_mut();
389        let status = unsafe { ffi::CFE_TBL_GetAddress(&mut ptr, handle.0) };
390        // CFE_TBL_INFO_UPDATED is a success code, but indicates a change.
391        if status != ffi::CFE_SUCCESS && status != ffi::CFE_TBL_INFO_UPDATED {
392            return Err(CfsError::from(status));
393        }
394
395        Ok(Self {
396            ptr: ptr as *const T,
397            handle,
398            _phantom: PhantomData,
399        })
400    }
401}
402
403impl<'a, T> Deref for TableAccessor<'a, T> {
404    type Target = T;
405    fn deref(&self) -> &Self::Target {
406        unsafe { &*self.ptr }
407    }
408}
409
410impl<'a, T> Drop for TableAccessor<'a, T> {
411    fn drop(&mut self) {
412        // Automatically release the address when the accessor goes out of scope.
413        let _ = unsafe { ffi::CFE_TBL_ReleaseAddress(self.handle.0) };
414    }
415}