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