Skip to content

Commit d5642f6

Browse files
committed
Add wrapper for char arrays to access str conveniently
This adds a wrapper around char arrays that allows to access them either as slices or as strings. When accessing the string representation, it will automatically convert the char array to a string slice up to the first null byte.
1 parent 85f6788 commit d5642f6

File tree

6 files changed

+179
-33
lines changed

6 files changed

+179
-33
lines changed

mavlink-bindgen/src/parser.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ impl MavProfile {
188188
#[allow(unused_imports)]
189189
use bitflags::bitflags;
190190

191-
use mavlink_core::{MavlinkVersion, Message, MessageData, bytes::Bytes, bytes_mut::BytesMut};
191+
use mavlink_core::{MavlinkVersion, Message, MessageData, bytes::Bytes, bytes_mut::BytesMut, types::CharArray};
192192

193193
#[cfg(feature = "serde")]
194194
use serde::{Serialize, Deserialize};
@@ -948,6 +948,7 @@ pub enum MavType {
948948
Char,
949949
Float,
950950
Double,
951+
CharArray(usize),
951952
Array(Box<MavType>, usize),
952953
}
953954

@@ -968,16 +969,18 @@ impl MavType {
968969
"float" => Some(Float),
969970
"Double" => Some(Double),
970971
"double" => Some(Double),
971-
_ => {
972-
if s.ends_with(']') {
973-
let start = s.find('[')?;
974-
let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
975-
let mtype = Self::parse_type(&s[0..start])?;
976-
Some(Array(Box::new(mtype), size))
977-
} else {
978-
None
979-
}
972+
_ if s.starts_with("char[") => {
973+
let start = s.find('[')?;
974+
let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
975+
Some(CharArray(size))
980976
}
977+
_ if s.ends_with(']') => {
978+
let start = s.find('[')?;
979+
let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
980+
let mtype = Self::parse_type(&s[0..start])?;
981+
Some(Array(Box::new(mtype), size))
982+
}
983+
_ => None,
981984
}
982985
}
983986

@@ -997,6 +1000,15 @@ impl MavType {
9971000
Int64 => quote! {#val = #buf.get_i64_le();},
9981001
Float => quote! {#val = #buf.get_f32_le();},
9991002
Double => quote! {#val = #buf.get_f64_le();},
1003+
CharArray(size) => {
1004+
quote! {
1005+
let mut tmp = [0_u8; #size];
1006+
for v in &mut tmp {
1007+
*v = #buf.get_u8();
1008+
}
1009+
#val = CharArray::new(tmp);
1010+
}
1011+
}
10001012
Array(t, _) => {
10011013
let r = t.rust_reader(&quote!(let val), buf);
10021014
quote! {
@@ -1025,6 +1037,14 @@ impl MavType {
10251037
UInt64 => quote! {#buf.put_u64_le(#val);},
10261038
Int64 => quote! {#buf.put_i64_le(#val);},
10271039
Double => quote! {#buf.put_f64_le(#val);},
1040+
CharArray(_) => {
1041+
let w = Char.rust_writer(&quote!(*val), buf);
1042+
quote! {
1043+
for val in &#val {
1044+
#w
1045+
}
1046+
}
1047+
}
10281048
Array(t, _size) => {
10291049
let w = t.rust_writer(&quote!(*val), buf);
10301050
quote! {
@@ -1044,6 +1064,7 @@ impl MavType {
10441064
UInt16 | Int16 => 2,
10451065
UInt32 | Int32 | Float => 4,
10461066
UInt64 | Int64 | Double => 8,
1067+
CharArray(size) => *size,
10471068
Array(t, size) => t.len() * size,
10481069
}
10491070
}
@@ -1052,7 +1073,7 @@ impl MavType {
10521073
fn order_len(&self) -> usize {
10531074
use self::MavType::*;
10541075
match self {
1055-
UInt8MavlinkVersion | UInt8 | Int8 | Char => 1,
1076+
UInt8MavlinkVersion | UInt8 | Int8 | Char | CharArray(_) => 1,
10561077
UInt16 | Int16 => 2,
10571078
UInt32 | Int32 | Float => 4,
10581079
UInt64 | Int64 | Double => 8,
@@ -1076,6 +1097,7 @@ impl MavType {
10761097
UInt64 => "uint64_t".into(),
10771098
Int64 => "int64_t".into(),
10781099
Double => "double".into(),
1100+
CharArray(_) => "char".into(),
10791101
Array(t, _) => t.primitive_type(),
10801102
}
10811103
}
@@ -1096,6 +1118,7 @@ impl MavType {
10961118
UInt64 => "u64".into(),
10971119
Int64 => "i64".into(),
10981120
Double => "f64".into(),
1121+
CharArray(size) => format!("CharArray<{}>", size),
10991122
Array(t, size) => format!("[{};{}]", t.rust_type(), size),
11001123
}
11011124
}
@@ -1114,6 +1137,7 @@ impl MavType {
11141137
UInt64 => quote!(0_u64),
11151138
Int64 => quote!(0_i64),
11161139
Double => quote!(0.0_f64),
1140+
CharArray(size) => quote!(CharArray::new([0_u8; #size])),
11171141
Array(ty, size) => {
11181142
let default_value = ty.emit_default_value();
11191143
quote!([#default_value; #size])
@@ -1564,7 +1588,7 @@ pub fn extra_crc(msg: &MavMessage) -> u8 {
15641588
crc.digest(field.name.as_bytes());
15651589
}
15661590
crc.digest(b" ");
1567-
if let MavType::Array(_, size) = field.mavtype {
1591+
if let MavType::Array(_, size) | MavType::CharArray(size) = field.mavtype {
15681592
crc.digest(&[size as u8]);
15691593
}
15701594
}

mavlink-bindgen/tests/snapshots/[email protected]

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ expression: contents
99
use arbitrary::Arbitrary;
1010
#[allow(unused_imports)]
1111
use bitflags::bitflags;
12-
use mavlink_core::{bytes::Bytes, bytes_mut::BytesMut, MavlinkVersion, Message, MessageData};
12+
use mavlink_core::{
13+
bytes::Bytes, bytes_mut::BytesMut, types::CharArray, MavlinkVersion, Message, MessageData,
14+
};
1315
#[allow(unused_imports)]
1416
use num_derive::FromPrimitive;
1517
#[allow(unused_imports)]

mavlink-bindgen/tests/snapshots/[email protected]

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ expression: contents
99
use arbitrary::Arbitrary;
1010
#[allow(unused_imports)]
1111
use bitflags::bitflags;
12-
use mavlink_core::{bytes::Bytes, bytes_mut::BytesMut, MavlinkVersion, Message, MessageData};
12+
use mavlink_core::{
13+
bytes::Bytes, bytes_mut::BytesMut, types::CharArray, MavlinkVersion, Message, MessageData,
14+
};
1315
#[allow(unused_imports)]
1416
use num_derive::FromPrimitive;
1517
#[allow(unused_imports)]
@@ -123,16 +125,15 @@ pub struct PARAM_REQUEST_READ_DATA {
123125
pub param_index: i16,
124126
pub target_system: u8,
125127
pub target_component: u8,
126-
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
127-
pub param_id: [u8; 16],
128+
pub param_id: CharArray<16>,
128129
}
129130
impl PARAM_REQUEST_READ_DATA {
130131
pub const ENCODED_LEN: usize = 20usize;
131132
pub const DEFAULT: Self = Self {
132133
param_index: 0_i16,
133134
target_system: 0_u8,
134135
target_component: 0_u8,
135-
param_id: [0_u8; 16usize],
136+
param_id: CharArray::new([0_u8; 16usize]),
136137
};
137138
#[cfg(feature = "arbitrary")]
138139
pub fn random<R: rand::RngCore>(rng: &mut R) -> Self {
@@ -170,10 +171,11 @@ impl MessageData for PARAM_REQUEST_READ_DATA {
170171
__struct.param_index = buf.get_i16_le();
171172
__struct.target_system = buf.get_u8();
172173
__struct.target_component = buf.get_u8();
173-
for v in &mut __struct.param_id {
174-
let val = buf.get_u8();
175-
*v = val;
174+
let mut tmp = [0_u8; 16usize];
175+
for v in &mut tmp {
176+
*v = buf.get_u8();
176177
}
178+
__struct.param_id = CharArray::new(tmp);
177179
Ok(__struct)
178180
}
179181
fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
@@ -208,8 +210,7 @@ pub struct PARAM_SET_DATA {
208210
pub param_value: f32,
209211
pub target_system: u8,
210212
pub target_component: u8,
211-
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
212-
pub param_id: [u8; 16],
213+
pub param_id: CharArray<16>,
213214
pub param_type: MavParamType,
214215
}
215216
impl PARAM_SET_DATA {
@@ -218,7 +219,7 @@ impl PARAM_SET_DATA {
218219
param_value: 0.0_f32,
219220
target_system: 0_u8,
220221
target_component: 0_u8,
221-
param_id: [0_u8; 16usize],
222+
param_id: CharArray::new([0_u8; 16usize]),
222223
param_type: MavParamType::DEFAULT,
223224
};
224225
#[cfg(feature = "arbitrary")]
@@ -257,10 +258,11 @@ impl MessageData for PARAM_SET_DATA {
257258
__struct.param_value = buf.get_f32_le();
258259
__struct.target_system = buf.get_u8();
259260
__struct.target_component = buf.get_u8();
260-
for v in &mut __struct.param_id {
261-
let val = buf.get_u8();
262-
*v = val;
261+
let mut tmp = [0_u8; 16usize];
262+
for v in &mut tmp {
263+
*v = buf.get_u8();
263264
}
265+
__struct.param_id = CharArray::new(tmp);
264266
let tmp = buf.get_u8();
265267
__struct.param_type =
266268
FromPrimitive::from_u8(tmp).ok_or(::mavlink_core::error::ParserError::InvalidEnum {
@@ -302,8 +304,7 @@ pub struct PARAM_VALUE_DATA {
302304
pub param_value: f32,
303305
pub param_count: u16,
304306
pub param_index: u16,
305-
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
306-
pub param_id: [u8; 16],
307+
pub param_id: CharArray<16>,
307308
pub param_type: MavParamType,
308309
}
309310
impl PARAM_VALUE_DATA {
@@ -312,7 +313,7 @@ impl PARAM_VALUE_DATA {
312313
param_value: 0.0_f32,
313314
param_count: 0_u16,
314315
param_index: 0_u16,
315-
param_id: [0_u8; 16usize],
316+
param_id: CharArray::new([0_u8; 16usize]),
316317
param_type: MavParamType::DEFAULT,
317318
};
318319
#[cfg(feature = "arbitrary")]
@@ -351,10 +352,11 @@ impl MessageData for PARAM_VALUE_DATA {
351352
__struct.param_value = buf.get_f32_le();
352353
__struct.param_count = buf.get_u16_le();
353354
__struct.param_index = buf.get_u16_le();
354-
for v in &mut __struct.param_id {
355-
let val = buf.get_u8();
356-
*v = val;
355+
let mut tmp = [0_u8; 16usize];
356+
for v in &mut tmp {
357+
*v = buf.get_u8();
357358
}
359+
__struct.param_id = CharArray::new(tmp);
358360
let tmp = buf.get_u8();
359361
__struct.param_type =
360362
FromPrimitive::from_u8(tmp).ok_or(::mavlink_core::error::ParserError::InvalidEnum {

mavlink-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub mod bytes_mut;
4848
#[cfg(feature = "std")]
4949
mod connection;
5050
pub mod error;
51+
pub mod types;
5152
#[cfg(feature = "std")]
5253
pub use self::connection::{connect, Connectable, MavConnection};
5354

mavlink-core/src/types.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use core::ops::{Deref, DerefMut};
2+
3+
#[cfg(feature = "serde")]
4+
use serde::{Deserialize, Serialize};
5+
6+
/// Abstraction around a byte array that represents a string.
7+
///
8+
/// MAVLink encodes strings as C char arrays and the handling is field dependent.
9+
/// This abstration allows to choose if one wants to handle the field as
10+
/// a raw byte array or if one wants the convenience of a str that stops at the first null byte.
11+
///
12+
/// # Example
13+
/// ```
14+
/// use mavlink_core::types::CharArray;
15+
///
16+
/// let data = [0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x00, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x00, 0x00, 0x00];
17+
/// let ca = CharArray::new(data);
18+
/// assert_eq!(ca.to_str(), "HELLO");
19+
/// ```
20+
#[derive(Debug, PartialEq, Clone)]
21+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22+
#[cfg_attr(feature = "serde", serde(transparent))]
23+
pub struct CharArray<const N: usize> {
24+
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
25+
data: [u8; N],
26+
27+
#[cfg_attr(feature = "serde", serde(skip))]
28+
str_len: usize,
29+
}
30+
31+
impl<const N: usize> CharArray<N> {
32+
pub const fn new(data: [u8; N]) -> Self {
33+
// Note: The generated code uses this in const contexts, so this is a const fn
34+
// and so we can't use iterators or other fancy stuff unfortunately.
35+
let mut first_null = N;
36+
let mut i = 0;
37+
loop {
38+
if i >= N {
39+
break;
40+
}
41+
if data[i] == 0 {
42+
first_null = i;
43+
break;
44+
}
45+
i += 1;
46+
}
47+
Self {
48+
data,
49+
str_len: first_null,
50+
}
51+
}
52+
53+
/// Get the string representation of the char array.
54+
/// Returns the string stopping at the first null byte and if the string is not valid utf8
55+
/// the returned string will be empty.
56+
pub fn to_str(&self) -> &str {
57+
std::str::from_utf8(&self.data[..self.str_len]).unwrap_or("")
58+
}
59+
}
60+
61+
impl<const N: usize> Deref for CharArray<N> {
62+
type Target = [u8; N];
63+
64+
fn deref(&self) -> &Self::Target {
65+
&self.data
66+
}
67+
}
68+
69+
impl<const N: usize> DerefMut for CharArray<N> {
70+
fn deref_mut(&mut self) -> &mut Self::Target {
71+
&mut self.data
72+
}
73+
}
74+
75+
impl<'a, const N: usize> IntoIterator for &'a CharArray<N> {
76+
type Item = &'a u8;
77+
type IntoIter = core::slice::Iter<'a, u8>;
78+
79+
fn into_iter(self) -> Self::IntoIter {
80+
self.data.iter()
81+
}
82+
}
83+
84+
impl<const N: usize> From<[u8; N]> for CharArray<N> {
85+
fn from(data: [u8; N]) -> Self {
86+
Self::new(data)
87+
}
88+
}
89+
90+
impl<const N: usize> From<CharArray<N>> for [u8; N] {
91+
fn from(value: CharArray<N>) -> Self {
92+
value.data
93+
}
94+
}
95+
96+
#[cfg(test)]
97+
mod tests {
98+
use super::CharArray;
99+
100+
#[test]
101+
fn char_array_to_str_handles_no_nulls() {
102+
let data = *b"HELLOWORLD";
103+
let ca = CharArray::new(data);
104+
assert_eq!(ca.len(), 10);
105+
assert_eq!(ca.to_str(), "HELLOWORLD");
106+
}
107+
108+
#[test]
109+
fn char_array_to_str_trims_after_first_null() {
110+
let mut data = [0u8; 10];
111+
data[..3].copy_from_slice(b"abc");
112+
// data[3..] are zeros
113+
let ca = CharArray::new(data);
114+
assert_eq!(ca.len(), 10);
115+
assert_eq!(ca.to_str(), "abc");
116+
}
117+
}

mavlink/tests/v2_encode_decode_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ mod test_v2_encode_decode {
208208
),
209209
};
210210

211-
let param_id = String::from_utf8(param_value.param_id[..11].to_vec()).unwrap();
211+
let param_id = param_value.param_id.to_str();
212212
assert_eq!(param_id, "_HASH_CHECK");
213213
assert_eq!(
214214
param_value.param_type,

0 commit comments

Comments
 (0)