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