Skip to main content

leodos_libcfs/cfe/sb/
msg.rs

1//! Safe, idiomatic wrappers for the CFE Message Services (CFE_MSG) API.
2//!
3//! This module provides `MessageRef` and `MessageMut` structs to safely create,
4//! access, and modify the headers of CFE Software Bus messages. It operates on
5//! Rust byte slices (`&[u8]` and `&mut [u8]`) to prevent common errors
6//! associated with raw pointer manipulation.
7
8use crate::cfe::time::SysTime;
9use crate::error::{CfsError, OsalError, Result};
10use crate::ffi;
11use crate::status::check;
12use core::mem::MaybeUninit;
13
14/// A Command Header.
15#[repr(transparent)]
16#[derive(Clone, Copy, Default)]
17pub struct CmdHeader(pub(crate) ffi::CFE_MSG_CommandHeader_t);
18
19impl core::fmt::Debug for CmdHeader {
20    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21        f.debug_struct("CmdHeader").finish()
22    }
23}
24
25/// A Telemetry Header.
26#[repr(transparent)]
27#[derive(Clone, Copy, Default)]
28pub struct TlmHeader(pub(crate) ffi::CFE_MSG_TelemetryHeader_t);
29
30impl core::fmt::Debug for TlmHeader {
31    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32        f.debug_struct("TlmHeader").finish()
33    }
34}
35
36/// A type-safe, zero-cost wrapper for a cFE Software Bus Message ID.
37#[derive(Debug, Clone, Copy)]
38#[repr(transparent)]
39pub struct MsgId(pub(crate) ffi::CFE_SB_MsgId_t);
40
41impl MsgId {
42    /// Returns the raw underlying `CFE_SB_MsgId_t`.
43    pub fn value(&self) -> u32 {
44        self.0.Value
45    }
46}
47
48impl PartialEq for MsgId {
49    fn eq(&self, other: &Self) -> bool {
50        self.0.Value == other.0.Value
51    }
52}
53impl Eq for MsgId {}
54
55impl MsgId {
56    /// Checks if the message ID is numerically within the valid mission-defined range.
57    ///
58    /// # C-API Mapping
59    /// This is a safe Rust implementation of the C function `CFE_SB_IsValidMsgId`.
60    pub fn is_valid(&self) -> bool {
61        // Per the CFE_SB_IsValidMsgId logic, a valid ID is non-zero and within the platform-defined range.
62        self.0.Value != 0 && self.0.Value <= ffi::CFE_PLATFORM_SB_HIGHEST_VALID_MSGID
63    }
64
65    /// Creates a command `MsgId` from a mission-defined topic ID and a CPU instance number.
66    pub fn cmd(topic_id: u16, instance_num: u16) -> Self {
67        Self(ffi::CFE_SB_MsgId_t {
68            Value: unsafe { ffi::CFE_SB_CmdTopicIdToMsgId(topic_id, instance_num) },
69        })
70    }
71
72    /// Creates a telemetry `MsgId` from a mission-defined topic ID and a CPU instance number.
73    pub fn tlm(topic_id: u16, instance_num: u16) -> Self {
74        Self(ffi::CFE_SB_MsgId_t {
75            Value: unsafe { ffi::CFE_SB_TlmTopicIdToMsgId(topic_id, instance_num) },
76        })
77    }
78
79    /// Creates a global command `MsgId` from a mission-defined topic ID.
80    pub fn global_cmd(topic_id: u16) -> Self {
81        Self(ffi::CFE_SB_MsgId_t {
82            Value: unsafe { ffi::CFE_SB_GlobalCmdTopicIdToMsgId(topic_id) },
83        })
84    }
85
86    /// Creates a global telemetry `MsgId` from a mission-defined topic ID.
87    pub fn global_tlm(topic_id: u16) -> Self {
88        Self(ffi::CFE_SB_MsgId_t {
89            Value: unsafe { ffi::CFE_SB_GlobalTlmTopicIdToMsgId(topic_id) },
90        })
91    }
92
93    /// Creates a local command `MsgId` from a mission-defined topic ID for the current CPU.
94    pub fn local_cmd(topic_id: u16) -> Self {
95        Self(ffi::CFE_SB_MsgId_t {
96            Value: unsafe { ffi::CFE_SB_LocalCmdTopicIdToMsgId(topic_id) },
97        })
98    }
99
100    /// Creates a local telemetry `MsgId` from a mission-defined topic ID for the current CPU.
101    pub fn local_tlm(topic_id: u16) -> Self {
102        Self(ffi::CFE_SB_MsgId_t {
103            Value: unsafe { ffi::CFE_SB_LocalTlmTopicIdToMsgId(topic_id) },
104        })
105    }
106
107    /// Gets the message type (Command or Telemetry) from this message ID.
108    pub fn get_type(&self) -> Result<MsgType> {
109        let mut msg_type = MaybeUninit::uninit();
110        check(unsafe { ffi::CFE_MSG_GetTypeFromMsgId(self.0, msg_type.as_mut_ptr()) })?;
111        Ok(unsafe { msg_type.assume_init() }.into())
112    }
113}
114
115/// The type of a cFE message (Command or Telemetry).
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[repr(u32)]
118pub enum MsgType {
119    /// An invalid or unknown message type.
120    Invalid = ffi::CFE_MSG_Type_CFE_MSG_Type_Invalid,
121    /// A command message.
122    Cmd = ffi::CFE_MSG_Type_CFE_MSG_Type_Cmd,
123    /// A telemetry message.
124    Tlm = ffi::CFE_MSG_Type_CFE_MSG_Type_Tlm,
125}
126
127impl From<ffi::CFE_MSG_Type_t> for MsgType {
128    fn from(val: ffi::CFE_MSG_Type_t) -> Self {
129        match val {
130            ffi::CFE_MSG_Type_CFE_MSG_Type_Cmd => MsgType::Cmd,
131            ffi::CFE_MSG_Type_CFE_MSG_Type_Tlm => MsgType::Tlm,
132            _ => MsgType::Invalid,
133        }
134    }
135}
136
137/// A safe, read-only wrapper around a CFE message byte slice.
138///
139/// This provides methods to access header fields without needing `unsafe` code
140/// or raw pointers in your application.
141#[derive(Debug, Copy, Clone)]
142pub struct MessageRef<'a> {
143    slice: &'a [u8],
144}
145
146impl<'a> MessageRef<'a> {
147    /// Creates a new `MessageRef` from a byte slice.
148    pub fn new(slice: &'a [u8]) -> Self {
149        Self { slice }
150    }
151
152    /// Returns the underlying byte slice of the entire message.
153    pub fn as_slice(&self) -> &'a [u8] {
154        self.slice
155    }
156
157    /// Gets the header version from the message header.
158    pub fn header_version(&self) -> Result<u16> {
159        let mut version = 0;
160        check(unsafe {
161            ffi::CFE_MSG_GetHeaderVersion(
162                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
163                &mut version,
164            )
165        })?;
166        Ok(version)
167    }
168
169    /// Gets the APID from the message header.
170    pub fn apid(&self) -> Result<u16> {
171        let mut apid = 0;
172        check(unsafe {
173            ffi::CFE_MSG_GetApId(
174                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
175                &mut apid,
176            )
177        })?;
178        Ok(apid)
179    }
180
181    /// Gets the total size of the message from its header.
182    pub fn size(&self) -> Result<usize> {
183        let mut size = 0;
184        let status = unsafe {
185            ffi::CFE_MSG_GetSize(
186                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
187                &mut size,
188            )
189        };
190        check(status)?;
191        Ok(size)
192    }
193
194    /// Gets the message ID from the message header.
195    pub fn msg_id(&self) -> Result<MsgId> {
196        let mut msg_id = MaybeUninit::uninit();
197        let status = unsafe {
198            ffi::CFE_MSG_GetMsgId(
199                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
200                msg_id.as_mut_ptr(),
201            )
202        };
203        check(status)?;
204        Ok(MsgId(unsafe { msg_id.assume_init() }))
205    }
206
207    /// Gets the function code from a command message header.
208    ///
209    /// Returns an error if the message does not have a secondary command header.
210    pub fn fcn_code(&self) -> Result<u16> {
211        let mut fcn_code = 0;
212        let status = unsafe {
213            ffi::CFE_MSG_GetFcnCode(
214                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
215                &mut fcn_code,
216            )
217        };
218        check(status)?;
219        Ok(fcn_code)
220    }
221
222    /// Gets the timestamp from a telemetry message header.
223    ///
224    /// Returns an error if the message does not have a secondary telemetry header.
225    pub fn time(&self) -> Result<SysTime> {
226        let mut time = MaybeUninit::uninit();
227        let status = unsafe {
228            ffi::CFE_MSG_GetMsgTime(
229                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
230                time.as_mut_ptr(),
231            )
232        };
233        check(status)?;
234        Ok(SysTime(unsafe { time.assume_init() }))
235    }
236
237    /// Gets the sequence count from the message header.
238    pub fn sequence_count(&self) -> Result<u16> {
239        let mut count = 0;
240        let status = unsafe {
241            ffi::CFE_MSG_GetSequenceCount(
242                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
243                &mut count,
244            )
245        };
246        check(status)?;
247        Ok(count)
248    }
249
250    /// Validates the checksum of a command message.
251    ///
252    /// Returns `Ok(true)` if the checksum is valid, `Ok(false)` if it is not.
253    /// Returns an error if the message does not have a command secondary header.
254    pub fn validate_checksum(&self) -> Result<bool> {
255        let mut is_valid = false;
256        let status = unsafe {
257            ffi::CFE_MSG_ValidateChecksum(
258                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
259                &mut is_valid,
260            )
261        };
262        check(status)?;
263        Ok(is_valid)
264    }
265
266    /// Gets the message type (Command or Telemetry) from the header.
267    pub fn get_type(&self) -> Result<MsgType> {
268        let mut msg_type = MaybeUninit::uninit();
269        check(unsafe {
270            ffi::CFE_MSG_GetType(
271                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
272                msg_type.as_mut_ptr(),
273            )
274        })?;
275        Ok(unsafe { msg_type.assume_init() }.into())
276    }
277
278    /// Checks if the message header indicates the presence of a secondary header.
279    pub fn has_secondary_header(&self) -> Result<bool> {
280        let mut has_secondary = false;
281        check(unsafe {
282            ffi::CFE_MSG_GetHasSecondaryHeader(
283                self.slice.as_ptr() as *const ffi::CFE_MSG_Message_t,
284                &mut has_secondary,
285            )
286        })?;
287        Ok(has_secondary)
288    }
289
290    /// Gets a pointer to the user data portion of the message.
291    ///
292    /// # C-API Mapping
293    /// This is a wrapper for `CFE_SB_GetUserData`.
294    ///
295    /// # Safety
296    /// The caller must ensure the returned pointer is cast to the correct payload struct type.
297    pub unsafe fn user_data(&self) -> *mut libc::c_void {
298        ffi::CFE_SB_GetUserData(self.slice.as_ptr() as *mut ffi::CFE_MSG_Message_t)
299    }
300
301    /// Gets the length of the user data portion of the message.
302    pub fn user_data_length(&self) -> usize {
303        unsafe { ffi::CFE_SB_GetUserDataLength(self.slice.as_ptr() as *const _) }
304    }
305
306    /// Gets the segmentation flag from the message header.
307    pub fn segmentation_flag(&self) -> Result<ffi::CFE_MSG_SegmentationFlag_t> {
308        let mut flag = MaybeUninit::uninit();
309        check(unsafe {
310            ffi::CFE_MSG_GetSegmentationFlag(
311                self.as_slice().as_ptr() as *const _,
312                flag.as_mut_ptr(),
313            )
314        })?;
315        Ok(unsafe { flag.assume_init() })
316    }
317
318    /// Gets the EDS version from the message header.
319    pub fn eds_version(&self) -> Result<u16> {
320        let mut version = MaybeUninit::uninit();
321        check(unsafe {
322            ffi::CFE_MSG_GetEDSVersion(self.as_slice().as_ptr() as *const _, version.as_mut_ptr())
323        })?;
324        Ok(unsafe { version.assume_init() })
325    }
326
327    /// Gets the endianness indicator from the message header.
328    pub fn endian(&self) -> Result<ffi::CFE_MSG_Endian_t> {
329        let mut endian = MaybeUninit::uninit();
330        check(unsafe {
331            ffi::CFE_MSG_GetEndian(self.as_slice().as_ptr() as *const _, endian.as_mut_ptr())
332        })?;
333        Ok(unsafe { endian.assume_init() })
334    }
335
336    /// Gets the playback flag from the message header.
337    pub fn playback_flag(&self) -> Result<ffi::CFE_MSG_PlaybackFlag_t> {
338        let mut flag = MaybeUninit::uninit();
339        check(unsafe {
340            ffi::CFE_MSG_GetPlaybackFlag(self.as_slice().as_ptr() as *const _, flag.as_mut_ptr())
341        })?;
342        Ok(unsafe { flag.assume_init() })
343    }
344
345    /// Gets the subsystem ID from the message header.
346    pub fn subsystem(&self) -> Result<u16> {
347        let mut subsystem = MaybeUninit::uninit();
348        check(unsafe {
349            ffi::CFE_MSG_GetSubsystem(self.as_slice().as_ptr() as *const _, subsystem.as_mut_ptr())
350        })?;
351        Ok(unsafe { subsystem.assume_init() })
352    }
353
354    /// Gets the system ID from the message header.
355    pub fn system(&self) -> Result<u16> {
356        let mut system = MaybeUninit::uninit();
357        check(unsafe {
358            ffi::CFE_MSG_GetSystem(self.as_slice().as_ptr() as *const _, system.as_mut_ptr())
359        })?;
360        Ok(unsafe { system.assume_init() })
361    }
362}
363
364/// A safe, writeable wrapper around a CFE message byte slice.
365///
366/// This provides methods to initialize and modify header fields. It is typically
367/// used with a `SendBuffer` from the `sb` module.
368#[derive(Debug)]
369pub struct MessageMut<'a> {
370    pub(crate) slice: &'a mut [u8],
371}
372
373impl<'a> MessageMut<'a> {
374    /// Returns the underlying byte slice of the entire message.
375    pub fn as_slice(&self) -> &[u8] {
376        self.slice
377    }
378
379    /// Returns the underlying mutable byte slice of the entire message.
380    pub fn as_mut_slice(&mut self) -> &mut [u8] {
381        self.slice
382    }
383
384    /// Transmits the message in this buffer.
385    /// The `is_origination` flag should be set to true if the message is being sent
386    /// as an original message from this application, or false if it is being relayed.
387    pub fn send(self, is_origination: bool) -> Result<()> {
388        let status = unsafe {
389            ffi::CFE_SB_TransmitBuffer(self.slice.as_mut_ptr() as *mut _, is_origination)
390        };
391
392        if status == ffi::CFE_SUCCESS {
393            Ok(())
394        } else {
395            Err(CfsError::from(status))
396        }
397    }
398
399    /// Initializes a message header within the buffer.
400    ///
401    /// This routine zeroes the buffer up to `size`, sets default header fields,
402    /// and then populates the message ID and length fields.
403    pub fn init(&mut self, msg_id: MsgId, size: usize) -> Result<()> {
404        if size > self.slice.len() {
405            return Err(CfsError::Osal(OsalError::InvalidSize));
406        }
407        let status = unsafe {
408            ffi::CFE_MSG_Init(
409                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
410                msg_id.0,
411                size,
412            )
413        };
414        check(status)?;
415        Ok(())
416    }
417
418    /// Sets the total size of the message in its header.
419    pub fn set_size(&mut self, size: usize) -> Result<()> {
420        if size > self.slice.len() {
421            return Err(CfsError::Osal(OsalError::InvalidSize));
422        }
423        let status = unsafe {
424            ffi::CFE_MSG_SetSize(self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t, size)
425        };
426        check(status)?;
427        Ok(())
428    }
429
430    /// Sets the message ID in the message header.
431    pub fn set_msg_id(&mut self, msg_id: MsgId) -> Result<()> {
432        let status = unsafe {
433            ffi::CFE_MSG_SetMsgId(
434                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
435                msg_id.0,
436            )
437        };
438        check(status)?;
439        Ok(())
440    }
441
442    /// Sets the header version in the message header.
443    pub fn set_header_version(&mut self, version: u16) -> Result<()> {
444        check(unsafe {
445            ffi::CFE_MSG_SetHeaderVersion(
446                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
447                version,
448            )
449        })?;
450        Ok(())
451    }
452
453    /// Sets the APID in the message header.
454    pub fn set_apid(&mut self, apid: u16) -> Result<()> {
455        check(unsafe {
456            ffi::CFE_MSG_SetApId(self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t, apid)
457        })?;
458        Ok(())
459    }
460
461    /// Sets the function code in a command message header.
462    ///
463    /// Returns an error if the message does not have a secondary command header.
464    pub fn set_fcn_code(&mut self, fcn_code: u16) -> Result<()> {
465        let status = unsafe {
466            ffi::CFE_MSG_SetFcnCode(
467                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
468                fcn_code,
469            )
470        };
471        check(status)?;
472        Ok(())
473    }
474
475    /// Sets the timestamp in a telemetry message header.
476    ///
477    /// Returns an error if the message does not have a secondary telemetry header.
478    pub fn set_time(&mut self, new_time: SysTime) -> Result<()> {
479        let status = unsafe {
480            ffi::CFE_MSG_SetMsgTime(
481                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
482                new_time.0,
483            )
484        };
485        check(status)?;
486        Ok(())
487    }
488
489    /// Sets the sequence count in the message header.
490    pub fn set_sequence_count(&mut self, count: u16) -> Result<()> {
491        let status = unsafe {
492            ffi::CFE_MSG_SetSequenceCount(
493                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
494                count,
495            )
496        };
497        check(status)?;
498        Ok(())
499    }
500
501    /// Calculates and sets the checksum field in a command message header.
502    ///
503    /// Returns an error if the message does not have a command secondary header.
504    pub fn generate_checksum(&mut self) -> Result<()> {
505        let status = unsafe {
506            ffi::CFE_MSG_GenerateChecksum(self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t)
507        };
508        check(status)?;
509        Ok(())
510    }
511
512    /// Sets the message type (Command or Telemetry) in the header.
513    pub fn set_type(&mut self, msg_type: MsgType) -> Result<()> {
514        check(unsafe {
515            ffi::CFE_MSG_SetType(
516                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
517                msg_type as ffi::CFE_MSG_Type_t,
518            )
519        })?;
520        Ok(())
521    }
522
523    /// Sets the flag indicating whether a secondary header is present.
524    pub fn set_has_secondary_header(&mut self, has_secondary: bool) -> Result<()> {
525        check(unsafe {
526            ffi::CFE_MSG_SetHasSecondaryHeader(
527                self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t,
528                has_secondary,
529            )
530        })?;
531        Ok(())
532    }
533
534    /// Gets a raw pointer to the user data portion of the message.
535    /// # C-API Mapping
536    /// This is a wrapper for `CFE_SB_GetUserData`.
537    ///
538    /// # Safety
539    /// The caller must ensure the returned pointer is cast to the correct payload struct type
540    /// and that the size of the payload struct does not exceed the user data length.
541    pub unsafe fn user_data(&mut self) -> *mut libc::c_void {
542        ffi::CFE_SB_GetUserData(self.slice.as_mut_ptr() as *mut ffi::CFE_MSG_Message_t)
543    }
544
545    /// Returns a mutable reference to the message payload, interpreted as type `P`.
546    ///
547    /// This is the primary safe method for accessing the payload of a message.
548    /// It performs a size check to ensure the payload type `P` fits within the
549    /// available user data area of the message buffer, preventing buffer overruns.
550    ///
551    /// # Errors
552    ///
553    /// Returns `Error::StatusWrongMsgLength` if `size_of::<P>()` is larger
554    /// than the available user data length in the message buffer.
555    pub fn payload<P: Sized>(&mut self) -> Result<&mut P> {
556        if core::mem::size_of::<P>() > self.user_data_length() {
557            return Err(CfsError::WrongMsgLength);
558        }
559        // This is safe because:
560        // 1. We have a mutable reference to the slice, ensuring exclusive access.
561        // 2. We have checked that the size of P fits within the user data area.
562        // 3. user_data() provides a correctly aligned pointer to the payload.
563        unsafe {
564            let payload_ptr = self.user_data() as *mut P;
565            Ok(&mut *payload_ptr)
566        }
567    }
568
569    /// Gets the length of the user data portion of the message.
570    pub fn user_data_length(&self) -> usize {
571        unsafe { ffi::CFE_SB_GetUserDataLength(self.slice.as_ptr() as *const _) }
572    }
573
574    /// Sets the length of the user data portion of the message.
575    pub fn set_user_data_length(&mut self, length: usize) {
576        unsafe { ffi::CFE_SB_SetUserDataLength(self.slice.as_mut_ptr() as *mut _, length) }
577    }
578
579    /// Sets the time field in the message header with the current spacecraft time.
580    pub fn timestamp(&mut self) {
581        unsafe { ffi::CFE_SB_TimeStampMsg(self.slice.as_mut_ptr() as *mut _) }
582    }
583
584    /// Sets the segmentation flag in the message header.
585    pub fn set_segmentation_flag(&mut self, flag: ffi::CFE_MSG_SegmentationFlag_t) -> Result<()> {
586        check(unsafe {
587            ffi::CFE_MSG_SetSegmentationFlag(self.slice.as_mut_ptr() as *mut _, flag)
588        })?;
589        Ok(())
590    }
591
592    /// Sets the EDS version in the message header.
593    pub fn set_eds_version(&mut self, version: u16) -> Result<()> {
594        check(unsafe { ffi::CFE_MSG_SetEDSVersion(self.slice.as_mut_ptr() as *mut _, version) })?;
595        Ok(())
596    }
597
598    /// Sets the endianness indicator in the message header.
599    pub fn set_endian(&mut self, endian: ffi::CFE_MSG_Endian_t) -> Result<()> {
600        check(unsafe { ffi::CFE_MSG_SetEndian(self.slice.as_mut_ptr() as *mut _, endian) })?;
601        Ok(())
602    }
603
604    /// Sets the playback flag in the message header.
605    pub fn set_playback_flag(&mut self, flag: ffi::CFE_MSG_PlaybackFlag_t) -> Result<()> {
606        check(unsafe { ffi::CFE_MSG_SetPlaybackFlag(self.slice.as_mut_ptr() as *mut _, flag) })?;
607        Ok(())
608    }
609
610    /// Sets the subsystem ID in the message header.
611    pub fn set_subsystem(&mut self, subsystem: u16) -> Result<()> {
612        check(unsafe { ffi::CFE_MSG_SetSubsystem(self.slice.as_mut_ptr() as *mut _, subsystem) })?;
613        Ok(())
614    }
615
616    /// Sets the system ID in the message header.
617    pub fn set_system(&mut self, system: u16) -> Result<()> {
618        check(unsafe { ffi::CFE_MSG_SetSystem(self.slice.as_mut_ptr() as *mut _, system) })?;
619        Ok(())
620    }
621}
622
623/// Copies a Rust string slice into a fixed-size C-style char array within a message.
624///
625/// This is a safe wrapper around `CFE_SB_MessageStringSet`. It handles truncation
626/// and null-padding correctly.
627///
628/// # Arguments
629/// * `dest`: A mutable slice representing the fixed-size char array in the message.
630/// * `src`: The Rust string slice to copy from.
631pub fn message_string_set(dest: &mut [i8], src: &str) -> Result<usize> {
632    let bytes_copied = unsafe {
633        ffi::CFE_SB_MessageStringSet(
634            dest.as_mut_ptr() as *mut libc::c_char,
635            src.as_ptr() as *const libc::c_char,
636            dest.len(),
637            src.len(),
638        )
639    };
640    if bytes_copied < 0 {
641        Err(CfsError::from(bytes_copied))
642    } else {
643        Ok(bytes_copied as usize)
644    }
645}
646
647/// Copies a string from a fixed-size C-style char array within a message to a Rust buffer.
648///
649/// This is a safe wrapper around `CFE_SB_MessageStringGet`. It correctly handles
650/// unterminated strings from the source buffer and ensures the destination is null-terminated.
651///
652/// # Arguments
653/// * `dest`: The mutable byte buffer to copy the string into.
654/// * `src`: The fixed-size array from the message.
655/// * `default_src`: An optional default string to use if the source string is empty.
656pub fn message_string_get<'a>(
657    dest: &'a mut [u8],
658    src: &[u8],
659    default_src: Option<&str>,
660) -> Result<&'a str> {
661    let default_ptr = default_src.map_or(core::ptr::null(), |s| s.as_ptr() as *const libc::c_char);
662    let bytes_copied = unsafe {
663        ffi::CFE_SB_MessageStringGet(
664            dest.as_mut_ptr() as *mut libc::c_char,
665            src.as_ptr() as *const libc::c_char,
666            default_ptr as *const libc::c_char,
667            dest.len(),
668            src.len(),
669        )
670    };
671    if bytes_copied < 0 {
672        return Err(CfsError::from(bytes_copied));
673    }
674    core::str::from_utf8(&dest[..bytes_copied as usize]).map_err(|_| CfsError::InvalidString)
675}
676
677/// Gets the next sequence count value, handling rollovers correctly.
678pub fn get_next_sequence_count(current_count: u16) -> u16 {
679    unsafe { ffi::CFE_MSG_GetNextSequenceCount(current_count) }
680}