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