Skip to main content

mavlink_bindgen/
parser.rs

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?://",                                               // url scheme
28        r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+",                       // one or more subdomains
29        r"[a-zA-Z]{2,63}",                                           // root domain
30        r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*[-a-zA-Z0-9@:%_\+~#?&/=])?)", // optional query or url fragments
31    ))
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    /// Go over all fields in the messages, and if you encounter an enum,
72    /// which is a bitmask, set the bitmask size based on field size
73    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                    // find the corresponding enum
78                    if let Some(enm) = self.enums.get_mut(enum_name) {
79                        // Handle legacy definition where bitmask is defined as display="bitmask"
80                        if field.display == Some("bitmask".to_string()) {
81                            enm.bitmask = true;
82                        }
83
84                        // it is a bitmask
85                        if enm.bitmask {
86                            // check if any enum values can be stored in the fields
87                            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                            // Fix fields in backwards manner
109                            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    /// Simple header comment
137    #[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    /// Emit rust messages
148    #[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    /// Emit rust enums
157    #[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    /// Get list of original message names
176    #[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    /// Emit message names with "_DATA" at the end
188    #[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                    // Leave a blank line before the message ID for readability.
313                    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    /// If contains Some, the string represents the primitive type (size) for bitflags.
545    /// If no fields use this enum, the bitmask is true, but primitive is None. In this case
546    /// regular enum is generated as primitive is unknown.
547    pub primitive: Option<MavType>,
548    pub bitmask: bool,
549    pub deprecated: Option<MavDeprecation>,
550    pub additional_primitives: HashSet<MavType>,
551}
552
553impl MavEnum {
554    /// Returns true when this enum will be emitted as a `bitflags` struct.
555    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!("&ge; {min}"),
860        (None, Some(max), None) => format!("&le; {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    /// Return Token of "MESSAGE_NAME_DATA
911    /// for mavlink struct data
912    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                // From MAVLink specification:
930                // If sent by an implementation that doesn't have the extensions fields
931                // then the recipient will see zero values for the extensions fields.
932                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    /// Generate description for the given message
967    #[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            // create hyperlinks
973            let doc = URL_REGEX.replace_all(&doc, "<$1>");
974            ts.extend(quote!(#[doc = #doc]));
975            // Leave a blank line before the message ID for readability.
976            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            // struct has no fields
1021            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                    //copy available bytes into an oversized buffer filled with zeros
1031                    payload_buf = [0; Self::ENCODED_LEN];
1032                    payload_buf[0..avail_len].copy_from_slice(__input);
1033                    Bytes::new(&payload_buf)
1034                } else {
1035                    // fast zero copy
1036                    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    /// Ensures that a message does not contain duplicate field names.
1143    ///
1144    /// Duplicate field names would generate invalid Rust structs.
1145    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    /// Ensure that the fields count is at least one and no more than 64
1159    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    /// Emit rust name of a given field
1187    #[inline(always)]
1188    fn emit_name(&self) -> TokenStream {
1189        let name = format_ident!("{}", self.name);
1190        quote!(#name)
1191    }
1192
1193    /// Emit rust type of the field
1194    #[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    /// Generate description for the given field
1221    #[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    /// Combine rust name and type of a given field
1235    #[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    /// Emit writer
1243    fn rust_writer(&self) -> TokenStream {
1244        let mut name = "self.".to_string() + &self.name.clone();
1245        if self.enumtype.is_some() {
1246            // casts are not necessary for arrays, because they are currently
1247            // generated as primitive arrays
1248            if !matches!(self.mavtype, MavType::Array(_, _)) {
1249                if let Some(dsp) = &self.display {
1250                    // potentially a bitflag
1251                    if dsp == "bitmask" {
1252                        // it is a bitflag
1253                        name += ".bits() as ";
1254                        name += &self.mavtype.rust_type();
1255                    } else {
1256                        panic!("Display option not implemented");
1257                    }
1258                } else {
1259                    // an enum, have to use "*foo as u8" cast
1260                    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    /// Emit reader
1272    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            // TODO: handle enum arrays properly, rather than just generating
1279            // primitive arrays
1280            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                    // bitflags
1286                    let tmp = self.mavtype.rust_reader(&quote!(let tmp), buf);
1287                    let enum_name_ident = self.emit_type();
1288                    quote! {
1289                        #tmp
1290                        // Keep unknown bits for forward compatibility.
1291                        #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                // handle enum by FromPrimitive
1298                let tmp = self.mavtype.rust_reader(&quote!(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        // FIXME: Is this actually expected behaviour??
1315        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    /// Emit reader of a given type
1381    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(&quote!(let val), buf);
1407                quote! {
1408                    for v in &mut #val {
1409                        #r
1410                        *v = val;
1411                    }
1412                }
1413            }
1414        }
1415    }
1416
1417    /// Emit writer of a given type
1418    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(&quote!(*val), buf);
1435                quote! {
1436                    for val in &#val {
1437                        #w
1438                    }
1439                }
1440            }
1441            Array(t, _size) => {
1442                let w = t.rust_writer(&quote!(*val), buf);
1443                quote! {
1444                    for val in &#val {
1445                        #w
1446                    }
1447                }
1448            }
1449        }
1450    }
1451
1452    /// Size of a given Mavtype
1453    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            // maximum precisly representable value minus 1 for float types
1476            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    /// Used for ordering of types
1483    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    /// Used for crc calculation
1495    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    /// Return rust equivalent of a given Mavtype
1516    /// Used for generating struct fields.
1517    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    /// Return rust equivalent of the primitive type of a MavType. The primitive
1566    /// type is the type itself for all except arrays, in which case it is the
1567    /// element type.
1568    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    /// Compare two MavTypes
1577    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    // YYYY-MM
1604    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()); // Keep track of which files have been parsed
1705
1706    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                                //mavenum.name = attr.value.clone();
1824                            } 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                                    // Deal with hexadecimal numbers
1836                                    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 = attr
1849                                    .value
1850                                    .clone()
1851                                    .split("_")
1852                                    .map(|x| x.to_lowercase())
1853                                    .map(|x| {
1854                                        let mut v: Vec<char> = x.chars().collect();
1855                                        v[0] = v[0].to_uppercase().nth(0).unwrap();
1856                                        v.into_iter().collect()
1857                                    })
1858                                    .collect::<Vec<String>>()
1859                                    .join("");
1860                                    */
1861                                    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                                    // Update field display if enum is a bitmask
1888                                    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                        // Follow mavlink ordering specification: https://mavlink.io/en/guide/serialization.html#field_reordering
2041                        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                        // Only not mavlink 1 fields need to be sorted
2048                        not_extension_fields.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
2049
2050                        // Update msg fields and add the new message
2051                        let mut msg = message.clone();
2052                        msg.fields.clear();
2053                        msg.fields.extend(not_extension_fields);
2054                        msg.fields.extend(extension_fields);
2055
2056                        // Validate there are no duplicate field names
2057                        msg.validate_unique_fields();
2058                        // Validate field count must be between 1 and 64
2059                        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                            // Some messages can jump between values, like: 1, 2, 7
2116                            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                // println!("{}-{}", indent(depth), name);
2152            }
2153            Err(e) => {
2154                eprintln!("Error: {e}");
2155                break;
2156            }
2157            _ => {}
2158        }
2159    }
2160
2161    Ok(profile.update_enums())
2162}
2163
2164/// Generate protobuf represenation of mavlink message set
2165/// Generate rust representation of mavlink message set with appropriate conversion methods
2166pub 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    // rust file
2177    let rust_tokens = profile.emit_rust(&dialect_name);
2178    writeln!(output_rust, "{rust_tokens}").unwrap();
2179
2180    Ok(())
2181}
2182
2183/// CRC operates over names of the message and names of its fields
2184/// Hence we have to preserve the original uppercase names delimited with an underscore
2185/// For field names, we replace "type" with "mavtype" to make it rust compatible (this is
2186/// needed for generating sensible rust code), but for calculating crc function we have to
2187/// use the original name "type"
2188pub fn extra_crc(msg: &MavMessage) -> u8 {
2189    // calculate a 8-bit checksum of the key fields of a message, so we
2190    // can detect incompatible XML changes
2191    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    // only mavlink 1 fields should be part of the extra_crc
2198    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    /// Ignore extension fields
2264    #[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    /// Filters messages by their name
2301    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        // Build a minimal profile containing one message with target fields and one without
2377        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        // Check the code contains the target_system/component_id functions
2430        assert!(code.contains("fntarget_system_id(&self)->Option<u8>"));
2431        assert!(code.contains("fntarget_component_id(&self)->Option<u8>"));
2432
2433        // Check the generated impl contains arms referencing COMMAND_INT(inner).target_system/component
2434        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        // Ensure a message without target fields returns None
2438        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        // Should not panic
2471        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        // Should panic due to duplicate field names
2504        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        // Should not panic
2536        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        // Should panic due to 65 fields
2563        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        // Should panic due to no fields
2577        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(), "&ge; 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(), "&le; 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        // test with all variations of columns
2672        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)|             | &ge; 0 |"]}.to_string()
2683        );
2684        assert_eq!(
2685            param.emit_doc_row(true, true).to_string(),
2686            quote! {#[doc = "| 3 (test param)|             | &ge; 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}