Skip to content

Commit cae26a9

Browse files
authored
Check field count (#395)
* fix generation of message fields without description * simplify MavField initialisation (fix clippy warning) * add message field count check
1 parent 2bf2ac2 commit cae26a9

File tree

5 files changed

+335
-2
lines changed

5 files changed

+335
-2
lines changed

mavlink-bindgen/src/parser.rs

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,20 @@ impl MavMessage {
907907
);
908908
}
909909
}
910+
911+
/// Ensure that the fields count is at least one and no more than 64
912+
fn validate_field_count(&self) {
913+
assert!(
914+
!self.fields.is_empty(),
915+
"Message '{}' does not any fields",
916+
self.name
917+
);
918+
assert!(
919+
self.fields.len() <= 64,
920+
"Message '{}' has more then 64 fields",
921+
self.name
922+
);
923+
}
910924
}
911925

912926
#[derive(Debug, PartialEq, Clone, Default)]
@@ -1424,8 +1438,10 @@ pub fn parse_profile(
14241438
message = MavMessage::default();
14251439
}
14261440
MavXmlElement::Field => {
1427-
field = MavField::default();
1428-
field.is_extension = is_in_extension;
1441+
field = MavField {
1442+
is_extension: is_in_extension,
1443+
..Default::default()
1444+
};
14291445
}
14301446
MavXmlElement::Enum => {
14311447
mavenum = MavEnum::default();
@@ -1610,6 +1626,46 @@ pub fn parse_profile(
16101626
}
16111627
}
16121628
}
1629+
b"field" => {
1630+
let mut field = MavField {
1631+
is_extension: is_in_extension,
1632+
..Default::default()
1633+
};
1634+
for attr in bytes.attributes() {
1635+
let attr = attr.unwrap();
1636+
match attr.key.into_inner() {
1637+
b"name" => {
1638+
let name = String::from_utf8_lossy(&attr.value);
1639+
field.name = if name == "type" {
1640+
"mavtype".to_string()
1641+
} else {
1642+
name.to_string()
1643+
};
1644+
}
1645+
b"type" => {
1646+
let r#type = String::from_utf8_lossy(&attr.value);
1647+
field.mavtype = MavType::parse_type(&r#type).unwrap();
1648+
}
1649+
b"enum" => {
1650+
field.enumtype = Some(to_pascal_case(&attr.value));
1651+
1652+
// Update field display if enum is a bitmask
1653+
if let Some(e) = profile.enums.get(field.enumtype.as_ref().unwrap())
1654+
{
1655+
if e.bitmask {
1656+
field.display = Some("bitmask".to_string());
1657+
}
1658+
}
1659+
}
1660+
b"display" => {
1661+
field.display =
1662+
Some(String::from_utf8_lossy(&attr.value).to_string());
1663+
}
1664+
_ => (),
1665+
}
1666+
}
1667+
message.fields.push(field);
1668+
}
16131669
_ => (),
16141670
},
16151671
Ok(Event::Text(bytes)) => {
@@ -1691,6 +1747,8 @@ pub fn parse_profile(
16911747

16921748
// Validate there are no duplicate field names
16931749
msg.validate_unique_fields();
1750+
// Validate field count must be between 1 and 64
1751+
msg.validate_field_count();
16941752

16951753
profile.add_message(&msg);
16961754
}
@@ -2068,4 +2126,74 @@ mod tests {
20682126
// Should panic due to duplicate field names
20692127
msg.validate_unique_fields();
20702128
}
2129+
2130+
#[test]
2131+
fn validate_field_count_ok() {
2132+
let msg = MavMessage {
2133+
id: 2,
2134+
name: "FOO".to_string(),
2135+
description: None,
2136+
fields: vec![
2137+
MavField {
2138+
mavtype: MavType::UInt8,
2139+
name: "a".to_string(),
2140+
description: None,
2141+
enumtype: None,
2142+
display: None,
2143+
is_extension: false,
2144+
},
2145+
MavField {
2146+
mavtype: MavType::UInt8,
2147+
name: "b".to_string(),
2148+
description: None,
2149+
enumtype: None,
2150+
display: None,
2151+
is_extension: false,
2152+
},
2153+
],
2154+
deprecated: None,
2155+
};
2156+
// Should not panic
2157+
msg.validate_field_count();
2158+
}
2159+
2160+
#[test]
2161+
#[should_panic]
2162+
fn validate_field_count_too_many() {
2163+
let mut fields = vec![];
2164+
for i in 0..65 {
2165+
let field = MavField {
2166+
mavtype: MavType::UInt8,
2167+
name: format!("field_{i}"),
2168+
description: None,
2169+
enumtype: None,
2170+
display: None,
2171+
is_extension: false,
2172+
};
2173+
fields.push(field);
2174+
}
2175+
let msg = MavMessage {
2176+
id: 2,
2177+
name: "BAZ".to_string(),
2178+
description: None,
2179+
fields,
2180+
deprecated: None,
2181+
};
2182+
// Should panic due to 65 fields
2183+
msg.validate_field_count();
2184+
}
2185+
2186+
#[test]
2187+
#[should_panic]
2188+
fn validate_field_count_empty() {
2189+
let msg = MavMessage {
2190+
id: 2,
2191+
name: "BAM".to_string(),
2192+
description: None,
2193+
fields: vec![],
2194+
deprecated: None,
2195+
};
2196+
// Should panic due to no fields
2197+
msg.validate_field_count();
2198+
}
20712199
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<mavlink>
2+
<messages>
3+
<message id="50001" name="CUBEPILOT_RAW_RC">
4+
<description>Raw RC Data</description>
5+
<field type="uint8_t[32]" name="rc_raw"/>
6+
</message>
7+
</messages>
8+
</mavlink>

mavlink-bindgen/tests/e2e_snapshots.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,8 @@ fn snapshot_parameters() {
4141
fn snapshot_deprecated() {
4242
run_snapshot("deprecated.xml");
4343
}
44+
45+
#[test]
46+
fn snapshot_no_field_description() {
47+
run_snapshot("no_field_description.xml");
48+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
source: mavlink-bindgen/tests/e2e_snapshots.rs
3+
assertion_line: 26
4+
expression: contents
5+
---
6+
#[allow(non_camel_case_types)]
7+
#[allow(clippy::derive_partial_eq_without_eq)]
8+
#[allow(clippy::field_reassign_with_default)]
9+
#[allow(non_snake_case)]
10+
#[allow(clippy::unnecessary_cast)]
11+
#[allow(clippy::bad_bit_mask)]
12+
#[allow(clippy::suspicious_else_formatting)]
13+
#[cfg(feature = "no_field_description")]
14+
pub mod no_field_description;
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
---
2+
source: mavlink-bindgen/tests/e2e_snapshots.rs
3+
assertion_line: 26
4+
expression: contents
5+
---
6+
#![doc = "MAVLink no_field_description dialect."]
7+
#![doc = ""]
8+
#![doc = "This file was automatically generated, do not edit."]
9+
#![allow(deprecated)]
10+
#[cfg(feature = "arbitrary")]
11+
use arbitrary::Arbitrary;
12+
#[allow(unused_imports)]
13+
use bitflags::bitflags;
14+
use mavlink_core::{bytes::Bytes, bytes_mut::BytesMut, MavlinkVersion, Message, MessageData};
15+
#[allow(unused_imports)]
16+
use num_derive::FromPrimitive;
17+
#[allow(unused_imports)]
18+
use num_derive::ToPrimitive;
19+
#[allow(unused_imports)]
20+
use num_traits::FromPrimitive;
21+
#[allow(unused_imports)]
22+
use num_traits::ToPrimitive;
23+
#[cfg(feature = "serde")]
24+
use serde::{Deserialize, Serialize};
25+
#[derive(Debug, Clone, PartialEq)]
26+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27+
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
28+
pub struct CUBEPILOT_RAW_RC_DATA {
29+
#[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
30+
pub rc_raw: [u8; 32],
31+
}
32+
impl CUBEPILOT_RAW_RC_DATA {
33+
pub const ENCODED_LEN: usize = 32usize;
34+
pub const DEFAULT: Self = Self {
35+
rc_raw: [0_u8; 32usize],
36+
};
37+
#[cfg(feature = "arbitrary")]
38+
pub fn random<R: rand::RngCore>(rng: &mut R) -> Self {
39+
use arbitrary::{Arbitrary, Unstructured};
40+
let mut buf = [0u8; 1024];
41+
rng.fill_bytes(&mut buf);
42+
let mut unstructured = Unstructured::new(&buf);
43+
Self::arbitrary(&mut unstructured).unwrap_or_default()
44+
}
45+
}
46+
impl Default for CUBEPILOT_RAW_RC_DATA {
47+
fn default() -> Self {
48+
Self::DEFAULT.clone()
49+
}
50+
}
51+
impl MessageData for CUBEPILOT_RAW_RC_DATA {
52+
type Message = MavMessage;
53+
const ID: u32 = 50001u32;
54+
const NAME: &'static str = "CUBEPILOT_RAW_RC";
55+
const EXTRA_CRC: u8 = 246u8;
56+
const ENCODED_LEN: usize = 32usize;
57+
fn deser(
58+
_version: MavlinkVersion,
59+
__input: &[u8],
60+
) -> Result<Self, ::mavlink_core::error::ParserError> {
61+
let avail_len = __input.len();
62+
let mut payload_buf = [0; Self::ENCODED_LEN];
63+
let mut buf = if avail_len < Self::ENCODED_LEN {
64+
payload_buf[0..avail_len].copy_from_slice(__input);
65+
Bytes::new(&payload_buf)
66+
} else {
67+
Bytes::new(__input)
68+
};
69+
let mut __struct = Self::default();
70+
for v in &mut __struct.rc_raw {
71+
let val = buf.get_u8();
72+
*v = val;
73+
}
74+
Ok(__struct)
75+
}
76+
fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
77+
let mut __tmp = BytesMut::new(bytes);
78+
#[allow(clippy::absurd_extreme_comparisons)]
79+
#[allow(unused_comparisons)]
80+
if __tmp.remaining() < Self::ENCODED_LEN {
81+
panic!(
82+
"buffer is too small (need {} bytes, but got {})",
83+
Self::ENCODED_LEN,
84+
__tmp.remaining(),
85+
)
86+
}
87+
for val in &self.rc_raw {
88+
__tmp.put_u8(*val);
89+
}
90+
if matches!(version, MavlinkVersion::V2) {
91+
let len = __tmp.len();
92+
::mavlink_core::utils::remove_trailing_zeroes(&bytes[..len])
93+
} else {
94+
__tmp.len()
95+
}
96+
}
97+
}
98+
#[derive(Clone, PartialEq, Debug)]
99+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100+
#[cfg_attr(feature = "serde", serde(tag = "type"))]
101+
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
102+
#[repr(u32)]
103+
pub enum MavMessage {
104+
CUBEPILOT_RAW_RC(CUBEPILOT_RAW_RC_DATA),
105+
}
106+
impl MavMessage {
107+
pub const fn all_ids() -> &'static [u32] {
108+
&[50001u32]
109+
}
110+
}
111+
impl Message for MavMessage {
112+
fn parse(
113+
version: MavlinkVersion,
114+
id: u32,
115+
payload: &[u8],
116+
) -> Result<Self, ::mavlink_core::error::ParserError> {
117+
match id {
118+
CUBEPILOT_RAW_RC_DATA::ID => {
119+
CUBEPILOT_RAW_RC_DATA::deser(version, payload).map(Self::CUBEPILOT_RAW_RC)
120+
}
121+
_ => Err(::mavlink_core::error::ParserError::UnknownMessage { id }),
122+
}
123+
}
124+
fn message_name(&self) -> &'static str {
125+
match self {
126+
Self::CUBEPILOT_RAW_RC(..) => CUBEPILOT_RAW_RC_DATA::NAME,
127+
}
128+
}
129+
fn message_id(&self) -> u32 {
130+
match self {
131+
Self::CUBEPILOT_RAW_RC(..) => CUBEPILOT_RAW_RC_DATA::ID,
132+
}
133+
}
134+
fn message_id_from_name(name: &str) -> Option<u32> {
135+
match name {
136+
CUBEPILOT_RAW_RC_DATA::NAME => Some(CUBEPILOT_RAW_RC_DATA::ID),
137+
_ => None,
138+
}
139+
}
140+
fn default_message_from_id(id: u32) -> Option<Self> {
141+
match id {
142+
CUBEPILOT_RAW_RC_DATA::ID => {
143+
Some(Self::CUBEPILOT_RAW_RC(CUBEPILOT_RAW_RC_DATA::default()))
144+
}
145+
_ => None,
146+
}
147+
}
148+
#[cfg(feature = "arbitrary")]
149+
fn random_message_from_id<R: rand::RngCore>(id: u32, rng: &mut R) -> Option<Self> {
150+
match id {
151+
CUBEPILOT_RAW_RC_DATA::ID => {
152+
Some(Self::CUBEPILOT_RAW_RC(CUBEPILOT_RAW_RC_DATA::random(rng)))
153+
}
154+
_ => None,
155+
}
156+
}
157+
fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
158+
match self {
159+
Self::CUBEPILOT_RAW_RC(body) => body.ser(version, bytes),
160+
}
161+
}
162+
fn extra_crc(id: u32) -> u8 {
163+
match id {
164+
CUBEPILOT_RAW_RC_DATA::ID => CUBEPILOT_RAW_RC_DATA::EXTRA_CRC,
165+
_ => 0,
166+
}
167+
}
168+
fn target_system_id(&self) -> Option<u8> {
169+
match self {
170+
_ => None,
171+
}
172+
}
173+
fn target_component_id(&self) -> Option<u8> {
174+
match self {
175+
_ => None,
176+
}
177+
}
178+
}

0 commit comments

Comments
 (0)