Skip to main content

leodos_libcfs/cfe/es/
app.rs

1//! Application management.
2//!
3//! Provides `AppId`, `AppInfo`, `RunStatus`, and
4//! `default_panic_handler`.
5
6use core::ffi::CStr;
7use core::mem::MaybeUninit;
8use core::ops::Deref;
9use core::str;
10
11use heapless::CString;
12use heapless::String;
13
14use crate::cstring;
15use crate::error::Result;
16use crate::error::{CfsError, OsalError};
17use crate::ffi;
18use crate::log;
19use crate::status::check;
20
21/// Represents the possible run statuses returned by `CFE_ES_RunLoop`.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(u32)]
24pub enum RunStatus {
25    /// The application run status is undefined.
26    Undefined = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_UNDEFINED,
27    /// The application should continue running.
28    Run = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_APP_RUN,
29    /// The application should exit gracefully.
30    Exit = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_APP_EXIT,
31    /// An error occurred; the application should handle it appropriately.
32    Error = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_APP_ERROR,
33    /// The application encountered a system exception.
34    Exception = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_SYS_EXCEPTION,
35    /// The application should be restarted by the system.
36    Restart = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_SYS_RESTART,
37    /// The application should be reloaded by the system.
38    Reload = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_SYS_RELOAD,
39    /// The application should be deleted by the system.
40    Delete = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_SYS_DELETE,
41    /// The core application failed to initialize.
42    CoreAppInitError = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_CORE_APP_INIT_ERROR,
43    /// The core application encountered a runtime error.
44    CoreAppRuntimeError = ffi::CFE_ES_RunStatus_CFE_ES_RunStatus_CORE_APP_RUNTIME_ERROR,
45}
46
47impl From<u32> for RunStatus {
48    fn from(value: u32) -> Self {
49        match value {
50            x if x == RunStatus::Undefined as u32 => RunStatus::Undefined,
51            x if x == RunStatus::Run as u32 => RunStatus::Run,
52            x if x == RunStatus::Exit as u32 => RunStatus::Exit,
53            x if x == RunStatus::Error as u32 => RunStatus::Error,
54            x if x == RunStatus::Exception as u32 => RunStatus::Exception,
55            x if x == RunStatus::Restart as u32 => RunStatus::Restart,
56            x if x == RunStatus::Reload as u32 => RunStatus::Reload,
57            x if x == RunStatus::Delete as u32 => RunStatus::Delete,
58            x if x == RunStatus::CoreAppInitError as u32 => RunStatus::CoreAppInitError,
59            x if x == RunStatus::CoreAppRuntimeError as u32 => RunStatus::CoreAppRuntimeError,
60            _ => RunStatus::Undefined, // Default case
61        }
62    }
63}
64
65/// Checks whether the application should continue running.
66///
67/// Returns `Ok(())` if the app should keep running, or
68/// `Err(status)` with the reason if cFS has commanded
69/// the app to stop.
70pub fn run_loop() -> core::result::Result<(), RunStatus> {
71    let mut status = RunStatus::Run as u32;
72    let should_run = unsafe { ffi::CFE_ES_RunLoop(&mut status) };
73    let status = RunStatus::from(status);
74    if should_run && status == RunStatus::Run {
75        Ok(())
76    } else {
77        Err(status)
78    }
79}
80
81/// Exits the application with the given status.
82pub fn exit_app(status: RunStatus) -> ! {
83    unsafe { ffi::CFE_ES_ExitApp(status as u32) };
84    loop {}
85}
86
87/// A type-safe, zero-cost wrapper for a cFE Application ID.
88///
89/// This is a lightweight, `Copy`-able handle that represents a unique application.
90/// It can be used to query information about that specific application.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[repr(transparent)]
93pub struct AppId(pub(crate) ffi::CFE_ES_AppId_t);
94
95impl AppId {
96    /// Retrieves detailed information about the application with this ID.
97    ///
98    /// # Errors
99    ///
100    /// Returns an error if the App ID is not valid or if the underlying
101    /// CFE call fails.
102    pub fn info(&self) -> Result<AppInfo> {
103        let mut app_info_uninit = MaybeUninit::uninit();
104        let status = unsafe { ffi::CFE_ES_GetAppInfo(app_info_uninit.as_mut_ptr(), self.0) };
105        check(status)?;
106        Ok(AppInfo {
107            inner: unsafe { app_info_uninit.assume_init() },
108        })
109    }
110
111    /// Retrieves the name for this application ID.
112    ///
113    /// # Errors
114    ///
115    /// Returns an error if the App ID is not valid, the buffer is too small,
116    /// or the name is not valid UTF-8.
117    pub fn name(&self) -> Result<String<{ ffi::OS_MAX_API_NAME as usize }>> {
118        let mut buffer = [0u8; ffi::OS_MAX_API_NAME as usize];
119        let status = unsafe {
120            ffi::CFE_ES_GetAppName(
121                buffer.as_mut_ptr() as *mut libc::c_char,
122                self.0,
123                buffer.len(),
124            )
125        };
126        check(status)?;
127        let len = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
128        let vec = heapless::Vec::from_slice(&buffer[..len])
129            .map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
130        String::from_utf8(vec).map_err(|_| CfsError::InvalidString)
131    }
132
133    /// Converts the App ID into a zero-based integer suitable for array indexing.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error if the App ID is not valid or if the underlying
138    /// CFE call fails.
139    pub fn to_index(&self) -> Result<u32> {
140        let mut index = MaybeUninit::uninit();
141        check(unsafe { ffi::CFE_ES_AppID_ToIndex(self.0, index.as_mut_ptr()) })?;
142        Ok(unsafe { index.assume_init() })
143    }
144
145    /// Requests cFE to restart this application.
146    ///
147    /// If the application file is missing or corrupt at restart time,
148    /// the application may be permanently deleted and unrecoverable
149    /// except via the `ES_STARTAPP` ground command.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the `app_id` is invalid or if the restart command fails.
154    pub fn restart(&self) -> Result<()> {
155        check(unsafe { ffi::CFE_ES_RestartApp(self.0) })?;
156        Ok(())
157    }
158
159    /// Requests cFE to reload this application from a new file.
160    ///
161    /// If the file is missing or corrupt, the application may be
162    /// permanently deleted and unrecoverable except via the
163    /// `ES_STARTAPP` ground command.
164    ///
165    /// # Arguments
166    /// * `filename`: The path to the new application binary file.
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if the `app_id` is invalid, the filename is invalid,
171    /// the file cannot be accessed, or the reload command fails.
172    pub fn reload(&self, filename: &str) -> Result<()> {
173        let c_filename = cstring::<{ ffi::OS_MAX_PATH_LEN as usize }>(filename)
174            .map_err(|_| CfsError::Osal(OsalError::FsPathTooLong))?;
175        check(unsafe { ffi::CFE_ES_ReloadApp(self.0, c_filename.as_ptr()) })?;
176        Ok(())
177    }
178
179    /// Requests cFE to delete this application.
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if the `app_id` is invalid or if the delete command fails.
184    pub fn delete(&self) -> Result<()> {
185        check(unsafe { ffi::CFE_ES_DeleteApp(self.0) })?;
186        Ok(())
187    }
188
189    /// Retrieves the cFE Application ID for a given application name.
190    ///
191    /// # Arguments
192    /// * `name`: The registered name of the application to look up.
193    ///
194    /// # Errors
195    ///
196    /// Returns an error if no application with the given name is found, or if the
197    /// name is too long for the internal CFE buffers.
198    pub fn from_name(name: &str) -> Result<AppId> {
199        let c_name = cstring::<{ ffi::OS_MAX_API_NAME as usize }>(name)?;
200
201        let mut app_id = MaybeUninit::uninit();
202        check(unsafe { ffi::CFE_ES_GetAppIDByName(app_id.as_mut_ptr(), c_name.as_ptr()) })?;
203        Ok(AppId(unsafe { app_id.assume_init() }))
204    }
205
206    /// Returns the ID of the currently running application.
207    ///
208    /// Child tasks return the same application ID as their parent.
209    ///
210    /// # Errors
211    ///
212    /// Returns an error if called from a context that is
213    /// not a registered cFE application task.
214    pub fn this() -> Result<AppId> {
215        let mut app_id = MaybeUninit::uninit();
216        check(unsafe { ffi::CFE_ES_GetAppID(app_id.as_mut_ptr()) })?;
217        Ok(AppId(unsafe { app_id.assume_init() }))
218    }
219}
220
221impl Deref for AppId {
222    type Target = ffi::CFE_ES_AppId_t;
223    fn deref(&self) -> &Self::Target {
224        &self.0
225    }
226}
227
228/// A high-level wrapper around the FFI's `CFE_ES_AppInfo_t`.
229///
230/// This struct contains detailed information about a cFE application, such as its
231/// name, entry point, memory layout, and task IDs.
232#[derive(Debug, Clone)]
233pub struct AppInfo {
234    /// The underlying FFI `CFE_ES_AppInfo_t` struct.
235    pub(crate) inner: ffi::CFE_ES_AppInfo_t,
236}
237
238impl AppInfo {
239    /// Returns the registered name of the application.
240    ///
241    /// # Errors
242    ///
243    /// Returns an error if the name from the underlying FFI struct is not
244    /// valid UTF-8 or cannot fit into the `CString` buffer.
245    pub fn name(&self) -> Result<CString<{ ffi::OS_MAX_API_NAME as usize }>> {
246        let c_str = unsafe { CStr::from_ptr(self.inner.Name.as_ptr()) };
247        let bytes = c_str.to_bytes();
248        let mut s = CString::new();
249        s.extend_from_bytes(bytes)
250            .map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
251        Ok(s)
252    }
253
254    /// Copies the entry point name of the application into the provided buffer.
255    /// Returns a &str slice of the valid UTF-8 part of the buffer.
256    ///
257    /// # Arguments
258    /// * `buffer`: A mutable byte slice to copy the name into.
259    ///
260    /// # Errors
261    ///
262    /// Returns an error if the buffer is too small or the name is not valid UTF-8.
263    pub fn copy_entry_point<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a str> {
264        let c_str = unsafe { CStr::from_ptr(self.inner.EntryPoint.as_ptr()) };
265        let bytes = c_str.to_bytes();
266        if bytes.len() >= buffer.len() {
267            return Err(CfsError::Osal(OsalError::InvalidSize));
268        }
269        buffer[..bytes.len()].copy_from_slice(bytes);
270        buffer[bytes.len()] = 0;
271        str::from_utf8(&buffer[..bytes.len()]).map_err(|_| CfsError::InvalidString)
272    }
273
274    /// Copies the file name of the application into the provided buffer.
275    /// Returns a &str slice of the valid UTF-8 part of the buffer.
276    ///
277    /// # Arguments
278    /// * `buffer`: A mutable byte slice to copy the name into.
279    ///
280    /// # Errors
281    ///
282    /// Returns an error if the buffer is too small or the name is not valid UTF-8.
283    pub fn copy_file_name<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a str> {
284        let c_str = unsafe { CStr::from_ptr(self.inner.FileName.as_ptr()) };
285        let bytes = c_str.to_bytes();
286        if bytes.len() >= buffer.len() {
287            return Err(CfsError::Osal(OsalError::InvalidSize));
288        }
289        buffer[..bytes.len()].copy_from_slice(bytes);
290        buffer[bytes.len()] = 0;
291        str::from_utf8(&buffer[..bytes.len()]).map_err(|_| CfsError::InvalidString)
292    }
293}
294
295/// Provides a default panic handler that logs the panic to the cFE System Log
296/// and exits the application. This is highly recommended for all applications.
297///
298/// To use this, add the following to your application's `main.rs` or `lib.rs`:
299///
300/// ```rust,ignore
301/// #[panic_handler]
302/// fn panic(info: &core::panic::PanicInfo) -> ! { // This signature is required.
303///     libcfs::es::app::default_panic_handler(info);
304/// }
305/// ```
306pub fn default_panic_handler(info: &core::panic::PanicInfo) -> ! {
307    if let Some(location) = info.location() {
308        log!("PANIC at {}:{}", location.file(), location.line()).ok();
309    } else {
310        log!("PANIC").ok();
311    }
312
313    unsafe { ffi::CFE_ES_ExitApp(RunStatus::Error as u32) };
314
315    loop {}
316}