1use crc_any::CRCu16;
2use std::cmp::Ordering;
3use std::collections::btree_map::Entry;
4use std::collections::{BTreeMap, HashSet};
5use std::default::Default;
6use std::fmt::Display;
7use std::io::Write;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use std::sync::LazyLock;
11
12use regex::Regex;
13
14use quick_xml::{Reader, escape::resolve_xml_entity, events::Event};
15
16use proc_macro2::{Ident, Literal, TokenStream};
17use quote::{format_ident, quote};
18
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22use crate::error::BindGenError;
23use crate::util;
24
25static URL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
26 Regex::new(concat!(
27 r"(https?://", r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", r"[a-zA-Z]{2,63}", r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*[-a-zA-Z0-9@:%_\+~#?&/=])?)", ))
32 .expect("failed to build regex")
33});
34
35#[derive(Debug, PartialEq, Clone, Default)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub struct MavProfile {
38 pub messages: BTreeMap<String, MavMessage>,
39 pub enums: BTreeMap<String, MavEnum>,
40 pub version: Option<u8>,
41 pub dialect: Option<u8>,
42}
43
44impl MavProfile {
45 fn add_message(&mut self, message: &MavMessage) {
46 match self.messages.entry(message.name.clone()) {
47 Entry::Occupied(entry) => {
48 assert!(
49 entry.get() == message,
50 "Message '{}' defined twice but definitions are different",
51 message.name
52 );
53 }
54 Entry::Vacant(entry) => {
55 entry.insert(message.clone());
56 }
57 }
58 }
59
60 fn add_enum(&mut self, enm: &MavEnum) {
61 match self.enums.entry(enm.name.clone()) {
62 Entry::Occupied(entry) => {
63 entry.into_mut().try_combine(enm);
64 }
65 Entry::Vacant(entry) => {
66 entry.insert(enm.clone());
67 }
68 }
69 }
70
71 fn update_enums(mut self) -> Self {
74 for msg in self.messages.values_mut() {
75 for field in &mut msg.fields {
76 if let Some(enum_name) = &field.enumtype {
77 if let Some(enm) = self.enums.get_mut(enum_name) {
79 if field.display == Some("bitmask".to_string()) {
81 enm.bitmask = true;
82 }
83
84 if enm.bitmask {
86 let mut any_fit = false;
88 let mut all_fit = true;
89 for entry in &enm.entries {
90 if field.mavtype.max_int_value() < entry.value.unwrap_or_default() {
91 field.is_undersized = true;
92 enm.additional_primitives.insert(field.mavtype.clone());
93 all_fit = false;
94 } else {
95 any_fit = true;
96 }
97 }
98 assert!(
99 any_fit,
100 "bitflag enum field {} of {} must be able to fit at least one possible value {}",
101 field.name, msg.name, enum_name,
102 );
103
104 if all_fit {
105 enm.primitive = Some(field.mavtype.clone());
106 }
107
108 if field.display.is_none() {
110 field.display = Some("bitmask".to_string());
111 }
112 }
113 }
114 }
115 }
116 }
117 for enm in self.enums.values_mut() {
118 if enm.bitmask && enm.primitive.is_none() {
119 let max = enm
120 .entries
121 .iter()
122 .filter_map(|e| e.value)
123 .max()
124 .unwrap_or_default();
125 let primitive = match max {
126 max if max <= u16::MAX as u64 => MavType::UInt16,
127 max if max <= u32::MAX as u64 => MavType::UInt32,
128 _ => MavType::UInt64,
129 };
130 enm.primitive = Some(primitive);
131 }
132 }
133 self
134 }
135
136 #[inline(always)]
138 fn emit_comments(&self, dialect_name: &str) -> TokenStream {
139 let message = format!("MAVLink {dialect_name} dialect.");
140 quote!(
141 #![doc = #message]
142 #![doc = ""]
143 #![doc = "This file was automatically generated, do not edit."]
144 )
145 }
146
147 #[inline(always)]
149 fn emit_msgs(&self) -> Vec<TokenStream> {
150 self.messages
151 .values()
152 .map(|d| d.emit_rust(self.version.is_some()))
153 .collect()
154 }
155
156 #[inline(always)]
158 fn emit_enums(&self) -> Vec<TokenStream> {
159 self.enums.values().map(|d| d.emit_rust()).collect()
160 }
161
162 #[inline(always)]
163 fn emit_deprecations(&self) -> Vec<TokenStream> {
164 self.messages
165 .values()
166 .map(|msg| {
167 msg.deprecated
168 .as_ref()
169 .map(|d| d.emit_tokens())
170 .unwrap_or_default()
171 })
172 .collect()
173 }
174
175 #[inline(always)]
177 fn emit_enum_names(&self) -> Vec<TokenStream> {
178 self.messages
179 .values()
180 .map(|msg| {
181 let name = format_ident!("{}", msg.name);
182 quote!(#name)
183 })
184 .collect()
185 }
186
187 #[inline(always)]
189 fn emit_struct_names(&self) -> Vec<TokenStream> {
190 self.messages
191 .values()
192 .map(|msg| msg.emit_struct_name())
193 .collect()
194 }
195
196 fn emit_rust(&self, dialect_name: &str) -> TokenStream {
197 let id_width = format_ident!("u32");
198
199 let comment = self.emit_comments(dialect_name);
200 let mav_minor_version = self.emit_minor_version();
201 let mav_dialect_number = self.emit_dialect_number();
202 let msgs = self.emit_msgs();
203 let deprecations = self.emit_deprecations();
204 let enum_names = self.emit_enum_names();
205 let struct_names = self.emit_struct_names();
206 let enums = self.emit_enums();
207
208 let variant_docs = self.emit_variant_description();
209
210 let mav_message =
211 self.emit_mav_message(&variant_docs, &deprecations, &enum_names, &struct_names);
212 let mav_message_all_ids = self.emit_mav_message_all_ids();
213 let mav_message_all_messages = self.emit_mav_message_all_messages();
214 let mav_message_parse = self.emit_mav_message_parse(&enum_names, &struct_names);
215 let mav_message_crc = self.emit_mav_message_crc(&id_width, &struct_names);
216 let mav_message_name = self.emit_mav_message_name(&enum_names, &struct_names);
217 let mav_message_id = self.emit_mav_message_id(&enum_names, &struct_names);
218 let mav_message_id_from_name = self.emit_mav_message_id_from_name(&struct_names);
219 let mav_message_default_from_id =
220 self.emit_mav_message_default_from_id(&enum_names, &struct_names);
221 let mav_message_random_from_id =
222 self.emit_mav_message_random_from_id(&enum_names, &struct_names);
223 let mav_message_serialize = self.emit_mav_message_serialize(&enum_names);
224 let mav_message_target_system_and_component = self.emit_mav_message_target_accessors();
225
226 quote! {
227 #comment
228 #![allow(deprecated)]
229 #![allow(clippy::match_single_binding)]
230 #![allow(rustdoc::broken_intra_doc_links)]
231 #[allow(unused_imports)]
232 use num_derive::{FromPrimitive, ToPrimitive};
233 #[allow(unused_imports)]
234 use num_traits::{FromPrimitive, ToPrimitive};
235 #[allow(unused_imports)]
236 use bitflags::{bitflags, Flags};
237 #[allow(unused_imports)]
238 use mavlink_core::{MavlinkVersion, Message, MessageData, bytes::Bytes, bytes_mut::BytesMut, types::CharArray};
239
240 #[cfg(feature = "serde")]
241 use serde::{Serialize, Deserialize};
242
243 #[cfg(feature = "arbitrary")]
244 use arbitrary::Arbitrary;
245
246 #[cfg(feature = "ts-rs")]
247 use ts_rs::TS;
248
249 #mav_minor_version
250 #mav_dialect_number
251
252 #(#enums)*
253
254 #(#msgs)*
255
256 #[derive(Clone, PartialEq, Debug)]
257 #mav_message
258
259 impl MavMessage {
260 #mav_message_all_ids
261 #mav_message_all_messages
262 }
263
264 impl Message for MavMessage {
265 #mav_message_parse
266 #mav_message_name
267 #mav_message_id
268 #mav_message_id_from_name
269 #mav_message_default_from_id
270 #mav_message_random_from_id
271 #mav_message_serialize
272 #mav_message_crc
273 #mav_message_target_system_and_component
274 }
275 }
276 }
277
278 #[inline(always)]
279 fn emit_mav_message(
280 &self,
281 docs: &[TokenStream],
282 deprecations: &[TokenStream],
283 enums: &[TokenStream],
284 structs: &[TokenStream],
285 ) -> TokenStream {
286 quote! {
287 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
288 #[cfg_attr(feature = "serde", serde(tag = "type"))]
289 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
290 #[cfg_attr(feature = "ts-rs", derive(TS))]
291 #[cfg_attr(feature = "ts-rs", ts(export))]
292 #[repr(u32)]
293 pub enum MavMessage {
294 #(#docs #deprecations #enums(#structs),)*
295 }
296 }
297 }
298
299 fn emit_variant_description(&self) -> Vec<TokenStream> {
300 self.messages
301 .values()
302 .map(|msg| {
303 let mut ts = TokenStream::new();
304
305 if let Some(doc) = msg.description.as_ref() {
306 let doc = format!("{doc}{}", if doc.ends_with('.') { "" } else { "." });
307 let doc = URL_REGEX.replace_all(&doc, "<$1>");
308 ts.extend(quote!(#[doc = #doc]));
309
310 ts.extend(quote!(#[doc = ""]));
312 }
313
314 let id = format!("ID: {}", msg.id);
315 ts.extend(quote!(#[doc = #id]));
316
317 ts
318 })
319 .collect()
320 }
321
322 #[inline(always)]
323 fn emit_mav_message_all_ids(&self) -> TokenStream {
324 let mut message_ids = self.messages.values().map(|m| m.id).collect::<Vec<u32>>();
325 message_ids.sort();
326
327 quote!(
328 pub const fn all_ids() -> &'static [u32] {
329 &[#(#message_ids),*]
330 }
331 )
332 }
333
334 #[inline(always)]
335 fn emit_minor_version(&self) -> TokenStream {
336 if let Some(version) = self.version {
337 quote! (pub const MINOR_MAVLINK_VERSION: u8 = #version;)
338 } else {
339 TokenStream::default()
340 }
341 }
342
343 #[inline(always)]
344 fn emit_dialect_number(&self) -> TokenStream {
345 if let Some(dialect) = self.dialect {
346 quote! (pub const DIALECT_NUMBER: u8 = #dialect;)
347 } else {
348 TokenStream::default()
349 }
350 }
351
352 #[inline(always)]
353 fn emit_mav_message_parse(
354 &self,
355 enums: &[TokenStream],
356 structs: &[TokenStream],
357 ) -> TokenStream {
358 let id_width = format_ident!("u32");
359
360 quote! {
361 fn parse(version: MavlinkVersion, id: #id_width, payload: &[u8]) -> Result<Self, ::mavlink_core::error::ParserError> {
362 match id {
363 #(#structs::ID => #structs::deser(version, payload).map(Self::#enums),)*
364 _ => {
365 Err(::mavlink_core::error::ParserError::UnknownMessage { id })
366 },
367 }
368 }
369 }
370 }
371
372 #[inline(always)]
373 fn emit_mav_message_crc(&self, id_width: &Ident, structs: &[TokenStream]) -> TokenStream {
374 quote! {
375 fn extra_crc(id: #id_width) -> u8 {
376 match id {
377 #(#structs::ID => #structs::EXTRA_CRC,)*
378 _ => {
379 0
380 },
381 }
382 }
383 }
384 }
385
386 #[inline(always)]
387 fn emit_mav_message_name(&self, enums: &[TokenStream], structs: &[TokenStream]) -> TokenStream {
388 quote! {
389 fn message_name(&self) -> &'static str {
390 match self {
391 #(Self::#enums(..) => #structs::NAME,)*
392 }
393 }
394 }
395 }
396
397 #[inline(always)]
398 fn emit_mav_message_id(&self, enums: &[TokenStream], structs: &[TokenStream]) -> TokenStream {
399 let id_width = format_ident!("u32");
400 quote! {
401 fn message_id(&self) -> #id_width {
402 match self {
403 #(Self::#enums(..) => #structs::ID,)*
404 }
405 }
406 }
407 }
408
409 #[inline(always)]
410 fn emit_mav_message_id_from_name(&self, structs: &[TokenStream]) -> TokenStream {
411 quote! {
412 fn message_id_from_name(name: &str) -> Option<u32> {
413 match name {
414 #(#structs::NAME => Some(#structs::ID),)*
415 _ => {
416 None
417 }
418 }
419 }
420 }
421 }
422
423 #[inline(always)]
424 fn emit_mav_message_default_from_id(
425 &self,
426 enums: &[TokenStream],
427 structs: &[TokenStream],
428 ) -> TokenStream {
429 quote! {
430 fn default_message_from_id(id: u32) -> Option<Self> {
431 match id {
432 #(#structs::ID => Some(Self::#enums(#structs::default())),)*
433 _ => {
434 None
435 }
436 }
437 }
438 }
439 }
440
441 #[inline(always)]
442 fn emit_mav_message_random_from_id(
443 &self,
444 enums: &[TokenStream],
445 structs: &[TokenStream],
446 ) -> TokenStream {
447 quote! {
448 #[cfg(feature = "arbitrary")]
449 fn random_message_from_id<R: rand::Rng>(id: u32, rng: &mut R) -> Option<Self> {
450 match id {
451 #(#structs::ID => Some(Self::#enums(#structs::random(rng))),)*
452 _ => None,
453 }
454 }
455 }
456 }
457
458 #[inline(always)]
459 fn emit_mav_message_serialize(&self, enums: &Vec<TokenStream>) -> TokenStream {
460 quote! {
461 fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
462 match self {
463 #(Self::#enums(body) => body.ser(version, bytes),)*
464 }
465 }
466 }
467 }
468
469 #[inline(always)]
470 fn emit_mav_message_target_accessors(&self) -> TokenStream {
471 const TARGET_SYSTEM: &str = "target_system";
472 const TARGET_COMPONENT: &str = "target_component";
473 const TARGET_FIELDS: [&str; 2] = [TARGET_SYSTEM, TARGET_COMPONENT];
474
475 fn is_target_system_field(message: &MavMessage, field: &MavField) -> bool {
476 matches!(
477 (message.name.as_str(), field.name.as_str()),
478 (_, TARGET_SYSTEM) | ("MANUAL_CONTROL", "target")
479 )
480 }
481
482 let accessors: Vec<TokenStream> = TARGET_FIELDS
483 .iter()
484 .map(|target_field| {
485 let getter = format_ident!("{}_id", target_field);
486 let getter_mut = format_ident!("{}_id_mut", target_field);
487 let fields: Vec<(Ident, Ident)> = self
488 .messages
489 .values()
490 .filter_map(|msg| {
491 msg.fields
492 .iter()
493 .find(|f| match *target_field {
494 TARGET_SYSTEM => is_target_system_field(msg, f),
495 TARGET_COMPONENT => f.name == TARGET_COMPONENT,
496 _ => false,
497 })
498 .map(|f| (format_ident!("{}", msg.name), format_ident!("{}", f.name)))
499 })
500 .collect();
501 let getter_arms: Vec<TokenStream> = fields
502 .iter()
503 .map(|(variant, field)| quote!(Self::#variant(inner) => Some(inner.#field),))
504 .collect();
505 let getter_mut_arms: Vec<TokenStream> = fields
506 .iter()
507 .map(|(variant, field)| {
508 quote!(Self::#variant(inner) => Some(&mut inner.#field),)
509 })
510 .collect();
511
512 quote! {
513 fn #getter(&self) -> Option<u8> {
514 match self {
515 #(#getter_arms)*
516 _ => None,
517 }
518 }
519
520 fn #getter_mut(&mut self) -> Option<&mut u8> {
521 match self {
522 #(#getter_mut_arms)*
523 _ => None,
524 }
525 }
526 }
527 })
528 .collect();
529
530 quote! { #(#accessors)* }
531 }
532
533 #[inline(always)]
534 fn emit_mav_message_all_messages(&self) -> TokenStream {
535 let mut entries = self
536 .messages
537 .values()
538 .map(|msg| (msg.id, msg.emit_struct_name()))
539 .collect::<Vec<_>>();
540
541 entries.sort_by_key(|(id, _)| *id);
542
543 let pairs = entries
544 .into_iter()
545 .map(|(_, struct_name)| quote!((#struct_name::NAME, #struct_name::ID)))
546 .collect::<Vec<_>>();
547
548 quote! {
549 pub const fn all_messages() -> &'static [(&'static str, u32)] {
550 &[#(#pairs),*]
551 }
552 }
553 }
554}
555
556#[derive(Debug, PartialEq, Clone, Default)]
557#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
558pub struct MavEnum {
559 pub name: String,
560 pub description: Option<String>,
561 pub entries: Vec<MavEnumEntry>,
562 pub primitive: Option<MavType>,
566 pub bitmask: bool,
567 pub deprecated: Option<MavDeprecation>,
568 pub additional_primitives: HashSet<MavType>,
569}
570
571impl MavEnum {
572 fn is_generated_as_bitflags(&self) -> bool {
574 self.primitive.is_some()
575 }
576
577 fn try_combine(&mut self, enm: &Self) {
578 if self.name == enm.name {
579 for enum_entry in &enm.entries {
580 let found_entry = self.entries.iter().find(|elem| {
581 elem.name == enum_entry.name && elem.value.unwrap() == enum_entry.value.unwrap()
582 });
583 match found_entry {
584 Some(entry) => panic!("Enum entry {} already exists", entry.name),
585 None => self.entries.push(enum_entry.clone()),
586 }
587 }
588 self.additional_primitives
589 .extend(enm.additional_primitives.clone());
590 }
591 }
592
593 #[doc(hidden)]
595 pub fn entries_with_values(
596 &self,
597 restricted_primitive: Option<&MavType>,
598 ) -> impl Iterator<Item = (&MavEnumEntry, u64)> {
599 let max_value = restricted_primitive.map_or(u64::MAX, MavType::max_int_value);
600 let mut previous = 0;
601 self.entries
602 .iter()
603 .map(move |entry| {
604 let value = entry.value.unwrap_or_else(|| previous + 1);
605 previous = previous.max(value);
606 (entry, value)
607 })
608 .filter(move |(_, value)| *value <= max_value)
609 }
610
611 fn emit_defs(&self, restricted_primitive: Option<&MavType>) -> Vec<TokenStream> {
612 self.entries_with_values(restricted_primitive)
613 .map(|(enum_entry, value)| {
614 let name = format_ident!("{}", enum_entry.name);
615
616 let deprecation = enum_entry.emit_deprecation();
617
618 let description = if let Some(description) = enum_entry.description.as_ref() {
619 let description = URL_REGEX.replace_all(description, "<$1>");
620 quote!(#[doc = #description])
621 } else {
622 quote!()
623 };
624
625 let params_doc = enum_entry.emit_params();
626
627 let value = Literal::u64_unsuffixed(value);
628 if self.is_generated_as_bitflags() {
629 quote! {
630 #deprecation
631 #description
632 #params_doc
633 const #name = #value;
634 }
635 } else {
636 quote! {
637 #deprecation
638 #description
639 #params_doc
640 #name = #value,
641 }
642 }
643 })
644 .collect()
645 }
646
647 #[inline(always)]
648 fn emit_name(&self, additional_primitive: Option<&MavType>) -> TokenStream {
649 let name = format_ident!(
650 "{}{}",
651 self.name,
652 &additional_primitive
653 .map(|s| s.rust_primitive_type().to_uppercase())
654 .unwrap_or_default()
655 );
656 quote!(#name)
657 }
658
659 #[inline(always)]
660 fn emit_const_default(&self) -> TokenStream {
661 let default = format_ident!("{}", self.entries[0].name);
662 quote!(pub const DEFAULT: Self = Self::#default;)
663 }
664
665 #[inline(always)]
666 fn emit_deprecation(&self) -> TokenStream {
667 self.deprecated
668 .as_ref()
669 .map(|d| d.emit_tokens())
670 .unwrap_or_default()
671 }
672
673 fn emit_rust(&self) -> TokenStream {
674 let base = self.emit_enum_def(None);
675 let adds = self.emit_additional_primitives();
676 quote! {
677 #base
678 #adds
679 }
680 }
681
682 fn emit_enum_def(&self, restricted_primitive: Option<MavType>) -> TokenStream {
683 let defs = self.emit_defs(restricted_primitive.as_ref());
684 let enum_name = self.emit_name(restricted_primitive.as_ref());
685 let const_default = self.emit_const_default();
686
687 let deprecated = self.emit_deprecation();
688
689 let mut description = if let Some(description) = self.description.as_ref() {
690 let desc = URL_REGEX.replace_all(description, "<$1>");
691 quote!(#[doc = #desc])
692 } else {
693 quote!()
694 };
695
696 if let Some(restricted_primitive) = &restricted_primitive {
697 let doc = format!(
698 "This is the `{0}` version of `{1}`. It does not allow flags that exceed `{0}::MAX`",
699 restricted_primitive.rust_primitive_type(),
700 self.emit_name(None),
701 );
702 description.extend(quote! {#[doc = #doc]});
703 }
704
705 let mav_bool_impl = if self.name == "MavBool"
706 && self
707 .entries
708 .iter()
709 .any(|entry| entry.name == "MAV_BOOL_TRUE")
710 {
711 if self.is_generated_as_bitflags() {
712 quote!(
713 pub fn as_bool(&self) -> bool {
714 self.contains(Self::MAV_BOOL_TRUE)
715 }
716 )
717 } else {
718 quote!(
719 pub fn as_bool(&self) -> bool {
720 *self == Self::MAV_BOOL_TRUE
721 }
722 )
723 }
724 } else {
725 quote!()
726 };
727
728 let enum_def = if let Some(primitive) = self.primitive.clone() {
729 let primitive = format_ident!(
730 "{}",
731 &restricted_primitive
732 .as_ref()
733 .unwrap_or(&primitive)
734 .rust_primitive_type()
735 );
736 quote! {
737 bitflags!{
738 #[cfg_attr(feature = "ts-rs", derive(TS))]
739 #[cfg_attr(feature = "ts-rs", ts(export, type = "number"))]
740 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
741 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
742 #[derive(Debug, Copy, Clone, PartialEq)]
743 #deprecated
744 #description
745 pub struct #enum_name: #primitive {
746 #(#defs)*
747 }
748 }
749 }
750 } else {
751 quote! {
752 #[cfg_attr(feature = "ts-rs", derive(TS))]
753 #[cfg_attr(feature = "ts-rs", ts(export))]
754 #[derive(Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
755 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
756 #[cfg_attr(feature = "serde", serde(tag = "type"))]
757 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
758 #[repr(u32)]
759 #deprecated
760 #description
761 pub enum #enum_name {
762 #(#defs)*
763 }
764 }
765 };
766
767 quote! {
768 #enum_def
769
770 impl #enum_name {
771 #const_default
772 #mav_bool_impl
773 }
774
775 impl Default for #enum_name {
776 fn default() -> Self {
777 Self::DEFAULT
778 }
779 }
780 }
781 }
782
783 fn emit_additional_primitives(&self) -> TokenStream {
784 let mut ts = TokenStream::new();
785 for primitive in &self.additional_primitives {
786 ts.extend(self.emit_enum_def(Some(primitive.clone())));
787 }
788 ts
789 }
790}
791
792#[derive(Debug, PartialEq, Clone, Default)]
793#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
794pub struct MavEnumEntry {
795 pub value: Option<u64>,
796 pub name: String,
797 pub description: Option<String>,
798 pub params: Option<Vec<MavParam>>,
799 pub deprecated: Option<MavDeprecation>,
800}
801
802impl MavEnumEntry {
803 #[inline(always)]
804 fn emit_deprecation(&self) -> TokenStream {
805 self.deprecated
806 .as_ref()
807 .map(|d| d.emit_tokens())
808 .unwrap_or_default()
809 }
810
811 #[inline(always)]
812 fn emit_params(&self) -> TokenStream {
813 let Some(params) = &self.params else {
814 return quote!();
815 };
816 let any_value_range = params.iter().any(|p| {
817 p.min_value.is_some()
818 || p.max_value.is_some()
819 || p.increment.is_some()
820 || p.enum_used.is_some()
821 || (p.reserved && p.default.is_some())
822 });
823 let any_units = params.iter().any(|p| p.units.is_some());
824 let lines = params
825 .iter()
826 .map(|param| param.emit_doc_row(any_value_range, any_units));
827 let mut table_header = "| Parameter | Description |".to_string();
828 let mut table_hl = "| --------- | ----------- |".to_string();
829 if any_value_range {
830 table_header += " Values |";
831 table_hl += " ------ |";
832 }
833 if any_units {
834 table_header += " Units |";
835 table_hl += " ----- |";
836 }
837 quote! {
838 #[doc = ""]
839 #[doc = "# Parameters"]
840 #[doc = ""]
841 #[doc = #table_header]
842 #[doc = #table_hl]
843 #(#lines)*
844 }
845 }
846}
847
848#[derive(Debug, PartialEq, Clone, Default)]
849#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
850pub struct MavParam {
851 pub index: usize,
852 pub description: Option<String>,
853 pub label: Option<String>,
854 pub units: Option<String>,
855 pub enum_used: Option<String>,
856 pub increment: Option<f32>,
857 pub min_value: Option<f32>,
858 pub max_value: Option<f32>,
859 pub reserved: bool,
860 pub default: Option<f32>,
861}
862
863fn format_number_range(min: Option<f32>, max: Option<f32>, inc: Option<f32>) -> String {
864 match (min, max, inc) {
865 (Some(min), Some(max), Some(inc)) => {
866 if min + inc == max {
867 format!("{min}, {max}")
868 } else if min + 2. * inc == max {
869 format!("{}, {}, {}", min, min + inc, max)
870 } else {
871 format!("{}, {}, .. , {}", min, min + inc, max)
872 }
873 }
874 (Some(min), Some(max), None) => format!("{min} .. {max}"),
875 (Some(min), None, Some(inc)) => format!("{}, {}, ..", min, min + inc),
876 (None, Some(max), Some(inc)) => format!(".., {}, {}", max - inc, max),
877 (Some(min), None, None) => format!("≥ {min}"),
878 (None, Some(max), None) => format!("≤ {max}"),
879 (None, None, Some(inc)) => format!("Multiples of {inc}"),
880 (None, None, None) => String::new(),
881 }
882}
883
884impl MavParam {
885 fn format_valid_values(&self) -> String {
886 if let (true, Some(default)) = (self.reserved, self.default) {
887 format!("Reserved (use {default})")
888 } else if let Some(enum_used) = &self.enum_used {
889 format!("[`{enum_used}`]")
890 } else {
891 format_number_range(self.min_value, self.max_value, self.increment)
892 }
893 }
894
895 fn emit_doc_row(&self, value_range_col: bool, units_col: bool) -> TokenStream {
896 let label = if let Some(label) = &self.label {
897 format!("{} ({})", self.index, label)
898 } else {
899 format!("{}", self.index)
900 };
901 let mut line = format!(
902 "| {label:10}| {:12}|",
903 self.description.as_deref().unwrap_or_default()
904 );
905 if value_range_col {
906 let range = self.format_valid_values();
907 line += &format!(" {range} |");
908 }
909 if units_col {
910 let units = self.units.clone().unwrap_or_default();
911 line += &format!(" {units} |");
912 }
913 quote! {#[doc = #line]}
914 }
915}
916
917#[derive(Debug, PartialEq, Clone, Default)]
918#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
919pub struct MavMessage {
920 pub id: u32,
921 pub name: String,
922 pub description: Option<String>,
923 pub fields: Vec<MavField>,
924 pub deprecated: Option<MavDeprecation>,
925}
926
927impl MavMessage {
928 fn emit_struct_name(&self) -> TokenStream {
931 let name = format_ident!("{}", format!("{}_DATA", self.name));
932 quote!(#name)
933 }
934
935 #[inline(always)]
936 fn emit_name_types(&self) -> (Vec<TokenStream>, usize) {
937 let mut encoded_payload_size: usize = 0;
938 let field_toks = self
939 .fields
940 .iter()
941 .map(|field| {
942 let nametype = field.emit_name_type();
943 encoded_payload_size += field.mavtype.size();
944
945 let description = field.emit_description();
946
947 let serde_default = if field.is_extension {
951 if field.enumtype.is_some() {
952 quote!(#[cfg_attr(feature = "serde", serde(default))])
953 } else {
954 quote!(#[cfg_attr(feature = "serde", serde(default = "crate::utils::RustDefault::rust_default"))])
955 }
956 } else {
957 quote!()
958 };
959
960 let serde_with_attr = if matches!(field.mavtype, MavType::Array(_, _)) {
961 quote!(
962 #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
963 #[cfg_attr(feature = "ts-rs", ts(type = "Array<number>"))]
964 )
965 } else if matches!(field.mavtype, MavType::CharArray(_)) {
966 quote!(
967 #[cfg_attr(feature = "ts-rs", ts(type = "string"))]
968 )
969 } else {
970 quote!()
971 };
972
973 quote! {
974 #description
975 #serde_default
976 #serde_with_attr
977 #nametype
978 }
979 })
980 .collect::<Vec<TokenStream>>();
981 (field_toks, encoded_payload_size)
982 }
983
984 #[inline(always)]
986 fn emit_description(&self) -> TokenStream {
987 let mut ts = TokenStream::new();
988 if let Some(doc) = self.description.as_ref() {
989 let doc = format!("{doc}{}", if doc.ends_with('.') { "" } else { "." });
990 let doc = URL_REGEX.replace_all(&doc, "<$1>");
992 ts.extend(quote!(#[doc = #doc]));
993 ts.extend(quote!(#[doc = ""]));
995 }
996 let id = format!("ID: {}", self.id);
997 ts.extend(quote!(#[doc = #id]));
998 ts
999 }
1000
1001 #[inline(always)]
1002 fn emit_serialize_vars(&self) -> TokenStream {
1003 let (base_fields, ext_fields): (Vec<_>, Vec<_>) =
1004 self.fields.iter().partition(|f| !f.is_extension);
1005 let ser_vars = base_fields.iter().map(|f| f.rust_writer());
1006 let ser_ext_vars = ext_fields.iter().map(|f| f.rust_writer());
1007 quote! {
1008 let mut __tmp = BytesMut::new(bytes);
1009
1010 if __tmp.remaining() < Self::ENCODED_LEN {
1011 panic!(
1012 "buffer is too small (need {} bytes, but got {})",
1013 Self::ENCODED_LEN,
1014 __tmp.remaining(),
1015 )
1016 }
1017
1018 #(#ser_vars)*
1019 if matches!(version, MavlinkVersion::V2) {
1020 #(#ser_ext_vars)*
1021 let len = __tmp.len();
1022 ::mavlink_core::utils::remove_trailing_zeroes(&bytes[..len])
1023 } else {
1024 __tmp.len()
1025 }
1026 }
1027 }
1028
1029 #[inline(always)]
1030 fn emit_deserialize_vars(&self) -> TokenStream {
1031 let deser_vars = self
1032 .fields
1033 .iter()
1034 .map(|f| f.rust_reader())
1035 .collect::<Vec<TokenStream>>();
1036
1037 if deser_vars.is_empty() {
1038 quote! {
1040 Ok(Self::default())
1041 }
1042 } else {
1043 quote! {
1044 let avail_len = __input.len();
1045
1046 let mut payload_buf;
1047 let mut buf = if avail_len < Self::ENCODED_LEN {
1048 payload_buf = [0; Self::ENCODED_LEN];
1050 payload_buf[0..avail_len].copy_from_slice(__input);
1051 Bytes::new(&payload_buf)
1052 } else {
1053 Bytes::new(__input)
1055 };
1056
1057 let mut __struct = Self::default();
1058 #(#deser_vars)*
1059 Ok(__struct)
1060 }
1061 }
1062 }
1063
1064 #[inline(always)]
1065 fn emit_default_impl(&self) -> TokenStream {
1066 let msg_name = self.emit_struct_name();
1067 quote! {
1068 impl Default for #msg_name {
1069 fn default() -> Self {
1070 Self::DEFAULT.clone()
1071 }
1072 }
1073 }
1074 }
1075
1076 #[inline(always)]
1077 fn emit_deprecation(&self) -> TokenStream {
1078 self.deprecated
1079 .as_ref()
1080 .map(|d| d.emit_tokens())
1081 .unwrap_or_default()
1082 }
1083
1084 #[inline(always)]
1085 fn emit_const_default(&self, dialect_has_version: bool) -> TokenStream {
1086 let initializers = self
1087 .fields
1088 .iter()
1089 .map(|field| field.emit_default_initializer(dialect_has_version));
1090 quote!(pub const DEFAULT: Self = Self { #(#initializers)* };)
1091 }
1092
1093 fn emit_rust(&self, dialect_has_version: bool) -> TokenStream {
1094 let msg_name = self.emit_struct_name();
1095 let id = self.id;
1096 let name = self.name.clone();
1097 let extra_crc = extra_crc(self);
1098 let (name_types, payload_encoded_size) = self.emit_name_types();
1099 assert!(
1100 (1..=255).contains(&payload_encoded_size),
1101 "payload length must be between 1 and 255 bytes"
1102 );
1103
1104 let deser_vars = self.emit_deserialize_vars();
1105 let serialize_vars = self.emit_serialize_vars();
1106 let const_default = self.emit_const_default(dialect_has_version);
1107 let default_impl = self.emit_default_impl();
1108
1109 let deprecation = self.emit_deprecation();
1110
1111 let description = self.emit_description();
1112
1113 quote! {
1114 #deprecation
1115 #description
1116 #[derive(Debug, Clone, PartialEq)]
1117 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1118 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
1119 #[cfg_attr(feature = "ts-rs", derive(TS))]
1120 #[cfg_attr(feature = "ts-rs", ts(export))]
1121 pub struct #msg_name {
1122 #(#name_types)*
1123 }
1124
1125 impl #msg_name {
1126 pub const ENCODED_LEN: usize = #payload_encoded_size;
1127 #const_default
1128
1129 #[cfg(feature = "arbitrary")]
1130 pub fn random<R: rand::Rng>(rng: &mut R) -> Self {
1131 use arbitrary::{Unstructured, Arbitrary};
1132 let mut buf = [0u8; 1024];
1133 rng.fill_bytes(&mut buf);
1134 let mut unstructured = Unstructured::new(&buf);
1135 Self::arbitrary(&mut unstructured).unwrap_or_default()
1136 }
1137 }
1138
1139 #default_impl
1140
1141 impl MessageData for #msg_name {
1142 type Message = MavMessage;
1143
1144 const ID: u32 = #id;
1145 const NAME: &'static str = #name;
1146 const EXTRA_CRC: u8 = #extra_crc;
1147 const ENCODED_LEN: usize = #payload_encoded_size;
1148
1149 fn deser(_version: MavlinkVersion, __input: &[u8]) -> Result<Self, ::mavlink_core::error::ParserError> {
1150 #deser_vars
1151 }
1152
1153 fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
1154 #serialize_vars
1155 }
1156 }
1157 }
1158 }
1159
1160 fn validate_unique_fields(&self) {
1164 let mut seen: HashSet<&str> = HashSet::new();
1165 for f in &self.fields {
1166 let name: &str = &f.name;
1167 assert!(
1168 seen.insert(name),
1169 "Duplicate field '{}' found in message '{}' while generating bindings",
1170 name,
1171 self.name
1172 );
1173 }
1174 }
1175
1176 fn validate_field_count(&self) {
1178 assert!(
1179 !self.fields.is_empty(),
1180 "Message '{}' does not any fields",
1181 self.name
1182 );
1183 assert!(
1184 self.fields.len() <= 64,
1185 "Message '{}' has more then 64 fields",
1186 self.name
1187 );
1188 }
1189}
1190
1191#[derive(Debug, PartialEq, Clone, Default)]
1192#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1193pub struct MavField {
1194 pub mavtype: MavType,
1195 pub name: String,
1196 pub description: Option<String>,
1197 pub enumtype: Option<String>,
1198 pub display: Option<String>,
1199 pub is_extension: bool,
1200 pub is_undersized: bool,
1201}
1202
1203impl MavField {
1204 #[inline(always)]
1206 fn emit_name(&self) -> TokenStream {
1207 let name = format_ident!("{}", self.name);
1208 quote!(#name)
1209 }
1210
1211 #[inline(always)]
1213 fn emit_type(&self) -> TokenStream {
1214 let mavtype;
1215 if matches!(self.mavtype, MavType::Array(_, _)) {
1216 let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
1217 mavtype = quote!(#rt);
1218 } else if let Some(enumname) = &self.enumtype {
1219 if self.is_undersized {
1220 let en = TokenStream::from_str(&format!(
1221 "{}{}",
1222 enumname,
1223 self.mavtype.rust_primitive_type().to_uppercase()
1224 ))
1225 .unwrap();
1226 mavtype = quote!(#en);
1227 } else {
1228 let en = TokenStream::from_str(enumname).unwrap();
1229 mavtype = quote!(#en);
1230 }
1231 } else {
1232 let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
1233 mavtype = quote!(#rt);
1234 }
1235 mavtype
1236 }
1237
1238 #[inline(always)]
1240 fn emit_description(&self) -> TokenStream {
1241 let mut ts = TokenStream::new();
1242 if let Some(val) = self.description.as_ref() {
1243 let desc = URL_REGEX.replace_all(val, "<$1>");
1244 ts.extend(quote!(#[doc = #desc]));
1245 }
1246 if self.is_undersized {
1247 ts.extend(quote!(#[doc = "This field can not store all possible values of its associated bitflag enum."]));
1248 }
1249 ts
1250 }
1251
1252 #[inline(always)]
1254 fn emit_name_type(&self) -> TokenStream {
1255 let name = self.emit_name();
1256 let fieldtype = self.emit_type();
1257 quote!(pub #name: #fieldtype,)
1258 }
1259
1260 fn rust_writer(&self) -> TokenStream {
1262 let mut name = "self.".to_string() + &self.name.clone();
1263 if self.enumtype.is_some() {
1264 if !matches!(self.mavtype, MavType::Array(_, _)) {
1267 if let Some(dsp) = &self.display {
1268 if dsp == "bitmask" {
1270 name += ".bits() as ";
1272 name += &self.mavtype.rust_type();
1273 } else {
1274 panic!("Display option not implemented");
1275 }
1276 } else {
1277 name += " as ";
1279 name += &self.mavtype.rust_type();
1280 }
1281 }
1282 }
1283 let ts = TokenStream::from_str(&name).unwrap();
1284 let name = quote!(#ts);
1285 let buf = format_ident!("__tmp");
1286 self.mavtype.rust_writer(&name, buf)
1287 }
1288
1289 fn rust_reader(&self) -> TokenStream {
1291 let _name = TokenStream::from_str(&self.name).unwrap();
1292
1293 let name = quote!(__struct.#_name);
1294 let buf = format_ident!("buf");
1295 if let Some(enum_name) = &self.enumtype {
1296 if let MavType::Array(_t, _size) = &self.mavtype {
1299 return self.mavtype.rust_reader(&name, buf);
1300 }
1301 if let Some(dsp) = &self.display {
1302 if dsp == "bitmask" {
1303 let tmp = self.mavtype.rust_reader("e!(let tmp), buf);
1305 let enum_name_ident = self.emit_type();
1306 quote! {
1307 #tmp
1308 #name = #enum_name_ident::from_bits_retain(tmp as <#enum_name_ident as Flags>::Bits);
1310 }
1311 } else {
1312 panic!("Display option not implemented");
1313 }
1314 } else {
1315 let tmp = self.mavtype.rust_reader("e!(let tmp), buf);
1317 let val = format_ident!("from_{}", &self.mavtype.rust_type());
1318 quote!(
1319 #tmp
1320 #name = FromPrimitive::#val(tmp)
1321 .ok_or(::mavlink_core::error::ParserError::InvalidEnum { enum_type: #enum_name, value: tmp as u64 })?;
1322 )
1323 }
1324 } else {
1325 self.mavtype.rust_reader(&name, buf)
1326 }
1327 }
1328
1329 #[inline(always)]
1330 fn emit_default_initializer(&self, dialect_has_version: bool) -> TokenStream {
1331 let field = self.emit_name();
1332 if matches!(self.mavtype, MavType::Array(_, _)) {
1334 let default_value = self.mavtype.emit_default_value(dialect_has_version);
1335 quote!(#field: #default_value,)
1336 } else if self.enumtype.is_some() {
1337 let ty = self.emit_type();
1338 quote!(#field: #ty::DEFAULT,)
1339 } else {
1340 let default_value = self.mavtype.emit_default_value(dialect_has_version);
1341 quote!(#field: #default_value,)
1342 }
1343 }
1344}
1345
1346#[derive(Debug, PartialEq, Clone, Default, Hash, Eq)]
1347#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1348pub enum MavType {
1349 UInt8MavlinkVersion,
1350 #[default]
1351 UInt8,
1352 UInt16,
1353 UInt32,
1354 UInt64,
1355 Int8,
1356 Int16,
1357 Int32,
1358 Int64,
1359 Char,
1360 Float,
1361 Double,
1362 CharArray(usize),
1363 Array(Box<Self>, usize),
1364}
1365
1366impl MavType {
1367 fn parse_type(s: &str) -> Option<Self> {
1368 use self::MavType::*;
1369 match s {
1370 "uint8_t_mavlink_version" => Some(UInt8MavlinkVersion),
1371 "uint8_t" => Some(UInt8),
1372 "uint16_t" => Some(UInt16),
1373 "uint32_t" => Some(UInt32),
1374 "uint64_t" => Some(UInt64),
1375 "int8_t" => Some(Int8),
1376 "int16_t" => Some(Int16),
1377 "int32_t" => Some(Int32),
1378 "int64_t" => Some(Int64),
1379 "char" => Some(Char),
1380 "float" => Some(Float),
1381 "Double" => Some(Double),
1382 "double" => Some(Double),
1383 _ if s.starts_with("char[") => {
1384 let start = 4;
1385 let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
1386 Some(CharArray(size))
1387 }
1388 _ if s.ends_with(']') => {
1389 let start = s.find('[')?;
1390 let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
1391 let mtype = Self::parse_type(&s[0..start])?;
1392 Some(Array(Box::new(mtype), size))
1393 }
1394 _ => None,
1395 }
1396 }
1397
1398 pub fn rust_reader(&self, val: &TokenStream, buf: Ident) -> TokenStream {
1400 use self::MavType::*;
1401 match self {
1402 Char => quote! {#val = #buf.get_u8()?;},
1403 UInt8 => quote! {#val = #buf.get_u8()?;},
1404 UInt16 => quote! {#val = #buf.get_u16_le()?;},
1405 UInt32 => quote! {#val = #buf.get_u32_le()?;},
1406 UInt64 => quote! {#val = #buf.get_u64_le()?;},
1407 UInt8MavlinkVersion => quote! {#val = #buf.get_u8()?;},
1408 Int8 => quote! {#val = #buf.get_i8()?;},
1409 Int16 => quote! {#val = #buf.get_i16_le()?;},
1410 Int32 => quote! {#val = #buf.get_i32_le()?;},
1411 Int64 => quote! {#val = #buf.get_i64_le()?;},
1412 Float => quote! {#val = #buf.get_f32_le()?;},
1413 Double => quote! {#val = #buf.get_f64_le()?;},
1414 CharArray(size) => {
1415 quote! {
1416 let mut tmp = [0_u8; #size];
1417 for v in &mut tmp {
1418 *v = #buf.get_u8()?;
1419 }
1420 #val = CharArray::new(tmp);
1421 }
1422 }
1423 Array(t, _) => {
1424 let r = t.rust_reader("e!(let val), buf);
1425 quote! {
1426 for v in &mut #val {
1427 #r
1428 *v = val;
1429 }
1430 }
1431 }
1432 }
1433 }
1434
1435 pub fn rust_writer(&self, val: &TokenStream, buf: Ident) -> TokenStream {
1437 use self::MavType::*;
1438 match self {
1439 UInt8MavlinkVersion => quote! {#buf.put_u8(#val);},
1440 UInt8 => quote! {#buf.put_u8(#val);},
1441 Char => quote! {#buf.put_u8(#val);},
1442 UInt16 => quote! {#buf.put_u16_le(#val);},
1443 UInt32 => quote! {#buf.put_u32_le(#val);},
1444 Int8 => quote! {#buf.put_i8(#val);},
1445 Int16 => quote! {#buf.put_i16_le(#val);},
1446 Int32 => quote! {#buf.put_i32_le(#val);},
1447 Float => quote! {#buf.put_f32_le(#val);},
1448 UInt64 => quote! {#buf.put_u64_le(#val);},
1449 Int64 => quote! {#buf.put_i64_le(#val);},
1450 Double => quote! {#buf.put_f64_le(#val);},
1451 CharArray(_) => {
1452 let w = Char.rust_writer("e!(*val), buf);
1453 quote! {
1454 for val in &#val {
1455 #w
1456 }
1457 }
1458 }
1459 Array(t, _size) => {
1460 let w = t.rust_writer("e!(*val), buf);
1461 quote! {
1462 for val in &#val {
1463 #w
1464 }
1465 }
1466 }
1467 }
1468 }
1469
1470 pub fn size(&self) -> usize {
1472 use self::MavType::*;
1473 match self {
1474 UInt8MavlinkVersion | UInt8 | Int8 | Char => 1,
1475 UInt16 | Int16 => 2,
1476 UInt32 | Int32 | Float => 4,
1477 UInt64 | Int64 | Double => 8,
1478 CharArray(size) => *size,
1479 Array(t, size) => t.size() * size,
1480 }
1481 }
1482
1483 fn max_int_value(&self) -> u64 {
1484 match self {
1485 Self::UInt8MavlinkVersion | Self::UInt8 => u8::MAX as u64,
1486 Self::UInt16 => u16::MAX as u64,
1487 Self::UInt32 => u32::MAX as u64,
1488 Self::UInt64 => u64::MAX,
1489 Self::Int8 | Self::Char | Self::CharArray(_) => i8::MAX as u64,
1490 Self::Int16 => i16::MAX as u64,
1491 Self::Int32 => i32::MAX as u64,
1492 Self::Int64 => i64::MAX as u64,
1493 Self::Float => (1 << f32::MANTISSA_DIGITS) - 1,
1495 Self::Double => (1 << f64::MANTISSA_DIGITS) - 1,
1496 Self::Array(mav_type, _) => mav_type.max_int_value(),
1497 }
1498 }
1499
1500 fn order_len(&self) -> usize {
1502 use self::MavType::*;
1503 match self {
1504 UInt8MavlinkVersion | UInt8 | Int8 | Char | CharArray(_) => 1,
1505 UInt16 | Int16 => 2,
1506 UInt32 | Int32 | Float => 4,
1507 UInt64 | Int64 | Double => 8,
1508 Array(t, _) => t.size(),
1509 }
1510 }
1511
1512 pub fn primitive_type(&self) -> String {
1514 use self::MavType::*;
1515 match self {
1516 UInt8MavlinkVersion => "uint8_t".into(),
1517 UInt8 => "uint8_t".into(),
1518 Int8 => "int8_t".into(),
1519 Char => "char".into(),
1520 UInt16 => "uint16_t".into(),
1521 Int16 => "int16_t".into(),
1522 UInt32 => "uint32_t".into(),
1523 Int32 => "int32_t".into(),
1524 Float => "float".into(),
1525 UInt64 => "uint64_t".into(),
1526 Int64 => "int64_t".into(),
1527 Double => "double".into(),
1528 CharArray(_) => "char".into(),
1529 Array(t, _) => t.primitive_type(),
1530 }
1531 }
1532
1533 pub fn rust_type(&self) -> String {
1536 use self::MavType::*;
1537 match self {
1538 UInt8 | UInt8MavlinkVersion => "u8".into(),
1539 Int8 => "i8".into(),
1540 Char => "u8".into(),
1541 UInt16 => "u16".into(),
1542 Int16 => "i16".into(),
1543 UInt32 => "u32".into(),
1544 Int32 => "i32".into(),
1545 Float => "f32".into(),
1546 UInt64 => "u64".into(),
1547 Int64 => "i64".into(),
1548 Double => "f64".into(),
1549 CharArray(size) => format!("CharArray<{size}>"),
1550 Array(t, size) => format!("[{};{}]", t.rust_type(), size),
1551 }
1552 }
1553
1554 pub fn emit_default_value(&self, dialect_has_version: bool) -> TokenStream {
1555 use self::MavType::*;
1556 match self {
1557 UInt8 => quote!(0_u8),
1558 UInt8MavlinkVersion => {
1559 if dialect_has_version {
1560 quote!(MINOR_MAVLINK_VERSION)
1561 } else {
1562 quote!(0_u8)
1563 }
1564 }
1565 Int8 => quote!(0_i8),
1566 Char => quote!(0_u8),
1567 UInt16 => quote!(0_u16),
1568 Int16 => quote!(0_i16),
1569 UInt32 => quote!(0_u32),
1570 Int32 => quote!(0_i32),
1571 Float => quote!(0.0_f32),
1572 UInt64 => quote!(0_u64),
1573 Int64 => quote!(0_i64),
1574 Double => quote!(0.0_f64),
1575 CharArray(size) => quote!(CharArray::new([0_u8; #size])),
1576 Array(ty, size) => {
1577 let default_value = ty.emit_default_value(dialect_has_version);
1578 quote!([#default_value; #size])
1579 }
1580 }
1581 }
1582
1583 pub fn rust_primitive_type(&self) -> String {
1587 use self::MavType::*;
1588 match self {
1589 Array(t, _) => t.rust_primitive_type(),
1590 _ => self.rust_type(),
1591 }
1592 }
1593
1594 pub fn compare(&self, other: &Self) -> Ordering {
1596 let len = self.order_len();
1597 (-(len as isize)).cmp(&(-(other.order_len() as isize)))
1598 }
1599}
1600
1601#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1602#[derive(Debug, PartialEq, Eq, Clone, Default)]
1603pub enum MavDeprecationType {
1604 #[default]
1605 Deprecated,
1606 Superseded,
1607}
1608
1609impl Display for MavDeprecationType {
1610 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1611 match self {
1612 Self::Deprecated => f.write_str("Deprecated"),
1613 Self::Superseded => f.write_str("Superseded"),
1614 }
1615 }
1616}
1617
1618#[derive(Debug, PartialEq, Eq, Clone, Default)]
1619#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1620pub struct MavDeprecation {
1621 pub since: String,
1623 pub replaced_by: Option<String>,
1624 pub deprecation_type: MavDeprecationType,
1625 pub note: Option<String>,
1626}
1627
1628impl MavDeprecation {
1629 pub fn emit_tokens(&self) -> TokenStream {
1630 let since = &self.since;
1631 let note = match &self.note {
1632 Some(str) if str.is_empty() || str.ends_with(".") => str.clone(),
1633 Some(str) => format!("{str}."),
1634 None => String::new(),
1635 };
1636 let replaced_by = match &self.replaced_by {
1637 Some(str) if str.starts_with('`') => format!("See {str}"),
1638 Some(str) => format!("See `{str}`"),
1639 None => String::new(),
1640 };
1641 let message = format!(
1642 "{note} {replaced_by} ({} since {since})",
1643 self.deprecation_type
1644 );
1645 quote!(#[deprecated = #message])
1646 }
1647}
1648
1649#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1650#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1651#[cfg_attr(feature = "serde", serde(tag = "type"))]
1652pub enum MavXmlElement {
1653 Version,
1654 Mavlink,
1655 Dialect,
1656 Include,
1657 Enums,
1658 Enum,
1659 Entry,
1660 Description,
1661 Param,
1662 Messages,
1663 Message,
1664 Field,
1665 Deprecated,
1666 Wip,
1667 Extensions,
1668 Superseded,
1669}
1670
1671const fn identify_element(s: &[u8]) -> Option<MavXmlElement> {
1672 use self::MavXmlElement::*;
1673 match s {
1674 b"version" => Some(Version),
1675 b"mavlink" => Some(Mavlink),
1676 b"dialect" => Some(Dialect),
1677 b"include" => Some(Include),
1678 b"enums" => Some(Enums),
1679 b"enum" => Some(Enum),
1680 b"entry" => Some(Entry),
1681 b"description" => Some(Description),
1682 b"param" => Some(Param),
1683 b"messages" => Some(Messages),
1684 b"message" => Some(Message),
1685 b"field" => Some(Field),
1686 b"deprecated" => Some(Deprecated),
1687 b"wip" => Some(Wip),
1688 b"extensions" => Some(Extensions),
1689 b"superseded" => Some(Superseded),
1690 _ => None,
1691 }
1692}
1693
1694fn is_valid_parent(p: Option<MavXmlElement>, s: MavXmlElement) -> bool {
1695 use self::MavXmlElement::*;
1696 match s {
1697 Version => p == Some(Mavlink),
1698 Mavlink => p.is_none(),
1699 Dialect => p == Some(Mavlink),
1700 Include => p == Some(Mavlink),
1701 Enums => p == Some(Mavlink),
1702 Enum => p == Some(Enums),
1703 Entry => p == Some(Enum),
1704 Description => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1705 Param => p == Some(Entry),
1706 Messages => p == Some(Mavlink),
1707 Message => p == Some(Messages),
1708 Field => p == Some(Message),
1709 Deprecated => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1710 Wip => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1711 Extensions => p == Some(Message),
1712 Superseded => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1713 }
1714}
1715
1716pub fn parse_profile(
1717 definitions_dir: &Path,
1718 definition_file: &Path,
1719 parsed_files: &mut HashSet<PathBuf>,
1720) -> Result<MavProfile, BindGenError> {
1721 let in_path = Path::new(&definitions_dir).join(definition_file);
1722 parsed_files.insert(in_path.clone()); let mut stack: Vec<MavXmlElement> = vec![];
1725
1726 let mut text = None;
1727
1728 let mut profile = MavProfile::default();
1729 let mut field = MavField::default();
1730 let mut message = MavMessage::default();
1731 let mut mavenum = MavEnum::default();
1732 let mut entry = MavEnumEntry::default();
1733 let mut param_index: Option<usize> = None;
1734 let mut param_label: Option<String> = None;
1735 let mut param_units: Option<String> = None;
1736 let mut param_enum: Option<String> = None;
1737 let mut param_increment: Option<f32> = None;
1738 let mut param_min_value: Option<f32> = None;
1739 let mut param_max_value: Option<f32> = None;
1740 let mut param_reserved = false;
1741 let mut param_default: Option<f32> = None;
1742 let mut deprecated: Option<MavDeprecation> = None;
1743
1744 let mut xml_filter = MavXmlFilter::default();
1745 let mut events: Vec<Result<Event, quick_xml::Error>> = Vec::new();
1746 let xml = std::fs::read_to_string(&in_path).map_err(|e| {
1747 BindGenError::CouldNotReadDefinitionFile {
1748 source: e,
1749 path: in_path.clone(),
1750 }
1751 })?;
1752 let mut reader = Reader::from_str(&xml);
1753 reader.config_mut().trim_text(true);
1754 reader.config_mut().expand_empty_elements = true;
1755
1756 loop {
1757 match reader.read_event() {
1758 Ok(Event::Eof) => {
1759 events.push(Ok(Event::Eof));
1760 break;
1761 }
1762 Ok(event) => events.push(Ok(event.into_owned())),
1763 Err(why) => events.push(Err(why)),
1764 }
1765 }
1766 xml_filter.filter(&mut events);
1767 let mut is_in_extension = false;
1768 for e in events {
1769 match e {
1770 Ok(Event::Start(bytes)) => {
1771 let Some(id) = identify_element(bytes.name().into_inner()) else {
1772 panic!(
1773 "unexpected element {:?}",
1774 String::from_utf8_lossy(bytes.name().into_inner())
1775 );
1776 };
1777
1778 assert!(
1779 is_valid_parent(stack.last().copied(), id),
1780 "not valid parent {:?} of {id:?}",
1781 stack.last(),
1782 );
1783
1784 match id {
1785 MavXmlElement::Extensions => {
1786 is_in_extension = true;
1787 }
1788 MavXmlElement::Message => {
1789 message = MavMessage::default();
1790 }
1791 MavXmlElement::Field => {
1792 field = MavField {
1793 is_extension: is_in_extension,
1794 ..Default::default()
1795 };
1796 }
1797 MavXmlElement::Enum => {
1798 mavenum = MavEnum::default();
1799 }
1800 MavXmlElement::Entry => {
1801 if mavenum.entries.is_empty() {
1802 mavenum.deprecated = deprecated;
1803 }
1804 deprecated = None;
1805 entry = MavEnumEntry::default();
1806 }
1807 MavXmlElement::Param => {
1808 param_index = None;
1809 param_increment = None;
1810 param_min_value = None;
1811 param_max_value = None;
1812 param_reserved = false;
1813 param_default = None;
1814 }
1815 MavXmlElement::Deprecated => {
1816 deprecated = Some(MavDeprecation {
1817 replaced_by: None,
1818 since: String::new(),
1819 deprecation_type: MavDeprecationType::Deprecated,
1820 note: None,
1821 });
1822 }
1823 MavXmlElement::Superseded => {
1824 deprecated = Some(MavDeprecation {
1825 replaced_by: Some(String::new()),
1826 since: String::new(),
1827 deprecation_type: MavDeprecationType::Superseded,
1828 note: None,
1829 });
1830 }
1831 _ => (),
1832 }
1833 stack.push(id);
1834
1835 for attr in bytes.attributes() {
1836 let attr = attr.unwrap();
1837 match stack.last() {
1838 Some(&MavXmlElement::Enum) => {
1839 if attr.key.into_inner() == b"name" {
1840 mavenum.name = to_pascal_case(attr.value);
1841 } else if attr.key.into_inner() == b"bitmask" {
1843 mavenum.bitmask = true;
1844 }
1845 }
1846 Some(&MavXmlElement::Entry) => {
1847 match attr.key.into_inner() {
1848 b"name" => {
1849 entry.name = String::from_utf8_lossy(&attr.value).to_string();
1850 }
1851 b"value" => {
1852 let value = String::from_utf8_lossy(&attr.value);
1853 let (src, radix) = value
1855 .strip_prefix("0x")
1856 .map(|value| (value, 16))
1857 .unwrap_or((value.as_ref(), 10));
1858 entry.value = Some(
1859 u64::from_str_radix(src, radix)
1860 .expect("invalid enum entry value"),
1861 );
1862 }
1863 _ => (),
1864 }
1865 }
1866 Some(&MavXmlElement::Message) => {
1867 match attr.key.into_inner() {
1868 b"name" => {
1869 message.name = String::from_utf8_lossy(&attr.value).to_string();
1883 }
1884 b"id" => {
1885 message.id =
1886 String::from_utf8_lossy(&attr.value).parse().unwrap();
1887 }
1888 _ => (),
1889 }
1890 }
1891 Some(&MavXmlElement::Field) => {
1892 match attr.key.into_inner() {
1893 b"name" => {
1894 let name = String::from_utf8_lossy(&attr.value);
1895 field.name = if name == "type" {
1896 "mavtype".to_string()
1897 } else {
1898 name.to_string()
1899 };
1900 }
1901 b"type" => {
1902 let r#type = String::from_utf8_lossy(&attr.value);
1903 field.mavtype = MavType::parse_type(&r#type).unwrap();
1904 }
1905 b"enum" => {
1906 field.enumtype = Some(to_pascal_case(&attr.value));
1907
1908 if let Some(e) =
1910 profile.enums.get(field.enumtype.as_ref().unwrap())
1911 && e.bitmask
1912 {
1913 field.display = Some("bitmask".to_string());
1914 }
1915 }
1916 b"display" => {
1917 field.display =
1918 Some(String::from_utf8_lossy(&attr.value).to_string());
1919 }
1920 _ => (),
1921 }
1922 }
1923 Some(&MavXmlElement::Param) => {
1924 if entry.params.is_none() {
1925 entry.params = Some(vec![]);
1926 }
1927 match attr.key.into_inner() {
1928 b"index" => {
1929 let value = String::from_utf8_lossy(&attr.value)
1930 .parse()
1931 .expect("failed to parse param index");
1932 assert!(
1933 (1..=7).contains(&value),
1934 "param index must be between 1 and 7"
1935 );
1936 param_index = Some(value);
1937 }
1938 b"label" => {
1939 param_label =
1940 std::str::from_utf8(&attr.value).ok().map(str::to_owned);
1941 }
1942 b"increment" => {
1943 param_increment = Some(
1944 String::from_utf8_lossy(&attr.value)
1945 .parse()
1946 .expect("failed to parse param increment"),
1947 );
1948 }
1949 b"minValue" => {
1950 param_min_value = Some(
1951 String::from_utf8_lossy(&attr.value)
1952 .parse()
1953 .expect("failed to parse param minValue"),
1954 );
1955 }
1956 b"maxValue" => {
1957 param_max_value = Some(
1958 String::from_utf8_lossy(&attr.value)
1959 .parse()
1960 .expect("failed to parse param maxValue"),
1961 );
1962 }
1963 b"units" => {
1964 param_units =
1965 std::str::from_utf8(&attr.value).ok().map(str::to_owned);
1966 }
1967 b"enum" => {
1968 param_enum =
1969 std::str::from_utf8(&attr.value).ok().map(to_pascal_case);
1970 }
1971 b"reserved" => {
1972 param_reserved = attr.value.as_ref() == b"true";
1973 }
1974 b"default" => {
1975 param_default = Some(
1976 String::from_utf8_lossy(&attr.value)
1977 .parse()
1978 .expect("failed to parse param maxValue"),
1979 );
1980 }
1981 _ => (),
1982 }
1983 }
1984 Some(&MavXmlElement::Deprecated) => match attr.key.into_inner() {
1985 b"since" => {
1986 deprecated.as_mut().unwrap().since =
1987 String::from_utf8_lossy(&attr.value).to_string();
1988 }
1989 b"replaced_by" => {
1990 let value = String::from_utf8_lossy(&attr.value);
1991 deprecated.as_mut().unwrap().replaced_by = if value.is_empty() {
1992 None
1993 } else {
1994 Some(value.to_string())
1995 };
1996 }
1997 _ => (),
1998 },
1999 Some(&MavXmlElement::Superseded) => match attr.key.into_inner() {
2000 b"since" => {
2001 deprecated.as_mut().unwrap().since =
2002 String::from_utf8_lossy(&attr.value).to_string();
2003 }
2004 b"replaced_by" => {
2005 deprecated.as_mut().unwrap().replaced_by =
2006 Some(String::from_utf8_lossy(&attr.value).to_string());
2007 }
2008 _ => (),
2009 },
2010 _ => (),
2011 }
2012 }
2013 }
2014 Ok(Event::Text(bytes)) => {
2015 let s = String::from_utf8_lossy(&bytes);
2016
2017 use self::MavXmlElement::*;
2018 match (stack.last(), stack.get(stack.len() - 2)) {
2019 (Some(&Description), Some(&Message))
2020 | (Some(&Field), Some(&Message))
2021 | (Some(&Description), Some(&Enum))
2022 | (Some(&Description), Some(&Entry))
2023 | (Some(&Include), Some(&Mavlink))
2024 | (Some(&Version), Some(&Mavlink))
2025 | (Some(&Dialect), Some(&Mavlink))
2026 | (Some(&Param), Some(&Entry))
2027 | (Some(Deprecated), _)
2028 | (Some(Superseded), _) => {
2029 text = Some(text.map(|t| t + s.as_ref()).unwrap_or(s.to_string()));
2030 }
2031 data => {
2032 panic!("unexpected text data {data:?} reading {s:?}");
2033 }
2034 }
2035 }
2036 Ok(Event::GeneralRef(bytes)) => {
2037 let entity = String::from_utf8_lossy(&bytes);
2038 let decoded = resolve_xml_entity(&entity)
2039 .map(str::to_owned)
2040 .unwrap_or_else(|| format!("&{entity};"));
2041
2042 text = Some(text.map(|t| t + &decoded).unwrap_or(decoded));
2043 }
2044 Ok(Event::End(_)) => {
2045 match stack.last() {
2046 Some(&MavXmlElement::Field) => {
2047 field.description = text.map(|t| t.replace('\n', " "));
2048 message.fields.push(field.clone());
2049 }
2050 Some(&MavXmlElement::Entry) => {
2051 entry.deprecated = deprecated;
2052 deprecated = None;
2053 mavenum.entries.push(entry.clone());
2054 }
2055 Some(&MavXmlElement::Message) => {
2056 message.deprecated = deprecated;
2057
2058 deprecated = None;
2059 is_in_extension = false;
2060 let mut not_extension_fields = message.fields.clone();
2062 let mut extension_fields = message.fields.clone();
2063
2064 not_extension_fields.retain(|field| !field.is_extension);
2065 extension_fields.retain(|field| field.is_extension);
2066
2067 not_extension_fields.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
2069
2070 let mut msg = message.clone();
2072 msg.fields.clear();
2073 msg.fields.extend(not_extension_fields);
2074 msg.fields.extend(extension_fields);
2075
2076 msg.validate_unique_fields();
2078 msg.validate_field_count();
2080
2081 profile.add_message(&msg);
2082 }
2083 Some(&MavXmlElement::Enum) => {
2084 profile.add_enum(&mavenum);
2085 }
2086 Some(&MavXmlElement::Include) => {
2087 let include =
2088 PathBuf::from(text.map(|t| t.replace('\n', "")).unwrap_or_default());
2089 let include_file = Path::new(&definitions_dir).join(include.clone());
2090 if !parsed_files.contains(&include_file) {
2091 let included_profile =
2092 parse_profile(definitions_dir, &include, parsed_files)?;
2093 for message in included_profile.messages.values() {
2094 profile.add_message(message);
2095 }
2096 for enm in included_profile.enums.values() {
2097 profile.add_enum(enm);
2098 }
2099 if profile.version.is_none() {
2100 profile.version = included_profile.version;
2101 }
2102 }
2103 }
2104 Some(&MavXmlElement::Description) => match stack.get(stack.len() - 2) {
2105 Some(&MavXmlElement::Message) => {
2106 message.description = text.map(|t| t.replace('\n', " "));
2107 }
2108 Some(&MavXmlElement::Enum) => {
2109 mavenum.description = text.map(|t| t.replace('\n', " "));
2110 }
2111 Some(&MavXmlElement::Entry) => {
2112 entry.description = text.map(|t| t.replace('\n', " "));
2113 }
2114 _ => (),
2115 },
2116 Some(&MavXmlElement::Version) => {
2117 if let Some(t) = text {
2118 profile.version =
2119 Some(t.parse().expect("Invalid minor version number format"));
2120 }
2121 }
2122 Some(&MavXmlElement::Dialect) => {
2123 if let Some(t) = text {
2124 profile.dialect =
2125 Some(t.parse().expect("Invalid dialect number format"));
2126 }
2127 }
2128 Some(&MavXmlElement::Deprecated) | Some(&MavXmlElement::Superseded) => {
2129 if let Some(t) = text {
2130 deprecated.as_mut().unwrap().note = Some(t);
2131 }
2132 }
2133 Some(&MavXmlElement::Param) => {
2134 if let Some(params) = entry.params.as_mut() {
2135 let param_index = param_index.expect("entry params must have an index");
2137 while params.len() < param_index {
2138 params.push(MavParam {
2139 index: params.len() + 1,
2140 description: None,
2141 ..Default::default()
2142 });
2143 }
2144 if let Some((min, max)) = param_min_value.zip(param_max_value) {
2145 assert!(
2146 min <= max,
2147 "param minValue must not be greater than maxValue"
2148 );
2149 }
2150 params[param_index - 1] = MavParam {
2151 index: param_index,
2152 description: text.map(|t| t.replace('\n', " ")),
2153 label: param_label,
2154 units: param_units,
2155 enum_used: param_enum,
2156 increment: param_increment,
2157 max_value: param_max_value,
2158 min_value: param_min_value,
2159 reserved: param_reserved,
2160 default: param_default,
2161 };
2162 param_label = None;
2163 param_units = None;
2164 param_enum = None;
2165 }
2166 }
2167 _ => (),
2168 }
2169 text = None;
2170 stack.pop();
2171 }
2173 Err(e) => {
2174 eprintln!("Error: {e}");
2175 break;
2176 }
2177 _ => {}
2178 }
2179 }
2180
2181 Ok(profile.update_enums())
2182}
2183
2184pub fn generate<W: Write>(
2187 definitions_dir: &Path,
2188 definition_file: &Path,
2189 output_rust: &mut W,
2190) -> Result<(), BindGenError> {
2191 let mut parsed_files: HashSet<PathBuf> = HashSet::new();
2192 let profile = parse_profile(definitions_dir, definition_file, &mut parsed_files)?;
2193
2194 let dialect_name = util::to_dialect_name(definition_file);
2195
2196 let rust_tokens = profile.emit_rust(&dialect_name);
2198 writeln!(output_rust, "{rust_tokens}").unwrap();
2199
2200 Ok(())
2201}
2202
2203pub fn extra_crc(msg: &MavMessage) -> u8 {
2209 let mut crc = CRCu16::crc16mcrf4cc();
2212
2213 crc.digest(msg.name.as_bytes());
2214 crc.digest(b" ");
2215
2216 let mut f = msg.fields.clone();
2217 f.retain(|f| !f.is_extension);
2219 f.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
2220 for field in &f {
2221 crc.digest(field.mavtype.primitive_type().as_bytes());
2222 crc.digest(b" ");
2223 if field.name == "mavtype" {
2224 crc.digest(b"type");
2225 } else {
2226 crc.digest(field.name.as_bytes());
2227 }
2228 crc.digest(b" ");
2229 if let MavType::Array(_, size) | MavType::CharArray(size) = field.mavtype {
2230 crc.digest(&[size as u8]);
2231 }
2232 }
2233
2234 let crcval = crc.get_crc();
2235 ((crcval & 0xFF) ^ (crcval >> 8)) as u8
2236}
2237
2238#[cfg(not(feature = "mav2-message-extensions"))]
2239struct ExtensionFilter {
2240 pub is_in: bool,
2241}
2242
2243struct MessageFilter {
2244 pub is_in: bool,
2245 pub messages: Vec<String>,
2246}
2247
2248impl MessageFilter {
2249 pub fn new() -> Self {
2250 Self {
2251 is_in: false,
2252 messages: vec![],
2253 }
2254 }
2255}
2256
2257struct MavXmlFilter {
2258 #[cfg(not(feature = "mav2-message-extensions"))]
2259 extension_filter: ExtensionFilter,
2260 message_filter: MessageFilter,
2261}
2262
2263impl Default for MavXmlFilter {
2264 fn default() -> Self {
2265 Self {
2266 #[cfg(not(feature = "mav2-message-extensions"))]
2267 extension_filter: ExtensionFilter { is_in: false },
2268 message_filter: MessageFilter::new(),
2269 }
2270 }
2271}
2272
2273impl MavXmlFilter {
2274 pub fn filter(&mut self, elements: &mut Vec<Result<Event, quick_xml::Error>>) {
2275 elements.retain(|x| self.filter_extension(x) && self.filter_messages(x));
2276 }
2277
2278 #[cfg(feature = "mav2-message-extensions")]
2279 pub fn filter_extension(&mut self, _element: &Result<Event, quick_xml::Error>) -> bool {
2280 true
2281 }
2282
2283 #[cfg(not(feature = "mav2-message-extensions"))]
2285 pub fn filter_extension(&mut self, element: &Result<Event, quick_xml::Error>) -> bool {
2286 match element {
2287 Ok(content) => {
2288 match content {
2289 Event::Start(bytes) | Event::Empty(bytes) => {
2290 let Some(id) = identify_element(bytes.name().into_inner()) else {
2291 panic!(
2292 "unexpected element {:?}",
2293 String::from_utf8_lossy(bytes.name().into_inner())
2294 );
2295 };
2296 if id == MavXmlElement::Extensions {
2297 self.extension_filter.is_in = true;
2298 }
2299 }
2300 Event::End(bytes) => {
2301 let Some(id) = identify_element(bytes.name().into_inner()) else {
2302 panic!(
2303 "unexpected element {:?}",
2304 String::from_utf8_lossy(bytes.name().into_inner())
2305 );
2306 };
2307
2308 if id == MavXmlElement::Message {
2309 self.extension_filter.is_in = false;
2310 }
2311 }
2312 _ => {}
2313 }
2314 !self.extension_filter.is_in
2315 }
2316 Err(error) => panic!("Failed to filter XML: {error}"),
2317 }
2318 }
2319
2320 pub fn filter_messages(&mut self, element: &Result<Event, quick_xml::Error>) -> bool {
2322 match element {
2323 Ok(content) => {
2324 match content {
2325 Event::Start(bytes) | Event::Empty(bytes) => {
2326 let Some(id) = identify_element(bytes.name().into_inner()) else {
2327 panic!(
2328 "unexpected element {:?}",
2329 String::from_utf8_lossy(bytes.name().into_inner())
2330 );
2331 };
2332 if id == MavXmlElement::Message {
2333 for attr in bytes.attributes() {
2334 let attr = attr.unwrap();
2335 if attr.key.into_inner() == b"name" {
2336 let value = String::from_utf8_lossy(&attr.value).into_owned();
2337 if self.message_filter.messages.contains(&value) {
2338 self.message_filter.is_in = true;
2339 return false;
2340 }
2341 }
2342 }
2343 }
2344 }
2345 Event::End(bytes) => {
2346 let Some(id) = identify_element(bytes.name().into_inner()) else {
2347 panic!(
2348 "unexpected element {:?}",
2349 String::from_utf8_lossy(bytes.name().into_inner())
2350 );
2351 };
2352
2353 if id == MavXmlElement::Message && self.message_filter.is_in {
2354 self.message_filter.is_in = false;
2355 return false;
2356 }
2357 }
2358 _ => {}
2359 }
2360 !self.message_filter.is_in
2361 }
2362 Err(error) => panic!("Failed to filter XML: {error}"),
2363 }
2364 }
2365}
2366
2367#[inline(always)]
2368fn to_pascal_case(text: impl AsRef<[u8]>) -> String {
2369 let input = text.as_ref();
2370 let mut result = String::with_capacity(input.len());
2371 let mut capitalize = true;
2372
2373 for &b in input {
2374 if b == b'_' {
2375 capitalize = true;
2376 continue;
2377 }
2378
2379 if capitalize {
2380 result.push((b as char).to_ascii_uppercase());
2381 capitalize = false;
2382 } else {
2383 result.push((b as char).to_ascii_lowercase());
2384 }
2385 }
2386
2387 result
2388}
2389
2390#[cfg(test)]
2391mod tests {
2392 use super::*;
2393
2394 #[test]
2395 fn emits_target_id_match_arms() {
2396 let mut profile = MavProfile::default();
2398
2399 let msg_with_targets = MavMessage {
2400 id: 300,
2401 name: "COMMAND_INT".to_string(),
2402 description: None,
2403 fields: vec![
2404 MavField {
2405 mavtype: MavType::UInt8,
2406 name: "target_system".to_string(),
2407 description: None,
2408 enumtype: None,
2409 display: None,
2410 is_extension: false,
2411 is_undersized: false,
2412 },
2413 MavField {
2414 mavtype: MavType::UInt8,
2415 name: "target_component".to_string(),
2416 description: None,
2417 enumtype: None,
2418 display: None,
2419 is_extension: false,
2420 is_undersized: false,
2421 },
2422 ],
2423 deprecated: None,
2424 };
2425
2426 let msg_without_targets = MavMessage {
2427 id: 0,
2428 name: "HEARTBEAT".to_string(),
2429 description: None,
2430 fields: vec![MavField {
2431 mavtype: MavType::UInt32,
2432 name: "custom_mode".to_string(),
2433 description: None,
2434 enumtype: None,
2435 display: None,
2436 is_extension: false,
2437 is_undersized: false,
2438 }],
2439 deprecated: None,
2440 };
2441
2442 profile.add_message(&msg_with_targets);
2443 profile.add_message(&msg_without_targets);
2444
2445 let tokens = profile.emit_rust("common");
2446 let mut code = tokens.to_string();
2447 code.retain(|c| !c.is_whitespace());
2448
2449 assert!(code.contains("fntarget_system_id(&self)->Option<u8>"));
2451 assert!(code.contains("fntarget_component_id(&self)->Option<u8>"));
2452 assert!(code.contains("fntarget_system_id_mut(&mutself)->Option<&mutu8>"));
2453 assert!(code.contains("fntarget_component_id_mut(&mutself)->Option<&mutu8>"));
2454
2455 assert!(code.contains("Self::COMMAND_INT(inner)=>Some(inner.target_system)"));
2457 assert!(code.contains("Self::COMMAND_INT(inner)=>Some(inner.target_component)"));
2458 assert!(code.contains("Self::COMMAND_INT(inner)=>Some(&mutinner.target_system)"));
2459 assert!(code.contains("Self::COMMAND_INT(inner)=>Some(&mutinner.target_component)"));
2460
2461 assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(inner.target_system)"));
2463 assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(inner.target_component)"));
2464 assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(&mutinner.target_system)"));
2465 assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(&mutinner.target_component)"));
2466 }
2467
2468 #[test]
2469 fn validate_unique_fields_allows_unique() {
2470 let msg = MavMessage {
2471 id: 1,
2472 name: "FOO".to_string(),
2473 description: None,
2474 fields: vec![
2475 MavField {
2476 mavtype: MavType::UInt8,
2477 name: "a".to_string(),
2478 description: None,
2479 enumtype: None,
2480 display: None,
2481 is_extension: false,
2482 is_undersized: false,
2483 },
2484 MavField {
2485 mavtype: MavType::UInt16,
2486 name: "b".to_string(),
2487 description: None,
2488 enumtype: None,
2489 display: None,
2490 is_extension: false,
2491 is_undersized: false,
2492 },
2493 ],
2494 deprecated: None,
2495 };
2496 msg.validate_unique_fields();
2498 }
2499
2500 #[test]
2501 #[should_panic(expected = "Duplicate field")]
2502 fn validate_unique_fields_panics_on_duplicate() {
2503 let msg = MavMessage {
2504 id: 2,
2505 name: "BAR".to_string(),
2506 description: None,
2507 fields: vec![
2508 MavField {
2509 mavtype: MavType::UInt8,
2510 name: "target_system".to_string(),
2511 description: None,
2512 enumtype: None,
2513 display: None,
2514 is_extension: false,
2515 is_undersized: false,
2516 },
2517 MavField {
2518 mavtype: MavType::UInt8,
2519 name: "target_system".to_string(),
2520 description: None,
2521 enumtype: None,
2522 display: None,
2523 is_extension: false,
2524 is_undersized: false,
2525 },
2526 ],
2527 deprecated: None,
2528 };
2529 msg.validate_unique_fields();
2531 }
2532
2533 #[test]
2534 fn validate_field_count_ok() {
2535 let msg = MavMessage {
2536 id: 2,
2537 name: "FOO".to_string(),
2538 description: None,
2539 fields: vec![
2540 MavField {
2541 mavtype: MavType::UInt8,
2542 name: "a".to_string(),
2543 description: None,
2544 enumtype: None,
2545 display: None,
2546 is_extension: false,
2547 is_undersized: false,
2548 },
2549 MavField {
2550 mavtype: MavType::UInt8,
2551 name: "b".to_string(),
2552 description: None,
2553 enumtype: None,
2554 display: None,
2555 is_extension: false,
2556 is_undersized: false,
2557 },
2558 ],
2559 deprecated: None,
2560 };
2561 msg.validate_field_count();
2563 }
2564
2565 #[test]
2566 #[should_panic]
2567 fn validate_field_count_too_many() {
2568 let mut fields = vec![];
2569 for i in 0..65 {
2570 let field = MavField {
2571 mavtype: MavType::UInt8,
2572 name: format!("field_{i}"),
2573 description: None,
2574 enumtype: None,
2575 display: None,
2576 is_extension: false,
2577 is_undersized: false,
2578 };
2579 fields.push(field);
2580 }
2581 let msg = MavMessage {
2582 id: 2,
2583 name: "BAZ".to_string(),
2584 description: None,
2585 fields,
2586 deprecated: None,
2587 };
2588 msg.validate_field_count();
2590 }
2591
2592 #[test]
2593 #[should_panic]
2594 fn validate_field_count_empty() {
2595 let msg = MavMessage {
2596 id: 2,
2597 name: "BAM".to_string(),
2598 description: None,
2599 fields: vec![],
2600 deprecated: None,
2601 };
2602 msg.validate_field_count();
2604 }
2605
2606 #[test]
2607 fn test_fmt_mav_param_values() {
2608 let enum_param = MavParam {
2609 enum_used: Some("ENUM_NAME".to_string()),
2610 ..Default::default()
2611 };
2612 assert_eq!(enum_param.format_valid_values(), "[`ENUM_NAME`]");
2613
2614 let reserved_param = MavParam {
2615 reserved: true,
2616 default: Some(f32::NAN),
2617 ..Default::default()
2618 };
2619 assert_eq!(reserved_param.format_valid_values(), "Reserved (use NaN)");
2620
2621 let unrestricted_param = MavParam::default();
2622 assert_eq!(unrestricted_param.format_valid_values(), "");
2623
2624 let int_param = MavParam {
2625 increment: Some(1.0),
2626 ..Default::default()
2627 };
2628 assert_eq!(int_param.format_valid_values(), "Multiples of 1");
2629
2630 let pos_param = MavParam {
2631 min_value: Some(0.0),
2632 ..Default::default()
2633 };
2634 assert_eq!(pos_param.format_valid_values(), "≥ 0");
2635
2636 let max_param = MavParam {
2637 max_value: Some(5.5),
2638 ..Default::default()
2639 };
2640 assert_eq!(max_param.format_valid_values(), "≤ 5.5");
2641
2642 let pos_int_param = MavParam {
2643 min_value: Some(0.0),
2644 increment: Some(1.0),
2645 ..Default::default()
2646 };
2647 assert_eq!(pos_int_param.format_valid_values(), "0, 1, ..");
2648
2649 let max_inc_param = MavParam {
2650 increment: Some(1.0),
2651 max_value: Some(360.0),
2652 ..Default::default()
2653 };
2654 assert_eq!(max_inc_param.format_valid_values(), ".., 359, 360");
2655
2656 let range_param = MavParam {
2657 min_value: Some(0.0),
2658 max_value: Some(10.0),
2659 ..Default::default()
2660 };
2661 assert_eq!(range_param.format_valid_values(), "0 .. 10");
2662
2663 let int_range_param = MavParam {
2664 min_value: Some(0.0),
2665 max_value: Some(10.0),
2666 increment: Some(1.0),
2667 ..Default::default()
2668 };
2669 assert_eq!(int_range_param.format_valid_values(), "0, 1, .. , 10");
2670
2671 let close_inc_range_param = MavParam {
2672 min_value: Some(-2.0),
2673 max_value: Some(2.0),
2674 increment: Some(2.0),
2675 ..Default::default()
2676 };
2677 assert_eq!(close_inc_range_param.format_valid_values(), "-2, 0, 2");
2678
2679 let bin_range_param = MavParam {
2680 min_value: Some(0.0),
2681 max_value: Some(1.0),
2682 increment: Some(1.0),
2683 ..Default::default()
2684 };
2685 assert_eq!(bin_range_param.format_valid_values(), "0, 1");
2686 }
2687
2688 #[test]
2689 fn test_emit_doc_row() {
2690 let param = MavParam {
2691 index: 3,
2692 label: Some("test param".to_string()),
2693 min_value: Some(0.0),
2694 units: Some("m/s".to_string()),
2695 ..Default::default()
2696 };
2697 assert_eq!(
2699 param.emit_doc_row(false, false).to_string(),
2700 quote! {#[doc = "| 3 (test param)| |"]}.to_string()
2701 );
2702 assert_eq!(
2703 param.emit_doc_row(false, true).to_string(),
2704 quote! {#[doc = "| 3 (test param)| | m/s |"]}.to_string()
2705 );
2706 assert_eq!(
2707 param.emit_doc_row(true, false).to_string(),
2708 quote! {#[doc = "| 3 (test param)| | ≥ 0 |"]}.to_string()
2709 );
2710 assert_eq!(
2711 param.emit_doc_row(true, true).to_string(),
2712 quote! {#[doc = "| 3 (test param)| | ≥ 0 | m/s |"]}.to_string()
2713 );
2714
2715 let unlabeled_param = MavParam {
2716 index: 2,
2717 ..Default::default()
2718 };
2719 assert_eq!(
2720 unlabeled_param.emit_doc_row(false, false).to_string(),
2721 quote! {#[doc = "| 2 | |"]}.to_string()
2722 );
2723 }
2724}