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