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        u.fill_buffer(&mut data)?;
141        Ok(CharArray::new(data))
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::CharArray;
148
149    #[test]
150    fn char_array_to_str_handles_no_nulls() {
151        let data = *b"HELLOWORLD";
152        let ca = CharArray::new(data);
153        assert_eq!(ca.len(), 10);
154        assert_eq!(ca.to_str().unwrap(), "HELLOWORLD");
155    }
156
157    #[test]
158    fn char_array_to_str_trims_after_first_null() {
159        let mut data = [0u8; 10];
160        data[..3].copy_from_slice(b"abc");
161        // data[3..] are zeros
162        let ca = CharArray::new(data);
163        assert_eq!(ca.len(), 10);
164        assert_eq!(ca.to_str().unwrap(), "abc");
165    }
166
167    #[test]
168    fn char_array_from_str_into_str() {
169        let ca: CharArray<10> = "HELLOWORLD".into();
170        assert_eq!(ca.len(), 10);
171        assert_eq!(ca.to_str().unwrap(), "HELLOWORLD");
172
173        let ca: CharArray<10> = "abc".into();
174        assert_eq!(ca.len(), 10);
175        assert_eq!(ca.to_str().unwrap(), "abc");
176    }
177}