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