mavlink_core/
types.rs

1use core::ops::{Deref, DerefMut};
2
3#[cfg(feature = "serde")]
4use crate::utils::nulstr;
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "arbitrary")]
9use arbitrary::{Arbitrary, Unstructured};
10
11/// Abstraction around a byte array that represents a string.
12///
13/// MAVLink encodes strings as C char arrays and the handling is field dependent.
14/// This abstration allows to choose if one wants to handle the field as
15/// a raw byte array or if one wants the convenience of a str that stops at the first null byte.
16///
17/// # Example
18/// ```
19/// use mavlink_core::types::CharArray;
20///
21/// let data = [0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x00, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x00, 0x00, 0x00];
22/// let ca = CharArray::new(data);
23/// assert_eq!(ca.to_str().unwrap(), "HELLO");
24/// // or using the from str method
25/// let ca: CharArray<10> = "HELLO!".into();
26/// assert_eq!(ca.to_str().unwrap(), "HELLO!");
27///
28/// ```
29#[cfg_attr(feature = "serde", derive(Serialize))]
30#[cfg_attr(feature = "serde", serde(transparent))]
31#[derive(Debug, PartialEq, Clone, Copy)]
32pub struct CharArray<const N: usize> {
33    #[cfg_attr(
34        feature = "serde",
35        serde(serialize_with = "nulstr::serialize::<_, N>",)
36    )]
37    data: [u8; N],
38
39    #[cfg_attr(feature = "serde", serde(skip))]
40    str_len: usize,
41}
42
43impl<const N: usize> CharArray<N> {
44    pub const fn new(data: [u8; N]) -> Self {
45        // Note: The generated code uses this in const contexts, so this is a const fn
46        // and so we can't use iterators or other fancy stuff unfortunately.
47        let mut first_null = N;
48        let mut i = 0;
49        loop {
50            if i >= N {
51                break;
52            }
53            if data[i] == 0 {
54                first_null = i;
55                break;
56            }
57            i += 1;
58        }
59        Self {
60            data,
61            str_len: first_null,
62        }
63    }
64
65    /// Get the string representation of the char array.
66    /// Returns the string stopping at the first null byte and if the string is not valid utf8
67    /// the returned string will be empty.
68    pub fn to_str(&self) -> Result<&str, core::str::Utf8Error> {
69        core::str::from_utf8(&self.data[..self.str_len])
70    }
71}
72
73impl<const N: usize> Deref for CharArray<N> {
74    type Target = [u8; N];
75
76    fn deref(&self) -> &Self::Target {
77        &self.data
78    }
79}
80
81impl<const N: usize> DerefMut for CharArray<N> {
82    fn deref_mut(&mut self) -> &mut Self::Target {
83        &mut self.data
84    }
85}
86
87impl<'a, const N: usize> IntoIterator for &'a CharArray<N> {
88    type Item = &'a u8;
89    type IntoIter = core::slice::Iter<'a, u8>;
90
91    fn into_iter(self) -> Self::IntoIter {
92        self.data.iter()
93    }
94}
95
96impl<const N: usize> From<[u8; N]> for CharArray<N> {
97    fn from(data: [u8; N]) -> Self {
98        Self::new(data)
99    }
100}
101
102impl<const N: usize> From<CharArray<N>> for [u8; N] {
103    fn from(value: CharArray<N>) -> Self {
104        value.data
105    }
106}
107
108impl<const N: usize> From<&str> for CharArray<N> {
109    fn from(s: &str) -> Self {
110        let mut data = [0u8; N];
111        let bytes = s.as_bytes();
112        let len = bytes.len().min(N);
113        data[..len].copy_from_slice(&bytes[..len]);
114        Self::new(data)
115    }
116}
117
118impl<const N: usize> crate::utils::RustDefault for CharArray<N> {
119    #[inline(always)]
120    fn rust_default() -> Self {
121        Self::new([0u8; N])
122    }
123}
124
125#[cfg(feature = "serde")]
126impl<'de, const N: usize> Deserialize<'de> for CharArray<N> {
127    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
128    where
129        D: serde::Deserializer<'de>,
130    {
131        let data: [u8; N] = nulstr::deserialize(deserializer)?;
132        Ok(Self::new(data))
133    }
134}
135
136#[cfg(feature = "arbitrary")]
137impl<'a, const N: usize> Arbitrary<'a> for CharArray<N> {
138    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
139        let mut data = [0u8; N];
140
141        for b in &mut data {
142            // Take a char from the printable ASCII range.
143            *b = u.int_in_range(32..=126)?;
144        }
145
146        Ok(CharArray::new(data))
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::CharArray;
153
154    #[test]
155    fn char_array_to_str_handles_no_nulls() {
156        let data = *b"HELLOWORLD";
157        let ca = CharArray::new(data);
158        assert_eq!(ca.len(), 10);
159        assert_eq!(ca.to_str().unwrap(), "HELLOWORLD");
160    }
161
162    #[test]
163    fn char_array_to_str_trims_after_first_null() {
164        let mut data = [0u8; 10];
165        data[..3].copy_from_slice(b"abc");
166        // data[3..] are zeros
167        let ca = CharArray::new(data);
168        assert_eq!(ca.len(), 10);
169        assert_eq!(ca.to_str().unwrap(), "abc");
170    }
171
172    #[test]
173    fn char_array_from_str_into_str() {
174        let ca: CharArray<10> = "HELLOWORLD".into();
175        assert_eq!(ca.len(), 10);
176        assert_eq!(ca.to_str().unwrap(), "HELLOWORLD");
177
178        let ca: CharArray<10> = "abc".into();
179        assert_eq!(ca.len(), 10);
180        assert_eq!(ca.to_str().unwrap(), "abc");
181    }
182}