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