osal_rs/utils.rs
1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2023/2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***************************************************************************/
19
20//! Utility types and functions for OSAL-RS.
21//!
22//! This module contains common types, error definitions, and helper functions
23//! used throughout the library.
24
25use core::ffi::{CStr, c_char};
26use core::{ffi::c_void, str::from_utf8_mut};
27use core::fmt::{Debug, Display};
28use core::ops::Deref;
29use core::time::Duration;
30use alloc::string::{String, ToString};
31use alloc::sync::Arc;
32
33use crate::os::Mutex;
34
35/// Error types for OSAL-RS operations.
36///
37/// Represents all possible error conditions that can occur when using
38/// the OSAL-RS library.
39///
40/// # Examples
41///
42/// ```ignore
43/// use osal_rs::os::{Queue, QueueFn};
44/// use osal_rs::utils::Error;
45///
46/// match Queue::new(10, 32) {
47/// Ok(queue) => { /* use queue */ },
48/// Err(Error::OutOfMemory) => println!("Failed to allocate queue"),
49/// Err(e) => println!("Other error: {:?}", e),
50/// }
51/// ```
52#[derive(Debug, Clone, PartialEq, Eq, Hash)]
53pub enum Error {
54 /// Insufficient memory to complete operation
55 OutOfMemory,
56 /// Queue send operation timed out
57 QueueSendTimeout,
58 /// Queue receive operation timed out
59 QueueReceiveTimeout,
60 /// Mutex operation timed out
61 MutexTimeout,
62 /// Failed to acquire mutex lock
63 MutexLockFailed,
64 /// Generic timeout error
65 Timeout,
66 /// Queue is full and cannot accept more items
67 QueueFull,
68 /// String conversion failed
69 StringConversionError,
70 /// Thread/task not found
71 TaskNotFound,
72 /// Invalid queue size specified
73 InvalidQueueSize,
74 /// Null pointer encountered
75 NullPtr,
76 /// Requested item not found
77 NotFound,
78 /// Index out of bounds
79 OutOfIndex,
80 /// Invalid type for operation
81 InvalidType,
82 /// Unhandled error with description
83 Unhandled(&'static str)
84}
85
86/// CPU register size enumeration.
87///
88/// Identifies whether the target CPU uses 32-bit or 64-bit registers.
89/// This is used for platform-specific tick count overflow handling.
90#[derive(PartialEq, Eq, Clone, Copy, Debug)]
91pub enum CpuRegisterSize {
92 /// 64-bit CPU registers
93 Bit64,
94 /// 32-bit CPU registers
95 Bit32
96}
97
98/// Boolean type compatible with RTOS return values.
99///
100/// Many RTOS functions return 0 for success and non-zero for failure.
101/// This type provides a Rust-idiomatic way to work with such values.
102///
103/// # Examples
104///
105/// ```ignore
106/// use osal_rs::os::{Semaphore, SemaphoreFn};
107/// use osal_rs::utils::OsalRsBool;
108/// use core::time::Duration;
109///
110/// let sem = Semaphore::new(1, 1).unwrap();
111///
112/// match sem.wait(Duration::from_millis(100)) {
113/// OsalRsBool::True => println!("Acquired semaphore"),
114/// OsalRsBool::False => println!("Failed to acquire"),
115/// }
116///
117/// // Can also convert to bool
118/// if sem.signal().into() {
119/// println!("Semaphore signaled");
120/// }
121/// ```
122#[derive(PartialEq, Eq, Clone, Copy, Debug)]
123#[repr(u8)]
124pub enum OsalRsBool {
125 /// Operation failed or condition is false
126 False = 1,
127 /// Operation succeeded or condition is true
128 True = 0
129}
130
131/// Maximum delay constant for blocking operations.
132///
133/// When used as a timeout parameter, indicates the operation should
134/// block indefinitely until it succeeds.
135///
136/// # Examples
137///
138/// ```ignore
139/// use osal_rs::os::{Mutex, MutexFn};
140/// use osal_rs::utils::MAX_DELAY;
141///
142/// let mutex = Mutex::new(0);
143/// let guard = mutex.lock(); // Blocks forever if needed
144/// ```
145pub const MAX_DELAY: Duration = Duration::from_millis(usize::MAX as u64);
146
147/// Standard Result type for OSAL-RS operations.
148///
149/// Uses [`Error`] as the default error type.
150pub type Result<T, E = Error> = core::result::Result<T, E>;
151
152/// Pointer to pointer type for C FFI.
153pub type DoublePtr = *mut *mut c_void;
154
155/// Mutable pointer type for C FFI.
156pub type Ptr = *mut c_void;
157
158/// Const pointer type for C FFI.
159pub type ConstPtr = *const c_void;
160
161/// Shortcut for Arc<Mutex<T>>
162pub type ArcMux<T> = Arc<Mutex<T>>;
163
164/// Determines the CPU register size at compile time.
165///
166/// This constant function checks the size of `usize` to determine whether
167/// the target architecture uses 32-bit or 64-bit registers. This information
168/// is used for platform-specific optimizations and overflow handling.
169///
170/// # Returns
171///
172/// * [`CpuRegisterSize::Bit64`] - For 64-bit architectures
173/// * [`CpuRegisterSize::Bit32`] - For 32-bit architectures
174///
175/// # Examples
176///
177/// ```ignore
178/// use osal_rs::utils::{register_bit_size, CpuRegisterSize};
179///
180/// match register_bit_size() {
181/// CpuRegisterSize::Bit64 => println!("Running on 64-bit platform"),
182/// CpuRegisterSize::Bit32 => println!("Running on 32-bit platform"),
183/// }
184/// ```
185pub const fn register_bit_size() -> CpuRegisterSize {
186 if size_of::<usize>() == 8 {
187 CpuRegisterSize::Bit64
188 } else {
189 CpuRegisterSize::Bit32
190 }
191}
192
193/// Converts a C string pointer to a Rust String.
194///
195/// This macro safely converts a raw C string pointer (`*const c_char`) into
196/// a Rust `String`. It handles UTF-8 conversion gracefully using lossy conversion.
197///
198/// # Safety
199///
200/// The pointer must be valid and point to a null-terminated C string.
201///
202/// # Examples
203///
204/// ```ignore
205/// use osal_rs::from_c_str;
206/// use core::ffi::c_char;
207///
208/// extern "C" {
209/// fn get_system_name() -> *const c_char;
210/// }
211///
212/// let name = from_c_str!(get_system_name());
213/// println!("System: {}", name);
214/// ```
215#[macro_export]
216macro_rules! from_c_str {
217 ($str:expr) => {
218 unsafe {
219 let c_str = core::ffi::CStr::from_ptr($str);
220 alloc::string::String::from_utf8_lossy(c_str.to_bytes()).to_string()
221 }
222 };
223}
224
225/// Converts a Rust string to a CString with error handling.
226///
227/// This macro creates a `CString` from a Rust string reference, returning
228/// a `Result` that can be used with the `?` operator. If the conversion fails
229/// (e.g., due to interior null bytes), it returns an appropriate error.
230///
231/// # Returns
232///
233/// * `Ok(CString)` - On successful conversion
234/// * `Err(Error::Unhandled)` - If the string contains interior null bytes
235///
236/// # Examples
237///
238/// ```ignore
239/// use osal_rs::to_cstring;
240/// use osal_rs::utils::Result;
241///
242/// fn pass_to_c_api(name: &str) -> Result<()> {
243/// let c_name = to_cstring!(name)?;
244/// // Use c_name.as_ptr() with C FFI
245/// Ok(())
246/// }
247/// ```
248#[macro_export]
249macro_rules! to_cstring {
250 ($s:expr) => {
251 alloc::ffi::CString::new($s.as_str())
252 .map_err(|_| $crate::utils::Error::Unhandled("Failed to convert string to CString"))
253 };
254}
255
256/// Converts a Rust string to a C string pointer.
257///
258/// This macro creates a `CString` from a Rust string and returns its raw pointer.
259/// **Warning**: This macro panics if the conversion fails. Consider using
260/// [`to_cstring!`] for safer error handling.
261///
262/// # Panics
263///
264/// Panics if the string contains interior null bytes.
265///
266/// # Examples
267///
268/// ```ignore
269/// use osal_rs::to_c_str;
270///
271/// extern "C" {
272/// fn set_name(name: *const core::ffi::c_char);
273/// }
274///
275/// let name = "FreeRTOS Task";
276/// unsafe {
277/// set_name(to_c_str!(name));
278/// }
279/// ```
280#[macro_export]
281macro_rules! to_c_str {
282 ($s:expr) => {
283 alloc::ffi::CString::new($s.as_ref() as &str).unwrap().as_ptr()
284 };
285}
286
287/// Converts a string to a fixed-size byte array.
288///
289/// This macro creates a byte array of the specified size and fills it with
290/// the bytes from the input string. If the string is shorter than the buffer,
291/// the remaining bytes are filled with spaces. If the string is longer, it
292/// is truncated to fit.
293///
294/// # Parameters
295///
296/// * `$str` - The source string to convert
297/// * `$buff_name` - The identifier name for the created buffer variable
298/// * `$buff_size` - The size of the byte array to create
299///
300/// # Examples
301///
302/// ```ignore
303/// use osal_rs::from_str_to_array;
304///
305/// let task_name = "MainTask";
306/// from_str_to_array!(task_name, name_buffer, 16);
307/// // name_buffer is now [u8; 16] containing "MainTask "
308///
309/// // Use with C FFI
310/// extern "C" {
311/// fn create_task(name: *const u8, len: usize);
312/// }
313///
314/// unsafe {
315/// create_task(name_buffer.as_ptr(), name_buffer.len());
316/// }
317/// ```
318#[macro_export]
319macro_rules! from_str_to_array {
320 ($str:expr, $buff_name:ident, $buff_size:expr) => {
321 let mut $buff_name = [b' '; $buff_size];
322 let _bytes = $str.as_bytes();
323 let _len = core::cmp::min(_bytes.len(), $buff_size);
324 $buff_name[.._len].copy_from_slice(&_bytes[.._len]);
325 };
326}
327
328/// Extracts a typed parameter from an optional boxed Any reference.
329///
330/// This macro is used in thread/task entry points to safely extract and
331/// downcast parameters passed to the thread. It handles both the Option
332/// unwrapping and the type downcast, returning appropriate errors if either
333/// operation fails.
334///
335/// # Parameters
336///
337/// * `$param` - An `Option<Box<dyn Any>>` containing the parameter
338/// * `$t` - The type to downcast the parameter to
339///
340/// # Returns
341///
342/// * A reference to the downcasted value of type `$t`
343/// * `Err(Error::NullPtr)` - If the parameter is None
344/// * `Err(Error::InvalidType)` - If the downcast fails
345///
346/// # Examples
347///
348/// ```ignore
349/// use osal_rs::thread_extract_param;
350/// use osal_rs::utils::Result;
351/// use core::any::Any;
352///
353/// struct TaskConfig {
354/// priority: u8,
355/// stack_size: usize,
356/// }
357///
358/// fn task_entry(param: Option<Box<dyn Any>>) -> Result<()> {
359/// let config = thread_extract_param!(param, TaskConfig);
360///
361/// println!("Priority: {}", config.priority);
362/// println!("Stack: {}", config.stack_size);
363///
364/// Ok(())
365/// }
366/// ```
367#[macro_export]
368macro_rules! thread_extract_param {
369 ($param:expr, $t:ty) => {
370 match $param.as_ref() {
371 Some(p) => {
372 match p.downcast_ref::<$t>() {
373 Some(value) => value,
374 None => return Err($crate::utils::Error::InvalidType),
375 }
376 }
377 None => return Err($crate::utils::Error::NullPtr),
378 }
379 };
380}
381
382/// Creates an Arc<Mutex<T>> from a value.
383///
384/// This is a convenience macro to reduce boilerplate when creating
385/// thread-safe shared data structures.
386///
387/// # Examples
388///
389/// ```ignore
390/// use osal_rs::arcmux;
391///
392/// let shared_counter = arcmux!(0);
393/// // Equivalent to: Arc::new(Mutex::new(0))
394/// ```
395#[macro_export]
396macro_rules! arcmux {
397 ($value:expr) => {
398 alloc::sync::Arc::new($crate::os::MutexFn::new($value))
399 };
400}
401
402
403
404/// Fixed-size byte array wrapper with string conversion utilities.
405///
406/// `Bytes` is a generic wrapper around a fixed-size byte array that provides
407/// convenient methods for converting between strings and byte arrays. It's
408/// particularly useful for interfacing with C APIs that expect fixed-size
409/// character buffers, or for storing strings in embedded systems with
410/// constrained memory.
411///
412/// # Type Parameters
413///
414/// * `SIZE` - The size of the internal byte array (default: 0)
415///
416/// # Examples
417///
418/// ```ignore
419/// use osal_rs::utils::Bytes;
420///
421/// // Create an empty 32-byte buffer
422/// let mut buffer = Bytes::<32>::new();
423///
424/// // Create a buffer from a string
425/// let name = Bytes::<16>::new_by_str("TaskName");
426/// println!("{}", name); // Prints "TaskName"
427///
428/// // Create from any type that implements ToString
429/// let number = 42;
430/// let num_bytes = Bytes::<8>::new_by_string(&number);
431/// ```
432#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
433pub struct Bytes<const SIZE: usize = 0> (pub [u8; SIZE]);
434
435impl<const SIZE: usize> Deref for Bytes<SIZE> {
436 type Target = [u8; SIZE];
437
438 /// Dereferences to the underlying byte array.
439 ///
440 /// This allows `Bytes` to be used anywhere a `[u8; SIZE]` reference is expected.
441 ///
442 /// # Examples
443 ///
444 /// ```ignore
445 /// use osal_rs::utils::Bytes;
446 ///
447 /// let bytes = Bytes::<8>::new_by_str("test");
448 /// assert_eq!(bytes[0], b't');
449 /// ```
450 fn deref(&self) -> &Self::Target {
451 &self.0
452 }
453}
454
455impl<const SIZE: usize> Display for Bytes<SIZE> {
456 /// Formats the byte array as a C-style null-terminated string.
457 ///
458 /// This implementation treats the byte array as a C string and converts it
459 /// to a Rust string for display. If the conversion fails, it displays
460 /// "Conversion error".
461 ///
462 /// # Safety
463 ///
464 /// This method assumes the byte array contains valid UTF-8 data and is
465 /// null-terminated. Invalid data may result in the error message being displayed.
466 ///
467 /// # Examples
468 ///
469 /// ```ignore
470 /// use osal_rs::utils::Bytes;
471 ///
472 /// let bytes = Bytes::<16>::new_by_str("Hello");
473 /// println!("{}", bytes); // Prints "Hello"
474 /// ```
475 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
476 let str = unsafe {
477 CStr::from_ptr(self.0.as_ptr() as *const c_char)
478 .to_str()
479 .unwrap_or("Conversion error")
480 };
481
482 write!(f, "{}", str.to_string())
483 }
484}
485
486impl AsSyncStr for Bytes<> {
487 /// Returns a string slice reference.
488 ///
489 /// This method provides access to the underlying string data in a way
490 /// that is safe to use across thread boundaries.
491 ///
492 /// # Returns
493 ///
494 /// A reference to a string slice with lifetime tied to `self`.
495 fn as_str(&self) -> &str {
496 unsafe {
497 CStr::from_ptr(self.0.as_ptr() as *const c_char)
498 .to_str()
499 .unwrap_or("Conversion error")
500 }
501 }
502}
503
504impl<const SIZE: usize> Bytes<SIZE> {
505 /// Creates a new `Bytes` instance filled with zeros.
506 ///
507 /// This is a const function, allowing it to be used in const contexts
508 /// and static variable declarations.
509 ///
510 /// # Returns
511 ///
512 /// A `Bytes` instance with all bytes set to 0.
513 ///
514 /// # Examples
515 ///
516 /// ```ignore
517 /// use osal_rs::utils::Bytes;
518 ///
519 /// const BUFFER: Bytes<64> = Bytes::new();
520 ///
521 /// let runtime_buffer = Bytes::<32>::new();
522 /// assert_eq!(runtime_buffer[0], 0);
523 /// ```
524 pub const fn new() -> Self {
525 Self( [0u8; SIZE] )
526 }
527
528 /// Creates a new `Bytes` instance from a string slice.
529 ///
530 /// Copies the bytes from the input string into the fixed-size array.
531 /// If the string is shorter than `SIZE`, the remaining bytes are zero-filled.
532 /// If the string is longer, it is truncated to fit.
533 ///
534 /// # Parameters
535 ///
536 /// * `str` - The source string to convert
537 ///
538 /// # Returns
539 ///
540 /// A `Bytes` instance containing the string data.
541 ///
542 /// # Examples
543 ///
544 /// ```ignore
545 /// use osal_rs::utils::Bytes;
546 ///
547 /// let short = Bytes::<16>::new_by_str("Hi");
548 /// // Internal array: [b'H', b'i', 0, 0, 0, ...]
549 ///
550 /// let exact = Bytes::<5>::new_by_str("Hello");
551 /// // Internal array: [b'H', b'e', b'l', b'l', b'o']
552 ///
553 /// let long = Bytes::<3>::new_by_str("Hello");
554 /// // Internal array: [b'H', b'e', b'l'] (truncated)
555 /// ```
556 pub fn new_by_str(str: &str) -> Self {
557
558 let mut array = [0u8; SIZE];
559
560 let mut i = 0usize ;
561 for byte in str.as_bytes() {
562 if i > SIZE - 1{
563 break;
564 }
565 array[i] = *byte;
566 i += 1;
567 }
568
569 Self( array )
570 }
571
572 /// Creates a new `Bytes` instance from any type implementing `ToString`.
573 ///
574 /// This is a convenience wrapper around [`new_by_str`](Self::new_by_str)
575 /// that first converts the input to a string.
576 ///
577 /// # Parameters
578 ///
579 /// * `str` - Any value that implements `ToString`
580 ///
581 /// # Returns
582 ///
583 /// A `Bytes` instance containing the string representation of the input.
584 ///
585 /// # Examples
586 ///
587 /// ```ignore
588 /// use osal_rs::utils::Bytes;
589 ///
590 /// // From integer
591 /// let num_bytes = Bytes::<8>::new_by_string(&42);
592 ///
593 /// // From String
594 /// let string = String::from("Task");
595 /// let str_bytes = Bytes::<16>::new_by_string(&string);
596 ///
597 /// // From custom type with ToString
598 /// #[derive(Debug)]
599 /// struct TaskId(u32);
600 /// impl ToString for TaskId {
601 /// fn to_string(&self) -> String {
602 /// format!("Task-{}", self.0)
603 /// }
604 /// }
605 /// let task_bytes = Bytes::<16>::new_by_string(&TaskId(5));
606 /// ```
607 pub fn new_by_string(str: &impl ToString) -> Self {
608 Self::new_by_str(&str.to_string())
609 }
610
611 /// Fills a mutable string slice with the contents of the byte array.
612 ///
613 /// Attempts to convert the internal byte array to a UTF-8 string and
614 /// copies it into the destination string slice. Only copies up to the
615 /// minimum of the source and destination lengths.
616 ///
617 /// # Parameters
618 ///
619 /// * `dest` - The destination string slice to fill
620 ///
621 /// # Panics
622 ///
623 /// Currently panics (todo!) if the byte array contains invalid UTF-8.
624 ///
625 /// # Examples
626 ///
627 /// ```ignore
628 /// use osal_rs::utils::Bytes;
629 ///
630 /// let bytes = Bytes::<16>::new_by_str("Hello World");
631 ///
632 /// let mut output = String::from(" "); // 16 spaces
633 /// bytes.fill_str(unsafe { output.as_mut_str() });
634 ///
635 /// assert_eq!(&output[..11], "Hello World");
636 /// ```
637 pub fn fill_str(&mut self, dest: &mut str) {
638 match from_utf8_mut(&mut self.0) {
639 Ok(str) => {
640 let len = core::cmp::min(str.len(), dest.len());
641 unsafe {
642 dest.as_bytes_mut()[..len].copy_from_slice(&str.as_bytes()[..len]);
643 }
644 }
645 Err(_) => todo!(),
646 }
647 }
648}
649
650/// Trait for types that can provide a string reference in a thread-safe manner.
651///
652/// This trait extends the basic string reference functionality with thread-safety
653/// guarantees by requiring both `Sync` and `Send` bounds. It's useful for types
654/// that need to provide string data across thread boundaries in a concurrent
655/// environment.
656///
657/// # Thread Safety
658///
659/// Implementors must be both `Sync` (safe to share references across threads) and
660/// `Send` (safe to transfer ownership across threads).
661///
662/// # Examples
663///
664/// ```ignore
665/// use osal_rs::utils::AsSyncStr;
666///
667/// struct ThreadSafeName {
668/// name: &'static str,
669/// }
670///
671/// impl AsSyncStr for ThreadSafeName {
672/// fn as_str(&self) -> &str {
673/// self.name
674/// }
675/// }
676///
677/// // Can be safely shared across threads
678/// fn use_in_thread(item: &dyn AsSyncStr) {
679/// println!("Name: {}", item.as_str());
680/// }
681/// ```
682pub trait AsSyncStr : Sync + Send {
683 /// Returns a string slice reference.
684 ///
685 /// This method provides access to the underlying string data in a way
686 /// that is safe to use across thread boundaries.
687 ///
688 /// # Returns
689 ///
690 /// A reference to a string slice with lifetime tied to `self`.
691 fn as_str(&self) -> &str;
692}
693
694impl PartialEq for dyn AsSyncStr {
695 fn eq(&self, other: &Self) -> bool {
696 self.as_str() == other.as_str()
697 }
698}
699
700impl Eq for dyn AsSyncStr {}
701
702impl Debug for dyn AsSyncStr {
703 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
704 write!(f, "{}", self.as_str())
705 }
706}
707
708impl Display for dyn AsSyncStr {
709 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
710 write!(f, "{}", self.as_str())
711 }
712}
713