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::fs::File;
7use std::io::{BufReader, 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")]
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", derive(TS))]
278            #[cfg_attr(feature = "ts", 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, Eq, 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    fn try_combine(&mut self, enm: &Self) {
539        if self.name == enm.name {
540            for enum_entry in &enm.entries {
541                let found_entry = self.entries.iter().find(|elem| {
542                    elem.name == enum_entry.name && elem.value.unwrap() == enum_entry.value.unwrap()
543                });
544                match found_entry {
545                    Some(entry) => panic!("Enum entry {} already exists", entry.name),
546                    None => self.entries.push(enum_entry.clone()),
547                }
548            }
549        }
550    }
551
552    fn emit_defs(&self) -> Vec<TokenStream> {
553        let mut cnt = 0u64;
554        self.entries
555            .iter()
556            .map(|enum_entry| {
557                let name = format_ident!("{}", enum_entry.name.clone());
558                let value;
559
560                let deprecation = enum_entry.emit_deprecation();
561
562                let description = if let Some(description) = enum_entry.description.as_ref() {
563                    let description = URL_REGEX.replace_all(description, "<$1>");
564                    quote!(#[doc = #description])
565                } else {
566                    quote!()
567                };
568
569                if let Some(tmp_value) = enum_entry.value {
570                    cnt = cnt.max(tmp_value);
571                    let tmp = TokenStream::from_str(&tmp_value.to_string()).unwrap();
572                    value = quote!(#tmp);
573                } else {
574                    cnt += 1;
575                    value = quote!(#cnt);
576                }
577
578                if self.primitive.is_some() {
579                    quote! {
580                        #deprecation
581                        #description
582                        const #name = #value;
583                    }
584                } else {
585                    quote! {
586                        #deprecation
587                        #description
588                        #name = #value,
589                    }
590                }
591            })
592            .collect()
593    }
594
595    #[inline(always)]
596    fn emit_name(&self) -> TokenStream {
597        let name = format_ident!("{}", self.name);
598        quote!(#name)
599    }
600
601    #[inline(always)]
602    fn emit_const_default(&self) -> TokenStream {
603        let default = format_ident!("{}", self.entries[0].name);
604        quote!(pub const DEFAULT: Self = Self::#default;)
605    }
606
607    #[inline(always)]
608    fn emit_deprecation(&self) -> TokenStream {
609        self.deprecated
610            .as_ref()
611            .map(|d| d.emit_tokens())
612            .unwrap_or_default()
613    }
614
615    fn emit_rust(&self) -> TokenStream {
616        let defs = self.emit_defs();
617        let enum_name = self.emit_name();
618        let const_default = self.emit_const_default();
619
620        let deprecated = self.emit_deprecation();
621
622        let description = if let Some(description) = self.description.as_ref() {
623            let desc = URL_REGEX.replace_all(description, "<$1>");
624            quote!(#[doc = #desc])
625        } else {
626            quote!()
627        };
628
629        let mav_bool_impl = if self.name == "MavBool"
630            && self
631                .entries
632                .iter()
633                .any(|entry| entry.name == "MAV_BOOL_TRUE")
634        {
635            quote!(
636                pub fn as_bool(&self) -> bool {
637                    self.contains(Self::MAV_BOOL_TRUE)
638                }
639            )
640        } else {
641            quote!()
642        };
643
644        let enum_def;
645        if let Some(primitive) = self.primitive.clone() {
646            let primitive = format_ident!("{}", primitive);
647            enum_def = quote! {
648                bitflags!{
649                    #[cfg_attr(feature = "ts", derive(TS))]
650                    #[cfg_attr(feature = "ts", ts(export, type = "number"))]
651                    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
652                    #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
653                    #[derive(Debug, Copy, Clone, PartialEq)]
654                    #deprecated
655                    #description
656                    pub struct #enum_name: #primitive {
657                        #(#defs)*
658                    }
659                }
660            };
661        } else {
662            enum_def = quote! {
663                #[cfg_attr(feature = "ts", derive(TS))]
664                #[cfg_attr(feature = "ts", ts(export))]
665                #[derive(Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
666                #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
667                #[cfg_attr(feature = "serde", serde(tag = "type"))]
668                #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
669                #[repr(u32)]
670                #deprecated
671                #description
672                pub enum #enum_name {
673                    #(#defs)*
674                }
675            };
676        }
677
678        quote! {
679            #enum_def
680
681            impl #enum_name {
682                #const_default
683                #mav_bool_impl
684            }
685
686            impl Default for #enum_name {
687                fn default() -> Self {
688                    Self::DEFAULT
689                }
690            }
691        }
692    }
693}
694
695#[derive(Debug, PartialEq, Eq, Clone, Default)]
696#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
697pub struct MavEnumEntry {
698    pub value: Option<u64>,
699    pub name: String,
700    pub description: Option<String>,
701    pub params: Option<Vec<String>>,
702    pub deprecated: Option<MavDeprecation>,
703}
704
705impl MavEnumEntry {
706    #[inline(always)]
707    fn emit_deprecation(&self) -> TokenStream {
708        self.deprecated
709            .as_ref()
710            .map(|d| d.emit_tokens())
711            .unwrap_or_default()
712    }
713}
714
715#[derive(Debug, PartialEq, Clone, Default)]
716#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
717pub struct MavMessage {
718    pub id: u32,
719    pub name: String,
720    pub description: Option<String>,
721    pub fields: Vec<MavField>,
722    pub deprecated: Option<MavDeprecation>,
723}
724
725impl MavMessage {
726    /// Return Token of "MESSAGE_NAME_DATA
727    /// for mavlink struct data
728    fn emit_struct_name(&self) -> TokenStream {
729        let name = format_ident!("{}", format!("{}_DATA", self.name));
730        quote!(#name)
731    }
732
733    #[inline(always)]
734    fn emit_name_types(&self) -> (Vec<TokenStream>, usize) {
735        let mut encoded_payload_len: usize = 0;
736        let field_toks = self
737            .fields
738            .iter()
739            .map(|field| {
740                let nametype = field.emit_name_type();
741                encoded_payload_len += field.mavtype.len();
742
743                let description = field.emit_description();
744
745                // From MAVLink specification:
746                // If sent by an implementation that doesn't have the extensions fields
747                // then the recipient will see zero values for the extensions fields.
748                let serde_default = if field.is_extension {
749                    if field.enumtype.is_some() {
750                        quote!(#[cfg_attr(feature = "serde", serde(default))])
751                    } else {
752                        quote!(#[cfg_attr(feature = "serde", serde(default = "crate::RustDefault::rust_default"))])
753                    }
754                } else {
755                    quote!()
756                };
757
758                let serde_with_attr = if matches!(field.mavtype, MavType::Array(_, _)) {
759                    quote!(
760                        #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
761                        #[cfg_attr(feature = "ts", ts(type = "Array<number>"))]
762                    )
763                } else if matches!(field.mavtype, MavType::CharArray(_)) {
764                    quote!(
765                        #[cfg_attr(feature = "ts", ts(type = "string"))]
766                    )
767                } else {
768                    quote!()
769                };
770
771                quote! {
772                    #description
773                    #serde_default
774                    #serde_with_attr
775                    #nametype
776                }
777            })
778            .collect::<Vec<TokenStream>>();
779        (field_toks, encoded_payload_len)
780    }
781
782    /// Generate description for the given message
783    #[inline(always)]
784    fn emit_description(&self) -> TokenStream {
785        let mut ts = TokenStream::new();
786        if let Some(doc) = self.description.as_ref() {
787            let doc = format!("{doc}{}", if doc.ends_with('.') { "" } else { "." });
788            // create hyperlinks
789            let doc = URL_REGEX.replace_all(&doc, "<$1>");
790            ts.extend(quote!(#[doc = #doc]));
791            // Leave a blank line before the message ID for readability.
792            ts.extend(quote!(#[doc = ""]));
793        }
794        let id = format!("ID: {}", self.id);
795        ts.extend(quote!(#[doc = #id]));
796        ts
797    }
798
799    #[inline(always)]
800    fn emit_serialize_vars(&self) -> TokenStream {
801        let (base_fields, ext_fields): (Vec<_>, Vec<_>) =
802            self.fields.iter().partition(|f| !f.is_extension);
803        let ser_vars = base_fields.iter().map(|f| f.rust_writer());
804        let ser_ext_vars = ext_fields.iter().map(|f| f.rust_writer());
805        quote! {
806            let mut __tmp = BytesMut::new(bytes);
807
808            // TODO: these lints are produced on a couple of cubepilot messages
809            // because they are generated as empty structs with no fields and
810            // therefore Self::ENCODED_LEN is 0. This itself is a bug because
811            // cubepilot.xml has unclosed tags in fields, which the parser has
812            // bad time handling. It should probably be fixed in both the parser
813            // and mavlink message definitions. However, until it's done, let's
814            // skip the lints.
815            #[allow(clippy::absurd_extreme_comparisons)]
816            #[allow(unused_comparisons)]
817            if __tmp.remaining() < Self::ENCODED_LEN {
818                panic!(
819                    "buffer is too small (need {} bytes, but got {})",
820                    Self::ENCODED_LEN,
821                    __tmp.remaining(),
822                )
823            }
824
825            #(#ser_vars)*
826            if matches!(version, MavlinkVersion::V2) {
827                #(#ser_ext_vars)*
828                let len = __tmp.len();
829                ::mavlink_core::utils::remove_trailing_zeroes(&bytes[..len])
830            } else {
831                __tmp.len()
832            }
833        }
834    }
835
836    #[inline(always)]
837    fn emit_deserialize_vars(&self) -> TokenStream {
838        let deser_vars = self
839            .fields
840            .iter()
841            .map(|f| f.rust_reader())
842            .collect::<Vec<TokenStream>>();
843
844        if deser_vars.is_empty() {
845            // struct has no fields
846            quote! {
847                Ok(Self::default())
848            }
849        } else {
850            quote! {
851                let avail_len = __input.len();
852
853                let mut payload_buf  = [0; Self::ENCODED_LEN];
854                let mut buf = if avail_len < Self::ENCODED_LEN {
855                    //copy available bytes into an oversized buffer filled with zeros
856                    payload_buf[0..avail_len].copy_from_slice(__input);
857                    Bytes::new(&payload_buf)
858                } else {
859                    // fast zero copy
860                    Bytes::new(__input)
861                };
862
863                let mut __struct = Self::default();
864                #(#deser_vars)*
865                Ok(__struct)
866            }
867        }
868    }
869
870    #[inline(always)]
871    fn emit_default_impl(&self) -> TokenStream {
872        let msg_name = self.emit_struct_name();
873        quote! {
874            impl Default for #msg_name {
875                fn default() -> Self {
876                    Self::DEFAULT.clone()
877                }
878            }
879        }
880    }
881
882    #[inline(always)]
883    fn emit_deprecation(&self) -> TokenStream {
884        self.deprecated
885            .as_ref()
886            .map(|d| d.emit_tokens())
887            .unwrap_or_default()
888    }
889
890    #[inline(always)]
891    fn emit_const_default(&self, dialect_has_version: bool) -> TokenStream {
892        let initializers = self
893            .fields
894            .iter()
895            .map(|field| field.emit_default_initializer(dialect_has_version));
896        quote!(pub const DEFAULT: Self = Self { #(#initializers)* };)
897    }
898
899    fn emit_rust(&self, dialect_has_version: bool) -> TokenStream {
900        let msg_name = self.emit_struct_name();
901        let id = self.id;
902        let name = self.name.clone();
903        let extra_crc = extra_crc(self);
904        let (name_types, payload_encoded_len) = self.emit_name_types();
905        assert!(
906            payload_encoded_len <= 255,
907            "maximum payload length is 255 bytes"
908        );
909
910        let deser_vars = self.emit_deserialize_vars();
911        let serialize_vars = self.emit_serialize_vars();
912        let const_default = self.emit_const_default(dialect_has_version);
913        let default_impl = self.emit_default_impl();
914
915        let deprecation = self.emit_deprecation();
916
917        let description = self.emit_description();
918
919        quote! {
920            #deprecation
921            #description
922            #[derive(Debug, Clone, PartialEq)]
923            #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
924            #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
925            #[cfg_attr(feature = "ts", derive(TS))]
926            #[cfg_attr(feature = "ts", ts(export))]
927            pub struct #msg_name {
928                #(#name_types)*
929            }
930
931            impl #msg_name {
932                pub const ENCODED_LEN: usize = #payload_encoded_len;
933                #const_default
934
935                #[cfg(feature = "arbitrary")]
936                pub fn random<R: rand::RngCore>(rng: &mut R) -> Self {
937                    use arbitrary::{Unstructured, Arbitrary};
938                    let mut buf = [0u8; 1024];
939                    rng.fill_bytes(&mut buf);
940                    let mut unstructured = Unstructured::new(&buf);
941                    Self::arbitrary(&mut unstructured).unwrap_or_default()
942                }
943            }
944
945            #default_impl
946
947            impl MessageData for #msg_name {
948                type Message = MavMessage;
949
950                const ID: u32 = #id;
951                const NAME: &'static str = #name;
952                const EXTRA_CRC: u8 = #extra_crc;
953                const ENCODED_LEN: usize = #payload_encoded_len;
954
955                fn deser(_version: MavlinkVersion, __input: &[u8]) -> Result<Self, ::mavlink_core::error::ParserError> {
956                    #deser_vars
957                }
958
959                fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
960                    #serialize_vars
961                }
962            }
963        }
964    }
965
966    /// Ensures that a message does not contain duplicate field names.
967    ///
968    /// Duplicate field names would generate invalid Rust structs.
969    fn validate_unique_fields(&self) {
970        let mut seen: HashSet<&str> = HashSet::new();
971        for f in &self.fields {
972            let name: &str = &f.name;
973            assert!(
974                seen.insert(name),
975                "Duplicate field '{}' found in message '{}' while generating bindings",
976                name,
977                self.name
978            );
979        }
980    }
981
982    /// Ensure that the fields count is at least one and no more than 64
983    fn validate_field_count(&self) {
984        assert!(
985            !self.fields.is_empty(),
986            "Message '{}' does not any fields",
987            self.name
988        );
989        assert!(
990            self.fields.len() <= 64,
991            "Message '{}' has more then 64 fields",
992            self.name
993        );
994    }
995}
996
997#[derive(Debug, PartialEq, Clone, Default)]
998#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
999pub struct MavField {
1000    pub mavtype: MavType,
1001    pub name: String,
1002    pub description: Option<String>,
1003    pub enumtype: Option<String>,
1004    pub display: Option<String>,
1005    pub is_extension: bool,
1006}
1007
1008impl MavField {
1009    /// Emit rust name of a given field
1010    #[inline(always)]
1011    fn emit_name(&self) -> TokenStream {
1012        let name = format_ident!("{}", self.name);
1013        quote!(#name)
1014    }
1015
1016    /// Emit rust type of the field
1017    #[inline(always)]
1018    fn emit_type(&self) -> TokenStream {
1019        let mavtype;
1020        if matches!(self.mavtype, MavType::Array(_, _)) {
1021            let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
1022            mavtype = quote!(#rt);
1023        } else if let Some(enumname) = &self.enumtype {
1024            let en = TokenStream::from_str(enumname).unwrap();
1025            mavtype = quote!(#en);
1026        } else {
1027            let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
1028            mavtype = quote!(#rt);
1029        }
1030        mavtype
1031    }
1032
1033    /// Generate description for the given field
1034    #[inline(always)]
1035    fn emit_description(&self) -> TokenStream {
1036        let mut ts = TokenStream::new();
1037        if let Some(val) = self.description.as_ref() {
1038            let desc = URL_REGEX.replace_all(val, "<$1>");
1039            ts.extend(quote!(#[doc = #desc]));
1040        }
1041        ts
1042    }
1043
1044    /// Combine rust name and type of a given field
1045    #[inline(always)]
1046    fn emit_name_type(&self) -> TokenStream {
1047        let name = self.emit_name();
1048        let fieldtype = self.emit_type();
1049        quote!(pub #name: #fieldtype,)
1050    }
1051
1052    /// Emit writer
1053    fn rust_writer(&self) -> TokenStream {
1054        let mut name = "self.".to_string() + &self.name.clone();
1055        if self.enumtype.is_some() {
1056            // casts are not necessary for arrays, because they are currently
1057            // generated as primitive arrays
1058            if !matches!(self.mavtype, MavType::Array(_, _)) {
1059                if let Some(dsp) = &self.display {
1060                    // potentially a bitflag
1061                    if dsp == "bitmask" {
1062                        // it is a bitflag
1063                        name += ".bits() as ";
1064                        name += &self.mavtype.rust_type();
1065                    } else {
1066                        panic!("Display option not implemented");
1067                    }
1068                } else {
1069                    // an enum, have to use "*foo as u8" cast
1070                    name += " as ";
1071                    name += &self.mavtype.rust_type();
1072                }
1073            }
1074        }
1075        let ts = TokenStream::from_str(&name).unwrap();
1076        let name = quote!(#ts);
1077        let buf = format_ident!("__tmp");
1078        self.mavtype.rust_writer(&name, buf)
1079    }
1080
1081    /// Emit reader
1082    fn rust_reader(&self) -> TokenStream {
1083        let _name = TokenStream::from_str(&self.name).unwrap();
1084
1085        let name = quote!(__struct.#_name);
1086        let buf = format_ident!("buf");
1087        if let Some(enum_name) = &self.enumtype {
1088            // TODO: handle enum arrays properly, rather than just generating
1089            // primitive arrays
1090            if let MavType::Array(_t, _size) = &self.mavtype {
1091                return self.mavtype.rust_reader(&name, buf);
1092            }
1093            if let Some(dsp) = &self.display {
1094                if dsp == "bitmask" {
1095                    // bitflags
1096                    let tmp = self.mavtype.rust_reader(&quote!(let tmp), buf);
1097                    let enum_name_ident = format_ident!("{}", enum_name);
1098                    quote! {
1099                        #tmp
1100                        #name = #enum_name_ident::from_bits(tmp as <#enum_name_ident as Flags>::Bits)
1101                            .ok_or(::mavlink_core::error::ParserError::InvalidFlag { flag_type: #enum_name, value: tmp as u64 })?;
1102                    }
1103                } else {
1104                    panic!("Display option not implemented");
1105                }
1106            } else {
1107                // handle enum by FromPrimitive
1108                let tmp = self.mavtype.rust_reader(&quote!(let tmp), buf);
1109                let val = format_ident!("from_{}", &self.mavtype.rust_type());
1110                quote!(
1111                    #tmp
1112                    #name = FromPrimitive::#val(tmp)
1113                        .ok_or(::mavlink_core::error::ParserError::InvalidEnum { enum_type: #enum_name, value: tmp as u64 })?;
1114                )
1115            }
1116        } else {
1117            self.mavtype.rust_reader(&name, buf)
1118        }
1119    }
1120
1121    #[inline(always)]
1122    fn emit_default_initializer(&self, dialect_has_version: bool) -> TokenStream {
1123        let field = self.emit_name();
1124        // FIXME: Is this actually expected behaviour??
1125        if matches!(self.mavtype, MavType::Array(_, _)) {
1126            let default_value = self.mavtype.emit_default_value(dialect_has_version);
1127            quote!(#field: #default_value,)
1128        } else if let Some(enumname) = &self.enumtype {
1129            let ty = TokenStream::from_str(enumname).unwrap();
1130            quote!(#field: #ty::DEFAULT,)
1131        } else {
1132            let default_value = self.mavtype.emit_default_value(dialect_has_version);
1133            quote!(#field: #default_value,)
1134        }
1135    }
1136}
1137
1138#[derive(Debug, PartialEq, Clone, Default)]
1139#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1140pub enum MavType {
1141    UInt8MavlinkVersion,
1142    #[default]
1143    UInt8,
1144    UInt16,
1145    UInt32,
1146    UInt64,
1147    Int8,
1148    Int16,
1149    Int32,
1150    Int64,
1151    Char,
1152    Float,
1153    Double,
1154    CharArray(usize),
1155    Array(Box<Self>, usize),
1156}
1157
1158impl MavType {
1159    fn parse_type(s: &str) -> Option<Self> {
1160        use self::MavType::*;
1161        match s {
1162            "uint8_t_mavlink_version" => Some(UInt8MavlinkVersion),
1163            "uint8_t" => Some(UInt8),
1164            "uint16_t" => Some(UInt16),
1165            "uint32_t" => Some(UInt32),
1166            "uint64_t" => Some(UInt64),
1167            "int8_t" => Some(Int8),
1168            "int16_t" => Some(Int16),
1169            "int32_t" => Some(Int32),
1170            "int64_t" => Some(Int64),
1171            "char" => Some(Char),
1172            "float" => Some(Float),
1173            "Double" => Some(Double),
1174            "double" => Some(Double),
1175            _ if s.starts_with("char[") => {
1176                let start = 4;
1177                let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
1178                Some(CharArray(size))
1179            }
1180            _ if s.ends_with(']') => {
1181                let start = s.find('[')?;
1182                let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
1183                let mtype = Self::parse_type(&s[0..start])?;
1184                Some(Array(Box::new(mtype), size))
1185            }
1186            _ => None,
1187        }
1188    }
1189
1190    /// Emit reader of a given type
1191    pub fn rust_reader(&self, val: &TokenStream, buf: Ident) -> TokenStream {
1192        use self::MavType::*;
1193        match self {
1194            Char => quote! {#val = #buf.get_u8()?;},
1195            UInt8 => quote! {#val = #buf.get_u8()?;},
1196            UInt16 => quote! {#val = #buf.get_u16_le()?;},
1197            UInt32 => quote! {#val = #buf.get_u32_le()?;},
1198            UInt64 => quote! {#val = #buf.get_u64_le()?;},
1199            UInt8MavlinkVersion => quote! {#val = #buf.get_u8()?;},
1200            Int8 => quote! {#val = #buf.get_i8()?;},
1201            Int16 => quote! {#val = #buf.get_i16_le()?;},
1202            Int32 => quote! {#val = #buf.get_i32_le()?;},
1203            Int64 => quote! {#val = #buf.get_i64_le()?;},
1204            Float => quote! {#val = #buf.get_f32_le()?;},
1205            Double => quote! {#val = #buf.get_f64_le()?;},
1206            CharArray(size) => {
1207                quote! {
1208                    let mut tmp = [0_u8; #size];
1209                    for v in &mut tmp {
1210                        *v = #buf.get_u8()?;
1211                    }
1212                    #val = CharArray::new(tmp);
1213                }
1214            }
1215            Array(t, _) => {
1216                let r = t.rust_reader(&quote!(let val), buf);
1217                quote! {
1218                    for v in &mut #val {
1219                        #r
1220                        *v = val;
1221                    }
1222                }
1223            }
1224        }
1225    }
1226
1227    /// Emit writer of a given type
1228    pub fn rust_writer(&self, val: &TokenStream, buf: Ident) -> TokenStream {
1229        use self::MavType::*;
1230        match self {
1231            UInt8MavlinkVersion => quote! {#buf.put_u8(#val);},
1232            UInt8 => quote! {#buf.put_u8(#val);},
1233            Char => quote! {#buf.put_u8(#val);},
1234            UInt16 => quote! {#buf.put_u16_le(#val);},
1235            UInt32 => quote! {#buf.put_u32_le(#val);},
1236            Int8 => quote! {#buf.put_i8(#val);},
1237            Int16 => quote! {#buf.put_i16_le(#val);},
1238            Int32 => quote! {#buf.put_i32_le(#val);},
1239            Float => quote! {#buf.put_f32_le(#val);},
1240            UInt64 => quote! {#buf.put_u64_le(#val);},
1241            Int64 => quote! {#buf.put_i64_le(#val);},
1242            Double => quote! {#buf.put_f64_le(#val);},
1243            CharArray(_) => {
1244                let w = Char.rust_writer(&quote!(*val), buf);
1245                quote! {
1246                    for val in &#val {
1247                        #w
1248                    }
1249                }
1250            }
1251            Array(t, _size) => {
1252                let w = t.rust_writer(&quote!(*val), buf);
1253                quote! {
1254                    for val in &#val {
1255                        #w
1256                    }
1257                }
1258            }
1259        }
1260    }
1261
1262    /// Size of a given Mavtype
1263    fn len(&self) -> usize {
1264        use self::MavType::*;
1265        match self {
1266            UInt8MavlinkVersion | UInt8 | Int8 | Char => 1,
1267            UInt16 | Int16 => 2,
1268            UInt32 | Int32 | Float => 4,
1269            UInt64 | Int64 | Double => 8,
1270            CharArray(size) => *size,
1271            Array(t, size) => t.len() * size,
1272        }
1273    }
1274
1275    fn max_int_value(&self) -> u64 {
1276        match self {
1277            Self::UInt8MavlinkVersion | Self::UInt8 => u8::MAX as u64,
1278            Self::UInt16 => u16::MAX as u64,
1279            Self::UInt32 => u32::MAX as u64,
1280            Self::UInt64 => u64::MAX,
1281            Self::Int8 | Self::Char | Self::CharArray(_) => i8::MAX as u64,
1282            Self::Int16 => i16::MAX as u64,
1283            Self::Int32 => i32::MAX as u64,
1284            Self::Int64 => i64::MAX as u64,
1285            // maximum precisly representable value minus 1 for float types
1286            Self::Float => (1 << f32::MANTISSA_DIGITS) - 1,
1287            Self::Double => (1 << f64::MANTISSA_DIGITS) - 1,
1288            Self::Array(mav_type, _) => mav_type.max_int_value(),
1289        }
1290    }
1291
1292    /// Used for ordering of types
1293    fn order_len(&self) -> usize {
1294        use self::MavType::*;
1295        match self {
1296            UInt8MavlinkVersion | UInt8 | Int8 | Char | CharArray(_) => 1,
1297            UInt16 | Int16 => 2,
1298            UInt32 | Int32 | Float => 4,
1299            UInt64 | Int64 | Double => 8,
1300            Array(t, _) => t.len(),
1301        }
1302    }
1303
1304    /// Used for crc calculation
1305    pub fn primitive_type(&self) -> String {
1306        use self::MavType::*;
1307        match self {
1308            UInt8MavlinkVersion => "uint8_t".into(),
1309            UInt8 => "uint8_t".into(),
1310            Int8 => "int8_t".into(),
1311            Char => "char".into(),
1312            UInt16 => "uint16_t".into(),
1313            Int16 => "int16_t".into(),
1314            UInt32 => "uint32_t".into(),
1315            Int32 => "int32_t".into(),
1316            Float => "float".into(),
1317            UInt64 => "uint64_t".into(),
1318            Int64 => "int64_t".into(),
1319            Double => "double".into(),
1320            CharArray(_) => "char".into(),
1321            Array(t, _) => t.primitive_type(),
1322        }
1323    }
1324
1325    /// Return rust equivalent of a given Mavtype
1326    /// Used for generating struct fields.
1327    pub fn rust_type(&self) -> String {
1328        use self::MavType::*;
1329        match self {
1330            UInt8 | UInt8MavlinkVersion => "u8".into(),
1331            Int8 => "i8".into(),
1332            Char => "u8".into(),
1333            UInt16 => "u16".into(),
1334            Int16 => "i16".into(),
1335            UInt32 => "u32".into(),
1336            Int32 => "i32".into(),
1337            Float => "f32".into(),
1338            UInt64 => "u64".into(),
1339            Int64 => "i64".into(),
1340            Double => "f64".into(),
1341            CharArray(size) => format!("CharArray<{size}>"),
1342            Array(t, size) => format!("[{};{}]", t.rust_type(), size),
1343        }
1344    }
1345
1346    pub fn emit_default_value(&self, dialect_has_version: bool) -> TokenStream {
1347        use self::MavType::*;
1348        match self {
1349            UInt8 => quote!(0_u8),
1350            UInt8MavlinkVersion => {
1351                if dialect_has_version {
1352                    quote!(MINOR_MAVLINK_VERSION)
1353                } else {
1354                    quote!(0_u8)
1355                }
1356            }
1357            Int8 => quote!(0_i8),
1358            Char => quote!(0_u8),
1359            UInt16 => quote!(0_u16),
1360            Int16 => quote!(0_i16),
1361            UInt32 => quote!(0_u32),
1362            Int32 => quote!(0_i32),
1363            Float => quote!(0.0_f32),
1364            UInt64 => quote!(0_u64),
1365            Int64 => quote!(0_i64),
1366            Double => quote!(0.0_f64),
1367            CharArray(size) => quote!(CharArray::new([0_u8; #size])),
1368            Array(ty, size) => {
1369                let default_value = ty.emit_default_value(dialect_has_version);
1370                quote!([#default_value; #size])
1371            }
1372        }
1373    }
1374
1375    /// Return rust equivalent of the primitive type of a MavType. The primitive
1376    /// type is the type itself for all except arrays, in which case it is the
1377    /// element type.
1378    pub fn rust_primitive_type(&self) -> String {
1379        use self::MavType::*;
1380        match self {
1381            Array(t, _) => t.rust_primitive_type(),
1382            _ => self.rust_type(),
1383        }
1384    }
1385
1386    /// Compare two MavTypes
1387    pub fn compare(&self, other: &Self) -> Ordering {
1388        let len = self.order_len();
1389        (-(len as isize)).cmp(&(-(other.order_len() as isize)))
1390    }
1391}
1392
1393#[derive(Debug, PartialEq, Eq, Clone, Default)]
1394#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1395pub struct MavDeprecation {
1396    // YYYY-MM
1397    pub since: String,
1398    // maybe empty, may be encapuslated in `` and contain a wildcard
1399    pub replaced_by: String,
1400    pub note: Option<String>,
1401}
1402
1403impl MavDeprecation {
1404    pub fn emit_tokens(&self) -> TokenStream {
1405        let since = &self.since;
1406        let note = match &self.note {
1407            Some(str) if str.is_empty() || str.ends_with(".") => str.clone(),
1408            Some(str) => format!("{str}."),
1409            None => String::new(),
1410        };
1411        let replaced_by = if self.replaced_by.starts_with("`") {
1412            format!("See {}", self.replaced_by)
1413        } else if self.replaced_by.is_empty() {
1414            String::new()
1415        } else {
1416            format!("See `{}`", self.replaced_by)
1417        };
1418        let message = format!("{note} {replaced_by} (Deprecated since {since})");
1419        quote!(#[deprecated = #message])
1420    }
1421}
1422
1423#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1424#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1425#[cfg_attr(feature = "serde", serde(tag = "type"))]
1426pub enum MavXmlElement {
1427    Version,
1428    Mavlink,
1429    Dialect,
1430    Include,
1431    Enums,
1432    Enum,
1433    Entry,
1434    Description,
1435    Param,
1436    Messages,
1437    Message,
1438    Field,
1439    Deprecated,
1440    Wip,
1441    Extensions,
1442}
1443
1444const fn identify_element(s: &[u8]) -> Option<MavXmlElement> {
1445    use self::MavXmlElement::*;
1446    match s {
1447        b"version" => Some(Version),
1448        b"mavlink" => Some(Mavlink),
1449        b"dialect" => Some(Dialect),
1450        b"include" => Some(Include),
1451        b"enums" => Some(Enums),
1452        b"enum" => Some(Enum),
1453        b"entry" => Some(Entry),
1454        b"description" => Some(Description),
1455        b"param" => Some(Param),
1456        b"messages" => Some(Messages),
1457        b"message" => Some(Message),
1458        b"field" => Some(Field),
1459        b"deprecated" => Some(Deprecated),
1460        b"wip" => Some(Wip),
1461        b"extensions" => Some(Extensions),
1462        _ => None,
1463    }
1464}
1465
1466fn is_valid_parent(p: Option<MavXmlElement>, s: MavXmlElement) -> bool {
1467    use self::MavXmlElement::*;
1468    match s {
1469        Version => p == Some(Mavlink),
1470        Mavlink => p.is_none(),
1471        Dialect => p == Some(Mavlink),
1472        Include => p == Some(Mavlink),
1473        Enums => p == Some(Mavlink),
1474        Enum => p == Some(Enums),
1475        Entry => p == Some(Enum),
1476        Description => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1477        Param => p == Some(Entry),
1478        Messages => p == Some(Mavlink),
1479        Message => p == Some(Messages),
1480        Field => p == Some(Message),
1481        Deprecated => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1482        Wip => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1483        Extensions => p == Some(Message),
1484    }
1485}
1486
1487pub fn parse_profile(
1488    definitions_dir: &Path,
1489    definition_file: &Path,
1490    parsed_files: &mut HashSet<PathBuf>,
1491) -> Result<MavProfile, BindGenError> {
1492    let in_path = Path::new(&definitions_dir).join(definition_file);
1493    parsed_files.insert(in_path.clone()); // Keep track of which files have been parsed
1494
1495    let mut stack: Vec<MavXmlElement> = vec![];
1496
1497    let mut text = None;
1498
1499    let mut profile = MavProfile::default();
1500    let mut field = MavField::default();
1501    let mut message = MavMessage::default();
1502    let mut mavenum = MavEnum::default();
1503    let mut entry = MavEnumEntry::default();
1504    let mut paramid: Option<usize> = None;
1505    let mut deprecated: Option<MavDeprecation> = None;
1506
1507    let mut xml_filter = MavXmlFilter::default();
1508    let mut events: Vec<Result<Event, quick_xml::Error>> = Vec::new();
1509    let file = File::open(&in_path).map_err(|e| BindGenError::CouldNotReadDefinitionFile {
1510        source: e,
1511        path: in_path.clone(),
1512    })?;
1513    let mut reader = Reader::from_reader(BufReader::new(file));
1514    reader.config_mut().trim_text(true);
1515
1516    let mut buf = Vec::new();
1517    loop {
1518        match reader.read_event_into(&mut buf) {
1519            Ok(Event::Eof) => {
1520                events.push(Ok(Event::Eof));
1521                break;
1522            }
1523            Ok(event) => events.push(Ok(event.into_owned())),
1524            Err(why) => events.push(Err(why)),
1525        }
1526        buf.clear();
1527    }
1528    xml_filter.filter(&mut events);
1529    let mut is_in_extension = false;
1530    for e in events {
1531        match e {
1532            Ok(Event::Start(bytes)) => {
1533                let Some(id) = identify_element(bytes.name().into_inner()) else {
1534                    panic!(
1535                        "unexpected element {:?}",
1536                        String::from_utf8_lossy(bytes.name().into_inner())
1537                    );
1538                };
1539
1540                assert!(
1541                    is_valid_parent(stack.last().copied(), id),
1542                    "not valid parent {:?} of {id:?}",
1543                    stack.last(),
1544                );
1545
1546                match id {
1547                    MavXmlElement::Extensions => {
1548                        is_in_extension = true;
1549                    }
1550                    MavXmlElement::Message => {
1551                        message = MavMessage::default();
1552                    }
1553                    MavXmlElement::Field => {
1554                        field = MavField {
1555                            is_extension: is_in_extension,
1556                            ..Default::default()
1557                        };
1558                    }
1559                    MavXmlElement::Enum => {
1560                        mavenum = MavEnum::default();
1561                    }
1562                    MavXmlElement::Entry => {
1563                        if mavenum.entries.is_empty() {
1564                            mavenum.deprecated = deprecated;
1565                        }
1566                        deprecated = None;
1567                        entry = MavEnumEntry::default();
1568                    }
1569                    MavXmlElement::Param => {
1570                        paramid = None;
1571                    }
1572                    MavXmlElement::Deprecated => {
1573                        deprecated = Some(MavDeprecation {
1574                            replaced_by: String::new(),
1575                            since: String::new(),
1576                            note: None,
1577                        });
1578                    }
1579                    _ => (),
1580                }
1581                stack.push(id);
1582
1583                for attr in bytes.attributes() {
1584                    let attr = attr.unwrap();
1585                    match stack.last() {
1586                        Some(&MavXmlElement::Enum) => {
1587                            if attr.key.into_inner() == b"name" {
1588                                mavenum.name = to_pascal_case(attr.value);
1589                                //mavenum.name = attr.value.clone();
1590                            } else if attr.key.into_inner() == b"bitmask" {
1591                                mavenum.bitmask = true;
1592                            }
1593                        }
1594                        Some(&MavXmlElement::Entry) => {
1595                            match attr.key.into_inner() {
1596                                b"name" => {
1597                                    entry.name = String::from_utf8_lossy(&attr.value).to_string();
1598                                }
1599                                b"value" => {
1600                                    let value = String::from_utf8_lossy(&attr.value);
1601                                    // Deal with hexadecimal numbers
1602                                    let (src, radix) = value
1603                                        .strip_prefix("0x")
1604                                        .map(|value| (value, 16))
1605                                        .unwrap_or((value.as_ref(), 10));
1606                                    entry.value = u64::from_str_radix(src, radix).ok();
1607                                }
1608                                _ => (),
1609                            }
1610                        }
1611                        Some(&MavXmlElement::Message) => {
1612                            match attr.key.into_inner() {
1613                                b"name" => {
1614                                    /*message.name = attr
1615                                    .value
1616                                    .clone()
1617                                    .split("_")
1618                                    .map(|x| x.to_lowercase())
1619                                    .map(|x| {
1620                                        let mut v: Vec<char> = x.chars().collect();
1621                                        v[0] = v[0].to_uppercase().nth(0).unwrap();
1622                                        v.into_iter().collect()
1623                                    })
1624                                    .collect::<Vec<String>>()
1625                                    .join("");
1626                                    */
1627                                    message.name = String::from_utf8_lossy(&attr.value).to_string();
1628                                }
1629                                b"id" => {
1630                                    message.id =
1631                                        String::from_utf8_lossy(&attr.value).parse().unwrap();
1632                                }
1633                                _ => (),
1634                            }
1635                        }
1636                        Some(&MavXmlElement::Field) => {
1637                            match attr.key.into_inner() {
1638                                b"name" => {
1639                                    let name = String::from_utf8_lossy(&attr.value);
1640                                    field.name = if name == "type" {
1641                                        "mavtype".to_string()
1642                                    } else {
1643                                        name.to_string()
1644                                    };
1645                                }
1646                                b"type" => {
1647                                    let r#type = String::from_utf8_lossy(&attr.value);
1648                                    field.mavtype = MavType::parse_type(&r#type).unwrap();
1649                                }
1650                                b"enum" => {
1651                                    field.enumtype = Some(to_pascal_case(&attr.value));
1652
1653                                    // Update field display if enum is a bitmask
1654                                    if let Some(e) =
1655                                        profile.enums.get(field.enumtype.as_ref().unwrap())
1656                                    {
1657                                        if e.bitmask {
1658                                            field.display = Some("bitmask".to_string());
1659                                        }
1660                                    }
1661                                }
1662                                b"display" => {
1663                                    field.display =
1664                                        Some(String::from_utf8_lossy(&attr.value).to_string());
1665                                }
1666                                _ => (),
1667                            }
1668                        }
1669                        Some(&MavXmlElement::Param) => {
1670                            if entry.params.is_none() {
1671                                entry.params = Some(vec![]);
1672                            }
1673                            if attr.key.into_inner() == b"index" {
1674                                paramid =
1675                                    Some(String::from_utf8_lossy(&attr.value).parse().unwrap());
1676                            }
1677                        }
1678                        Some(&MavXmlElement::Deprecated) => match attr.key.into_inner() {
1679                            b"since" => {
1680                                deprecated.as_mut().unwrap().since =
1681                                    String::from_utf8_lossy(&attr.value).to_string();
1682                            }
1683                            b"replaced_by" => {
1684                                deprecated.as_mut().unwrap().replaced_by =
1685                                    String::from_utf8_lossy(&attr.value).to_string();
1686                            }
1687                            _ => (),
1688                        },
1689                        _ => (),
1690                    }
1691                }
1692            }
1693            Ok(Event::Empty(bytes)) => match bytes.name().into_inner() {
1694                b"extensions" => {
1695                    is_in_extension = true;
1696                }
1697                b"entry" => {
1698                    if mavenum.entries.is_empty() {
1699                        mavenum.deprecated = deprecated;
1700                    }
1701                    deprecated = None;
1702                    entry = MavEnumEntry::default();
1703                    for attr in bytes.attributes() {
1704                        let attr = attr.unwrap();
1705                        match attr.key.into_inner() {
1706                            b"name" => {
1707                                entry.name = String::from_utf8_lossy(&attr.value).to_string();
1708                            }
1709                            b"value" => {
1710                                entry.value =
1711                                    Some(String::from_utf8_lossy(&attr.value).parse().unwrap());
1712                            }
1713                            _ => (),
1714                        }
1715                    }
1716                    mavenum.entries.push(entry.clone());
1717                }
1718                b"deprecated" => {
1719                    deprecated = Some(MavDeprecation {
1720                        since: String::new(),
1721                        replaced_by: String::new(),
1722                        note: None,
1723                    });
1724                    for attr in bytes.attributes() {
1725                        let attr = attr.unwrap();
1726                        match attr.key.into_inner() {
1727                            b"since" => {
1728                                deprecated.as_mut().unwrap().since =
1729                                    String::from_utf8_lossy(&attr.value).to_string();
1730                            }
1731                            b"replaced_by" => {
1732                                deprecated.as_mut().unwrap().replaced_by =
1733                                    String::from_utf8_lossy(&attr.value).to_string();
1734                            }
1735                            _ => (),
1736                        }
1737                    }
1738                }
1739                b"field" => {
1740                    let mut field = MavField {
1741                        is_extension: is_in_extension,
1742                        ..Default::default()
1743                    };
1744                    for attr in bytes.attributes() {
1745                        let attr = attr.unwrap();
1746                        match attr.key.into_inner() {
1747                            b"name" => {
1748                                let name = String::from_utf8_lossy(&attr.value);
1749                                field.name = if name == "type" {
1750                                    "mavtype".to_string()
1751                                } else {
1752                                    name.to_string()
1753                                };
1754                            }
1755                            b"type" => {
1756                                let r#type = String::from_utf8_lossy(&attr.value);
1757                                field.mavtype = MavType::parse_type(&r#type).unwrap();
1758                            }
1759                            b"enum" => {
1760                                field.enumtype = Some(to_pascal_case(&attr.value));
1761
1762                                // Update field display if enum is a bitmask
1763                                if let Some(e) = profile.enums.get(field.enumtype.as_ref().unwrap())
1764                                {
1765                                    if e.bitmask {
1766                                        field.display = Some("bitmask".to_string());
1767                                    }
1768                                }
1769                            }
1770                            b"display" => {
1771                                field.display =
1772                                    Some(String::from_utf8_lossy(&attr.value).to_string());
1773                            }
1774                            _ => (),
1775                        }
1776                    }
1777                    message.fields.push(field);
1778                }
1779                _ => (),
1780            },
1781            Ok(Event::Text(bytes)) => {
1782                let s = String::from_utf8_lossy(&bytes);
1783
1784                use self::MavXmlElement::*;
1785                match (stack.last(), stack.get(stack.len() - 2)) {
1786                    (Some(&Description), Some(&Message))
1787                    | (Some(&Field), Some(&Message))
1788                    | (Some(&Description), Some(&Enum))
1789                    | (Some(&Description), Some(&Entry))
1790                    | (Some(&Include), Some(&Mavlink))
1791                    | (Some(&Version), Some(&Mavlink))
1792                    | (Some(&Dialect), Some(&Mavlink))
1793                    | (Some(Deprecated), _) => {
1794                        text = Some(text.map(|t| t + s.as_ref()).unwrap_or(s.to_string()));
1795                    }
1796                    (Some(&Param), Some(&Entry)) => {
1797                        if let Some(params) = entry.params.as_mut() {
1798                            // Some messages can jump between values, like:
1799                            // 0, 1, 2, 7
1800                            let paramid = paramid.unwrap();
1801                            if params.len() < paramid {
1802                                for index in params.len()..paramid {
1803                                    params.insert(index, String::from("The use of this parameter (if any), must be defined in the requested message. By default assumed not used (0)."));
1804                                }
1805                            }
1806                            params[paramid - 1] = s.to_string();
1807                        }
1808                    }
1809                    data => {
1810                        panic!("unexpected text data {data:?} reading {s:?}");
1811                    }
1812                }
1813            }
1814            Ok(Event::GeneralRef(bytes)) => {
1815                let entity = String::from_utf8_lossy(&bytes);
1816                text = Some(
1817                    text.map(|t| format!("{t}&{entity};"))
1818                        .unwrap_or(format!("&{entity};")),
1819                );
1820            }
1821            Ok(Event::End(_)) => {
1822                match stack.last() {
1823                    Some(&MavXmlElement::Field) => {
1824                        field.description = text.map(|t| t.replace('\n', " "));
1825                        message.fields.push(field.clone());
1826                    }
1827                    Some(&MavXmlElement::Entry) => {
1828                        entry.deprecated = deprecated;
1829                        deprecated = None;
1830                        mavenum.entries.push(entry.clone());
1831                    }
1832                    Some(&MavXmlElement::Message) => {
1833                        message.deprecated = deprecated;
1834
1835                        deprecated = None;
1836                        is_in_extension = false;
1837                        // Follow mavlink ordering specification: https://mavlink.io/en/guide/serialization.html#field_reordering
1838                        let mut not_extension_fields = message.fields.clone();
1839                        let mut extension_fields = message.fields.clone();
1840
1841                        not_extension_fields.retain(|field| !field.is_extension);
1842                        extension_fields.retain(|field| field.is_extension);
1843
1844                        // Only not mavlink 1 fields need to be sorted
1845                        not_extension_fields.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
1846
1847                        // Update msg fields and add the new message
1848                        let mut msg = message.clone();
1849                        msg.fields.clear();
1850                        msg.fields.extend(not_extension_fields);
1851                        msg.fields.extend(extension_fields);
1852
1853                        // Validate there are no duplicate field names
1854                        msg.validate_unique_fields();
1855                        // Validate field count must be between 1 and 64
1856                        msg.validate_field_count();
1857
1858                        profile.add_message(&msg);
1859                    }
1860                    Some(&MavXmlElement::Enum) => {
1861                        profile.add_enum(&mavenum);
1862                    }
1863                    Some(&MavXmlElement::Include) => {
1864                        let include =
1865                            PathBuf::from(text.map(|t| t.replace('\n', "")).unwrap_or_default());
1866                        let include_file = Path::new(&definitions_dir).join(include.clone());
1867                        if !parsed_files.contains(&include_file) {
1868                            let included_profile =
1869                                parse_profile(definitions_dir, &include, parsed_files)?;
1870                            for message in included_profile.messages.values() {
1871                                profile.add_message(message);
1872                            }
1873                            for enm in included_profile.enums.values() {
1874                                profile.add_enum(enm);
1875                            }
1876                            if profile.version.is_none() {
1877                                profile.version = included_profile.version;
1878                            }
1879                        }
1880                    }
1881                    Some(&MavXmlElement::Description) => match stack.get(stack.len() - 2) {
1882                        Some(&MavXmlElement::Message) => {
1883                            message.description = text.map(|t| t.replace('\n', " "));
1884                        }
1885                        Some(&MavXmlElement::Enum) => {
1886                            mavenum.description = text.map(|t| t.replace('\n', " "));
1887                        }
1888                        Some(&MavXmlElement::Entry) => {
1889                            entry.description = text.map(|t| t.replace('\n', " "));
1890                        }
1891                        _ => (),
1892                    },
1893                    Some(&MavXmlElement::Version) => {
1894                        if let Some(t) = text {
1895                            profile.version =
1896                                Some(t.parse().expect("Invalid minor version number format"));
1897                        }
1898                    }
1899                    Some(&MavXmlElement::Dialect) => {
1900                        if let Some(t) = text {
1901                            profile.dialect =
1902                                Some(t.parse().expect("Invalid dialect number format"));
1903                        }
1904                    }
1905                    Some(&MavXmlElement::Deprecated) => {
1906                        if let Some(t) = text {
1907                            deprecated.as_mut().unwrap().note = Some(t);
1908                        }
1909                    }
1910                    _ => (),
1911                }
1912                text = None;
1913                stack.pop();
1914                // println!("{}-{}", indent(depth), name);
1915            }
1916            Err(e) => {
1917                eprintln!("Error: {e}");
1918                break;
1919            }
1920            _ => {}
1921        }
1922    }
1923
1924    //let profile = profile.update_messages(); //TODO verify no longer needed
1925    Ok(profile.update_enums())
1926}
1927
1928/// Generate protobuf represenation of mavlink message set
1929/// Generate rust representation of mavlink message set with appropriate conversion methods
1930pub fn generate<W: Write>(
1931    definitions_dir: &Path,
1932    definition_file: &Path,
1933    output_rust: &mut W,
1934) -> Result<(), BindGenError> {
1935    let mut parsed_files: HashSet<PathBuf> = HashSet::new();
1936    let profile = parse_profile(definitions_dir, definition_file, &mut parsed_files)?;
1937
1938    let dialect_name = util::to_dialect_name(definition_file);
1939
1940    // rust file
1941    let rust_tokens = profile.emit_rust(&dialect_name);
1942    writeln!(output_rust, "{rust_tokens}").unwrap();
1943
1944    Ok(())
1945}
1946
1947/// CRC operates over names of the message and names of its fields
1948/// Hence we have to preserve the original uppercase names delimited with an underscore
1949/// For field names, we replace "type" with "mavtype" to make it rust compatible (this is
1950/// needed for generating sensible rust code), but for calculating crc function we have to
1951/// use the original name "type"
1952pub fn extra_crc(msg: &MavMessage) -> u8 {
1953    // calculate a 8-bit checksum of the key fields of a message, so we
1954    // can detect incompatible XML changes
1955    let mut crc = CRCu16::crc16mcrf4cc();
1956
1957    crc.digest(msg.name.as_bytes());
1958    crc.digest(b" ");
1959
1960    let mut f = msg.fields.clone();
1961    // only mavlink 1 fields should be part of the extra_crc
1962    f.retain(|f| !f.is_extension);
1963    f.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
1964    for field in &f {
1965        crc.digest(field.mavtype.primitive_type().as_bytes());
1966        crc.digest(b" ");
1967        if field.name == "mavtype" {
1968            crc.digest(b"type");
1969        } else {
1970            crc.digest(field.name.as_bytes());
1971        }
1972        crc.digest(b" ");
1973        if let MavType::Array(_, size) | MavType::CharArray(size) = field.mavtype {
1974            crc.digest(&[size as u8]);
1975        }
1976    }
1977
1978    let crcval = crc.get_crc();
1979    ((crcval & 0xFF) ^ (crcval >> 8)) as u8
1980}
1981
1982#[cfg(not(feature = "emit-extensions"))]
1983struct ExtensionFilter {
1984    pub is_in: bool,
1985}
1986
1987struct MessageFilter {
1988    pub is_in: bool,
1989    pub messages: Vec<String>,
1990}
1991
1992impl MessageFilter {
1993    pub fn new() -> Self {
1994        Self {
1995            is_in: false,
1996            messages: vec![
1997                // device_cap_flags is u32, when enum is u16, which is not handled by the parser yet
1998                "STORM32_GIMBAL_MANAGER_INFORMATION".to_string(),
1999            ],
2000        }
2001    }
2002}
2003
2004struct MavXmlFilter {
2005    #[cfg(not(feature = "emit-extensions"))]
2006    extension_filter: ExtensionFilter,
2007    message_filter: MessageFilter,
2008}
2009
2010impl Default for MavXmlFilter {
2011    fn default() -> Self {
2012        Self {
2013            #[cfg(not(feature = "emit-extensions"))]
2014            extension_filter: ExtensionFilter { is_in: false },
2015            message_filter: MessageFilter::new(),
2016        }
2017    }
2018}
2019
2020impl MavXmlFilter {
2021    pub fn filter(&mut self, elements: &mut Vec<Result<Event, quick_xml::Error>>) {
2022        elements.retain(|x| self.filter_extension(x) && self.filter_messages(x));
2023    }
2024
2025    #[cfg(feature = "emit-extensions")]
2026    pub fn filter_extension(&mut self, _element: &Result<Event, quick_xml::Error>) -> bool {
2027        true
2028    }
2029
2030    /// Ignore extension fields
2031    #[cfg(not(feature = "emit-extensions"))]
2032    pub fn filter_extension(&mut self, element: &Result<Event, quick_xml::Error>) -> bool {
2033        match element {
2034            Ok(content) => {
2035                match content {
2036                    Event::Start(bytes) | Event::Empty(bytes) => {
2037                        let Some(id) = identify_element(bytes.name().into_inner()) else {
2038                            panic!(
2039                                "unexpected element {:?}",
2040                                String::from_utf8_lossy(bytes.name().into_inner())
2041                            );
2042                        };
2043                        if id == MavXmlElement::Extensions {
2044                            self.extension_filter.is_in = true;
2045                        }
2046                    }
2047                    Event::End(bytes) => {
2048                        let Some(id) = identify_element(bytes.name().into_inner()) else {
2049                            panic!(
2050                                "unexpected element {:?}",
2051                                String::from_utf8_lossy(bytes.name().into_inner())
2052                            );
2053                        };
2054
2055                        if id == MavXmlElement::Message {
2056                            self.extension_filter.is_in = false;
2057                        }
2058                    }
2059                    _ => {}
2060                }
2061                !self.extension_filter.is_in
2062            }
2063            Err(error) => panic!("Failed to filter XML: {error}"),
2064        }
2065    }
2066
2067    /// Filters messages by their name
2068    pub fn filter_messages(&mut self, element: &Result<Event, quick_xml::Error>) -> bool {
2069        match element {
2070            Ok(content) => {
2071                match content {
2072                    Event::Start(bytes) | Event::Empty(bytes) => {
2073                        let Some(id) = identify_element(bytes.name().into_inner()) else {
2074                            panic!(
2075                                "unexpected element {:?}",
2076                                String::from_utf8_lossy(bytes.name().into_inner())
2077                            );
2078                        };
2079                        if id == MavXmlElement::Message {
2080                            for attr in bytes.attributes() {
2081                                let attr = attr.unwrap();
2082                                if attr.key.into_inner() == b"name" {
2083                                    let value = String::from_utf8_lossy(&attr.value).into_owned();
2084                                    if self.message_filter.messages.contains(&value) {
2085                                        self.message_filter.is_in = true;
2086                                        return false;
2087                                    }
2088                                }
2089                            }
2090                        }
2091                    }
2092                    Event::End(bytes) => {
2093                        let Some(id) = identify_element(bytes.name().into_inner()) else {
2094                            panic!(
2095                                "unexpected element {:?}",
2096                                String::from_utf8_lossy(bytes.name().into_inner())
2097                            );
2098                        };
2099
2100                        if id == MavXmlElement::Message && self.message_filter.is_in {
2101                            self.message_filter.is_in = false;
2102                            return false;
2103                        }
2104                    }
2105                    _ => {}
2106                }
2107                !self.message_filter.is_in
2108            }
2109            Err(error) => panic!("Failed to filter XML: {error}"),
2110        }
2111    }
2112}
2113
2114#[inline(always)]
2115fn to_pascal_case(text: impl AsRef<[u8]>) -> String {
2116    let input = text.as_ref();
2117    let mut result = String::with_capacity(input.len());
2118    let mut capitalize = true;
2119
2120    for &b in input {
2121        if b == b'_' {
2122            capitalize = true;
2123            continue;
2124        }
2125
2126        if capitalize {
2127            result.push((b as char).to_ascii_uppercase());
2128            capitalize = false;
2129        } else {
2130            result.push((b as char).to_ascii_lowercase());
2131        }
2132    }
2133
2134    result
2135}
2136
2137#[cfg(test)]
2138mod tests {
2139    use super::*;
2140
2141    #[test]
2142    fn emits_target_id_match_arms() {
2143        // Build a minimal profile containing one message with target fields and one without
2144        let mut profile = MavProfile::default();
2145
2146        let msg_with_targets = MavMessage {
2147            id: 300,
2148            name: "COMMAND_INT".to_string(),
2149            description: None,
2150            fields: vec![
2151                MavField {
2152                    mavtype: MavType::UInt8,
2153                    name: "target_system".to_string(),
2154                    description: None,
2155                    enumtype: None,
2156                    display: None,
2157                    is_extension: false,
2158                },
2159                MavField {
2160                    mavtype: MavType::UInt8,
2161                    name: "target_component".to_string(),
2162                    description: None,
2163                    enumtype: None,
2164                    display: None,
2165                    is_extension: false,
2166                },
2167            ],
2168            deprecated: None,
2169        };
2170
2171        let msg_without_targets = MavMessage {
2172            id: 0,
2173            name: "HEARTBEAT".to_string(),
2174            description: None,
2175            fields: vec![MavField {
2176                mavtype: MavType::UInt32,
2177                name: "custom_mode".to_string(),
2178                description: None,
2179                enumtype: None,
2180                display: None,
2181                is_extension: false,
2182            }],
2183            deprecated: None,
2184        };
2185
2186        profile.add_message(&msg_with_targets);
2187        profile.add_message(&msg_without_targets);
2188
2189        let tokens = profile.emit_rust("common");
2190        let mut code = tokens.to_string();
2191        code.retain(|c| !c.is_whitespace());
2192
2193        // Check the code contains the target_system/component_id functions
2194        assert!(code.contains("fntarget_system_id(&self)->Option<u8>"));
2195        assert!(code.contains("fntarget_component_id(&self)->Option<u8>"));
2196
2197        // Check the generated impl contains arms referencing COMMAND_INT(inner).target_system/component
2198        assert!(code.contains("Self::COMMAND_INT(inner)=>Some(inner.target_system)"));
2199        assert!(code.contains("Self::COMMAND_INT(inner)=>Some(inner.target_component)"));
2200
2201        // Ensure a message without target fields returns None
2202        assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(inner.target_system)"));
2203        assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(inner.target_component)"));
2204    }
2205
2206    #[test]
2207    fn validate_unique_fields_allows_unique() {
2208        let msg = MavMessage {
2209            id: 1,
2210            name: "FOO".to_string(),
2211            description: None,
2212            fields: vec![
2213                MavField {
2214                    mavtype: MavType::UInt8,
2215                    name: "a".to_string(),
2216                    description: None,
2217                    enumtype: None,
2218                    display: None,
2219                    is_extension: false,
2220                },
2221                MavField {
2222                    mavtype: MavType::UInt16,
2223                    name: "b".to_string(),
2224                    description: None,
2225                    enumtype: None,
2226                    display: None,
2227                    is_extension: false,
2228                },
2229            ],
2230            deprecated: None,
2231        };
2232        // Should not panic
2233        msg.validate_unique_fields();
2234    }
2235
2236    #[test]
2237    #[should_panic(expected = "Duplicate field")]
2238    fn validate_unique_fields_panics_on_duplicate() {
2239        let msg = MavMessage {
2240            id: 2,
2241            name: "BAR".to_string(),
2242            description: None,
2243            fields: vec![
2244                MavField {
2245                    mavtype: MavType::UInt8,
2246                    name: "target_system".to_string(),
2247                    description: None,
2248                    enumtype: None,
2249                    display: None,
2250                    is_extension: false,
2251                },
2252                MavField {
2253                    mavtype: MavType::UInt8,
2254                    name: "target_system".to_string(),
2255                    description: None,
2256                    enumtype: None,
2257                    display: None,
2258                    is_extension: false,
2259                },
2260            ],
2261            deprecated: None,
2262        };
2263        // Should panic due to duplicate field names
2264        msg.validate_unique_fields();
2265    }
2266
2267    #[test]
2268    fn validate_field_count_ok() {
2269        let msg = MavMessage {
2270            id: 2,
2271            name: "FOO".to_string(),
2272            description: None,
2273            fields: vec![
2274                MavField {
2275                    mavtype: MavType::UInt8,
2276                    name: "a".to_string(),
2277                    description: None,
2278                    enumtype: None,
2279                    display: None,
2280                    is_extension: false,
2281                },
2282                MavField {
2283                    mavtype: MavType::UInt8,
2284                    name: "b".to_string(),
2285                    description: None,
2286                    enumtype: None,
2287                    display: None,
2288                    is_extension: false,
2289                },
2290            ],
2291            deprecated: None,
2292        };
2293        // Should not panic
2294        msg.validate_field_count();
2295    }
2296
2297    #[test]
2298    #[should_panic]
2299    fn validate_field_count_too_many() {
2300        let mut fields = vec![];
2301        for i in 0..65 {
2302            let field = MavField {
2303                mavtype: MavType::UInt8,
2304                name: format!("field_{i}"),
2305                description: None,
2306                enumtype: None,
2307                display: None,
2308                is_extension: false,
2309            };
2310            fields.push(field);
2311        }
2312        let msg = MavMessage {
2313            id: 2,
2314            name: "BAZ".to_string(),
2315            description: None,
2316            fields,
2317            deprecated: None,
2318        };
2319        // Should panic due to 65 fields
2320        msg.validate_field_count();
2321    }
2322
2323    #[test]
2324    #[should_panic]
2325    fn validate_field_count_empty() {
2326        let msg = MavMessage {
2327            id: 2,
2328            name: "BAM".to_string(),
2329            description: None,
2330            fields: vec![],
2331            deprecated: None,
2332        };
2333        // Should panic due to no fields
2334        msg.validate_field_count();
2335    }
2336}