genapi_xml/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! Load and pre-parse GenICam XML using quick-xml.
3
4use std::future::Future;
5
6use quick_xml::events::{BytesStart, Event};
7use quick_xml::name::QName;
8use quick_xml::Reader;
9use thiserror::Error;
10use tracing::warn;
11
12const FIRST_URL_ADDRESS: u64 = 0x0000;
13const FIRST_URL_MAX_LEN: usize = 512;
14
15/// XML element name referencing another node that provides an address.
16const TAG_P_ADDRESS: &[u8] = b"pAddress";
17/// XML element holding an inline literal value.
18const TAG_VALUE: &[u8] = b"Value";
19/// XML element referencing another node supplying the value at runtime.
20const TAG_P_VALUE: &[u8] = b"pValue";
21/// XML element specifying a user friendly label for an enum entry.
22const TAG_DISPLAY_NAME: &[u8] = b"DisplayName";
23/// XML element describing the least significant bit of a bitfield.
24const TAG_LSB: &[u8] = b"Lsb";
25/// XML element describing the most significant bit of a bitfield.
26const TAG_MSB: &[u8] = b"Msb";
27/// XML element describing the starting bit index of a bitfield.
28const TAG_BIT: &[u8] = b"Bit";
29/// XML element describing a bitmask for a bitfield.
30const TAG_MASK: &[u8] = b"Mask";
31/// XML element providing the register byte order (common spelling).
32const TAG_ENDIANNESS: &[u8] = b"Endianness";
33/// XML element providing the register byte order (alternate spelling).
34const TAG_ENDIANESS: &[u8] = b"Endianess";
35/// XML element providing the register byte order (PFNC style).
36const TAG_BYTE_ORDER: &[u8] = b"ByteOrder";
37
38/// Source of the numeric value backing an enumeration entry.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum EnumValueSrc {
41    /// Numeric literal declared directly in the XML.
42    Literal(i64),
43    /// Value obtained from another node referenced via `<pValue>`.
44    FromNode(String),
45}
46
47/// Declaration for a single enumeration entry.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct EnumEntryDecl {
50    /// Symbolic entry name exposed to clients.
51    pub name: String,
52    /// Source describing how to resolve the numeric value for this entry.
53    pub value: EnumValueSrc,
54    /// Optional user facing label.
55    pub display_name: Option<String>,
56}
57
58#[derive(Debug, Error)]
59pub enum XmlError {
60    #[error("xml: {0}")]
61    Xml(String),
62    #[error("invalid descriptor: {0}")]
63    Invalid(String),
64    #[error("transport: {0}")]
65    Transport(String),
66    #[error("unsupported URL: {0}")]
67    Unsupported(String),
68}
69
70/// Access privileges for a GenICam node as described in the XML.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum AccessMode {
73    /// Read-only node. The underlying register must not be modified by the client.
74    RO,
75    /// Write-only node. Reading the register is not permitted.
76    WO,
77    /// Read-write node. The register may be read and written by the client.
78    RW,
79}
80
81impl AccessMode {
82    fn parse(value: &str) -> Result<Self, XmlError> {
83        match value.trim().to_ascii_uppercase().as_str() {
84            "RO" => Ok(AccessMode::RO),
85            "WO" => Ok(AccessMode::WO),
86            "RW" => Ok(AccessMode::RW),
87            other => Err(XmlError::Invalid(format!("unknown access mode: {other}"))),
88        }
89    }
90}
91
92/// Register addressing metadata for a node.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum Addressing {
95    /// Node uses a fixed register block regardless of selector state.
96    Fixed { address: u64, len: u32 },
97    /// Node switches between register blocks based on a selector value.
98    BySelector {
99        /// Name of the selector node controlling the address.
100        selector: String,
101        /// Mapping of selector value to `(address, length)` pair.
102        map: Vec<(String, (u64, u32))>,
103    },
104    /// Node resolves its register block through another node providing the address.
105    Indirect {
106        /// Node providing the register address at runtime.
107        p_address_node: String,
108        /// Length of the target register block in bytes.
109        len: u32,
110    },
111}
112
113/// Byte order used to interpret a multi-byte register payload.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum ByteOrder {
116    /// The first byte contains the least significant bits.
117    Little,
118    /// The first byte contains the most significant bits.
119    Big,
120}
121
122impl ByteOrder {
123    fn parse(tag: &str) -> Option<Self> {
124        match tag.trim().to_ascii_lowercase().as_str() {
125            "littleendian" => Some(ByteOrder::Little),
126            "bigendian" => Some(ByteOrder::Big),
127            _ => None,
128        }
129    }
130}
131
132/// Bitfield metadata describing a sub-range of a register payload.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct BitField {
135    /// Starting bit offset within the interpreted register value.
136    pub bit_offset: u16,
137    /// Number of bits covered by the field.
138    pub bit_length: u16,
139    /// Byte order used when interpreting the enclosing register.
140    pub byte_order: ByteOrder,
141}
142
143/// Output type of a SwissKnife expression node.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
145pub enum SkOutput {
146    /// Integer output. The runtime rounds the computed value to the nearest
147    /// integer with ties going towards zero.
148    Integer,
149    /// Floating point output. The runtime exposes the value as a `f64` without
150    /// any additional processing.
151    #[default]
152    Float,
153}
154
155impl SkOutput {
156    fn parse(tag: &str) -> Option<Self> {
157        match tag.trim().to_ascii_lowercase().as_str() {
158            "integer" => Some(SkOutput::Integer),
159            "float" => Some(SkOutput::Float),
160            _ => None,
161        }
162    }
163}
164
165/// Declaration of a SwissKnife node consisting of an arithmetic expression.
166#[derive(Debug, Clone)]
167pub struct SwissKnifeDecl {
168    /// Feature name exposed to clients.
169    pub name: String,
170    /// Raw expression string to be parsed by the runtime.
171    pub expr: String,
172    /// Mapping of variables used in the expression to provider node names.
173    pub variables: Vec<(String, String)>,
174    /// Desired output type (integer or float).
175    pub output: SkOutput,
176}
177
178/// Declaration of a node extracted from the GenICam XML description.
179#[derive(Debug, Clone)]
180pub enum NodeDecl {
181    /// Integer feature backed by a fixed register block.
182    Integer {
183        /// Feature name.
184        name: String,
185        /// Addressing metadata.
186        addressing: Addressing,
187        /// Length in bytes of the register payload.
188        len: u32,
189        /// Access privileges.
190        access: AccessMode,
191        /// Minimum allowed user value.
192        min: i64,
193        /// Maximum allowed user value.
194        max: i64,
195        /// Optional increment step enforced by the device.
196        inc: Option<i64>,
197        /// Engineering unit (if provided).
198        unit: Option<String>,
199        /// Optional bitfield metadata describing the active bit range.
200        bitfield: Option<BitField>,
201        /// Selector nodes referencing this feature.
202        selectors: Vec<String>,
203        /// Selector gating rules in the form (selector name, allowed values).
204        selected_if: Vec<(String, Vec<String>)>,
205    },
206    /// Floating point feature backed by an integer register with scaling.
207    Float {
208        name: String,
209        addressing: Addressing,
210        access: AccessMode,
211        min: f64,
212        max: f64,
213        unit: Option<String>,
214        /// Optional rational scale applied to the raw register value.
215        scale: Option<(i64, i64)>,
216        /// Optional additive offset applied after scaling.
217        offset: Option<f64>,
218        selectors: Vec<String>,
219        selected_if: Vec<(String, Vec<String>)>,
220    },
221    /// Enumeration feature exposing a list of named integer values.
222    Enum {
223        name: String,
224        addressing: Addressing,
225        access: AccessMode,
226        entries: Vec<EnumEntryDecl>,
227        default: Option<String>,
228        selectors: Vec<String>,
229        selected_if: Vec<(String, Vec<String>)>,
230    },
231    /// Boolean feature backed by a single bit/byte register.
232    Boolean {
233        name: String,
234        addressing: Addressing,
235        len: u32,
236        access: AccessMode,
237        bitfield: BitField,
238        selectors: Vec<String>,
239        selected_if: Vec<(String, Vec<String>)>,
240    },
241    /// Command feature that triggers an action when written.
242    Command {
243        name: String,
244        address: u64,
245        len: u32,
246    },
247    /// Category used to organise features.
248    Category { name: String, children: Vec<String> },
249    /// Computed value backed by an arithmetic expression referencing other nodes.
250    SwissKnife(SwissKnifeDecl),
251}
252
253/// Full XML model describing the GenICam schema version and all declared nodes.
254#[derive(Debug, Clone)]
255pub struct XmlModel {
256    /// Combined schema version extracted from the RegisterDescription attributes.
257    pub version: String,
258    /// Flat list of node declarations present in the document.
259    pub nodes: Vec<NodeDecl>,
260}
261
262/// Fetch the GenICam XML document using the provided memory reader closure.
263///
264/// The closure must return the requested number of bytes starting at the
265/// provided address. It can internally perform chunked transfers.
266pub async fn fetch_and_load_xml<F, Fut>(mut read_mem: F) -> Result<String, XmlError>
267where
268    F: FnMut(u64, usize) -> Fut,
269    Fut: Future<Output = Result<Vec<u8>, XmlError>>,
270{
271    let url_bytes = read_mem(FIRST_URL_ADDRESS, FIRST_URL_MAX_LEN).await?;
272    let url = first_cstring(&url_bytes)
273        .ok_or_else(|| XmlError::Invalid("FirstURL register is empty".into()))?;
274    let location = UrlLocation::parse(&url)?;
275    match location {
276        UrlLocation::Local { address, length } => {
277            let xml_bytes = read_mem(address, length).await?;
278            String::from_utf8(xml_bytes)
279                .map_err(|err| XmlError::Xml(format!("invalid UTF-8: {err}")))
280        }
281        UrlLocation::LocalNamed(name) => Err(XmlError::Unsupported(format!(
282            "named local URL '{name}' is not supported"
283        ))),
284        UrlLocation::Http(url) => Err(XmlError::Unsupported(format!(
285            "HTTP retrieval is not implemented ({url})"
286        ))),
287        UrlLocation::File(path) => Err(XmlError::Unsupported(format!(
288            "file URL '{path}' is not supported"
289        ))),
290    }
291}
292
293/// Parse a GenICam XML snippet and collect minimal metadata.
294pub fn parse_into_minimal_nodes(xml: &str) -> Result<MinimalXmlInfo, XmlError> {
295    let mut reader = Reader::from_str(xml);
296    reader.trim_text(true);
297    let mut buf = Vec::new();
298    let mut depth = 0usize;
299    let mut schema_version: Option<String> = None;
300    let mut top_level_features = Vec::new();
301
302    loop {
303        match reader.read_event_into(&mut buf) {
304            Ok(Event::Start(e)) => {
305                depth += 1;
306                handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
307            }
308            Ok(Event::Empty(e)) => {
309                depth += 1;
310                handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
311                if depth > 0 {
312                    depth = depth.saturating_sub(1);
313                }
314            }
315            Ok(Event::End(_)) => {
316                if depth > 0 {
317                    depth = depth.saturating_sub(1);
318                }
319            }
320            Ok(Event::Eof) => break,
321            Err(err) => return Err(XmlError::Xml(err.to_string())),
322            _ => {}
323        }
324        buf.clear();
325    }
326
327    Ok(MinimalXmlInfo {
328        schema_version,
329        top_level_features,
330    })
331}
332
333/// Parse a GenICam XML document into an [`XmlModel`].
334///
335/// The parser only understands a practical subset of the schema. Unknown tags
336/// are skipped which keeps the implementation forward compatible with richer
337/// documents.
338pub fn parse(xml: &str) -> Result<XmlModel, XmlError> {
339    let mut reader = Reader::from_str(xml);
340    reader.trim_text(true);
341    let mut buf = Vec::new();
342    let mut version = String::from("0.0.0");
343    let mut nodes = Vec::new();
344
345    loop {
346        match reader.read_event_into(&mut buf) {
347            Ok(Event::Start(ref e)) => match e.name().as_ref() {
348                b"RegisterDescription" => {
349                    version = schema_version_from(e)?;
350                }
351                b"Integer" => {
352                    let node = parse_integer(&mut reader, e.clone())?;
353                    nodes.push(node);
354                }
355                b"Float" => {
356                    let node = parse_float(&mut reader, e.clone())?;
357                    nodes.push(node);
358                }
359                b"Enumeration" => {
360                    let node = parse_enum(&mut reader, e.clone())?;
361                    nodes.push(node);
362                }
363                b"Boolean" => {
364                    let node = parse_boolean(&mut reader, e.clone())?;
365                    nodes.push(node);
366                }
367                b"Command" => {
368                    let node = parse_command(&mut reader, e.clone())?;
369                    nodes.push(node);
370                }
371                b"Category" => {
372                    let node = parse_category(&mut reader, e.clone())?;
373                    nodes.push(node);
374                }
375                b"SwissKnife" => {
376                    let node = parse_swissknife(&mut reader, e.clone())?;
377                    nodes.push(node);
378                }
379                _ => {
380                    skip_element(&mut reader, e.name().as_ref())?;
381                }
382            },
383            Ok(Event::Empty(ref e)) => match e.name().as_ref() {
384                b"RegisterDescription" => {
385                    version = schema_version_from(e)?;
386                }
387                b"Command" => {
388                    let node = parse_command_empty(e)?;
389                    nodes.push(node);
390                }
391                b"Category" => {
392                    let node = parse_category_empty(e)?;
393                    nodes.push(node);
394                }
395                _ => {}
396            },
397            Ok(Event::Eof) => break,
398            Err(err) => return Err(XmlError::Xml(err.to_string())),
399            _ => {}
400        }
401        buf.clear();
402    }
403
404    Ok(XmlModel { version, nodes })
405}
406
407fn schema_version_from(event: &BytesStart<'_>) -> Result<String, XmlError> {
408    let major = attribute_value(event, b"SchemaMajorVersion")?;
409    let minor = attribute_value(event, b"SchemaMinorVersion")?;
410    let sub = attribute_value(event, b"SchemaSubMinorVersion")?;
411    let major = major.unwrap_or_else(|| "0".to_string());
412    let minor = minor.unwrap_or_else(|| "0".to_string());
413    let sub = sub.unwrap_or_else(|| "0".to_string());
414    Ok(format!("{major}.{minor}.{sub}"))
415}
416
417#[derive(Debug, Default)]
418struct AddressingBuilder {
419    fixed_address: Option<u64>,
420    length: Option<u32>,
421    selector: Option<String>,
422    entries: Vec<AddressEntry>,
423    pending_value: Option<String>,
424    pending_len: Option<u32>,
425    p_address_node: Option<String>,
426}
427
428#[derive(Debug, Clone)]
429struct AddressEntry {
430    value: String,
431    address: u64,
432    len: Option<u32>,
433}
434
435impl AddressingBuilder {
436    fn set_fixed_address(&mut self, address: u64) {
437        self.fixed_address = Some(address);
438    }
439
440    fn set_length(&mut self, len: u32) {
441        self.length = Some(len);
442    }
443
444    fn set_p_address_node(&mut self, node: &str) {
445        self.p_address_node = Some(node.to_string());
446    }
447
448    fn register_selector(&mut self, selector: &str) {
449        if self.selector.is_none() {
450            self.selector = Some(selector.to_string());
451        }
452    }
453
454    fn push_selected_value(&mut self, value: String) {
455        self.pending_value = Some(value);
456        self.pending_len = None;
457    }
458
459    fn apply_length(&mut self, len: u32) {
460        if self.pending_value.is_some() {
461            self.pending_len = Some(len);
462        } else {
463            self.length = Some(len);
464        }
465    }
466
467    fn attach_selected_address(&mut self, address: u64, len_override: Option<u32>) {
468        if let Some(value) = self.pending_value.take() {
469            let len = len_override.or(self.pending_len.take());
470            self.entries.push(AddressEntry {
471                value,
472                address,
473                len,
474            });
475        } else {
476            self.fixed_address = Some(address);
477            if let Some(len) = len_override {
478                self.length = Some(len);
479            }
480        }
481    }
482
483    fn finalize(self, node: &str, default_len: Option<u32>) -> Result<Addressing, XmlError> {
484        if !self.entries.is_empty() {
485            let selector = self.selector.ok_or_else(|| {
486                XmlError::Invalid(format!(
487                    "node {node} provides <Selected> addresses without <pSelected>"
488                ))
489            })?;
490            let mut map = Vec::new();
491            for entry in self.entries {
492                let len = entry.len.or(self.length).or(default_len).ok_or_else(|| {
493                    XmlError::Invalid(format!(
494                        "node {node} is missing <Length> for selector value {}",
495                        entry.value
496                    ))
497                })?;
498                if let Some(existing) = map.iter_mut().find(|(value, _)| *value == entry.value) {
499                    *existing = (entry.value.clone(), (entry.address, len));
500                } else {
501                    map.push((entry.value.clone(), (entry.address, len)));
502                }
503            }
504            if self.p_address_node.is_some() {
505                warn!(
506                    node = %node,
507                    "ignoring <pAddress> in favour of selector table"
508                );
509            }
510            if self.fixed_address.is_some() {
511                warn!(
512                    node = %node,
513                    selector = %selector,
514                    "ignoring fixed <Address> in favour of selector table"
515                );
516            }
517            Ok(Addressing::BySelector { selector, map })
518        } else {
519            let len = self
520                .length
521                .or(default_len)
522                .ok_or_else(|| XmlError::Invalid(format!("node {node} is missing <Length>")))?;
523            if let Some(p_address_node) = self.p_address_node {
524                if self.fixed_address.is_some() {
525                    warn!(
526                        node = %node,
527                        address_node = %p_address_node,
528                        "ignoring fixed <Address> in favour of <pAddress>"
529                    );
530                }
531                Ok(Addressing::Indirect {
532                    p_address_node,
533                    len,
534                })
535            } else {
536                let address = self.fixed_address.ok_or_else(|| {
537                    XmlError::Invalid(format!("node {node} is missing <Address>"))
538                })?;
539                Ok(Addressing::Fixed { address, len })
540            }
541        }
542    }
543}
544
545#[derive(Debug, Clone, Copy, PartialEq, Eq)]
546enum BitfieldSource {
547    LsbMsb,
548    BitLength,
549    Mask,
550}
551
552#[derive(Debug, Default)]
553struct BitfieldBuilder {
554    lsb: Option<u32>,
555    msb: Option<u32>,
556    bit: Option<u32>,
557    bit_length: Option<u32>,
558    mask: Option<u64>,
559    byte_order: Option<ByteOrder>,
560    source: Option<BitfieldSource>,
561}
562
563impl BitfieldBuilder {
564    fn note_lsb(&mut self, value: u32) {
565        if self
566            .source
567            .map(|source| source != BitfieldSource::LsbMsb)
568            .unwrap_or(false)
569        {
570            return;
571        }
572        self.source.get_or_insert(BitfieldSource::LsbMsb);
573        self.lsb = Some(value);
574    }
575
576    fn note_msb(&mut self, value: u32) {
577        if self
578            .source
579            .map(|source| source != BitfieldSource::LsbMsb)
580            .unwrap_or(false)
581        {
582            return;
583        }
584        self.source.get_or_insert(BitfieldSource::LsbMsb);
585        self.msb = Some(value);
586    }
587
588    fn note_bit(&mut self, value: u32) {
589        if self
590            .source
591            .map(|source| source != BitfieldSource::BitLength)
592            .unwrap_or(false)
593        {
594            return;
595        }
596        self.source.get_or_insert(BitfieldSource::BitLength);
597        self.bit = Some(value);
598    }
599
600    fn note_bit_length(&mut self, value: u32) {
601        if self
602            .source
603            .map(|source| source != BitfieldSource::BitLength)
604            .unwrap_or(false)
605        {
606            return;
607        }
608        self.source.get_or_insert(BitfieldSource::BitLength);
609        self.bit_length = Some(value);
610    }
611
612    fn note_mask(&mut self, mask: u64) {
613        if self.source.is_some() {
614            return;
615        }
616        self.source = Some(BitfieldSource::Mask);
617        self.mask = Some(mask);
618    }
619
620    fn note_byte_order(&mut self, order: ByteOrder) {
621        self.byte_order = Some(order);
622    }
623
624    fn finish(self, node: &str, lengths: &[u32]) -> Result<Option<BitField>, XmlError> {
625        let source = match self.source {
626            Some(source) => source,
627            None => return Ok(None),
628        };
629        let byte_order = self.byte_order.unwrap_or(ByteOrder::Little);
630        if lengths.is_empty() {
631            return Err(XmlError::Invalid(format!(
632                "node {node} is missing register length information"
633            )));
634        }
635        let mut unique_len = None;
636        for len in lengths {
637            if *len == 0 {
638                return Err(XmlError::Invalid(format!(
639                    "node {node} declares zero-length register"
640                )));
641            }
642            if let Some(existing) = unique_len {
643                if existing != *len {
644                    return Err(XmlError::Invalid(format!(
645                        "node {node} uses inconsistent register lengths"
646                    )));
647                }
648            } else {
649                unique_len = Some(*len);
650            }
651        }
652        let len_bytes = unique_len.unwrap_or(0);
653        let total_bits = len_bytes
654            .checked_mul(8)
655            .ok_or_else(|| XmlError::Invalid(format!("node {node} register length overflow")))?;
656
657        let (offset_lsb, bit_length) = match source {
658            BitfieldSource::LsbMsb => {
659                let lsb = self
660                    .lsb
661                    .ok_or_else(|| XmlError::Invalid(format!("node {node} is missing <Lsb>")))?;
662                let msb = self
663                    .msb
664                    .ok_or_else(|| XmlError::Invalid(format!("node {node} is missing <Msb>")))?;
665                let lower = lsb.min(msb);
666                let upper = lsb.max(msb);
667                let length = upper
668                    .checked_sub(lower)
669                    .and_then(|value| value.checked_add(1))
670                    .ok_or_else(|| {
671                        XmlError::Invalid(format!(
672                            "node {node} has invalid bit range <Lsb>={lsb}, <Msb>={msb}"
673                        ))
674                    })?;
675                (lower, length)
676            }
677            BitfieldSource::BitLength => {
678                let bit = self
679                    .bit
680                    .ok_or_else(|| XmlError::Invalid(format!("node {node} is missing <Bit>")))?;
681                let length = self.bit_length.unwrap_or(1);
682                (bit, length)
683            }
684            BitfieldSource::Mask => {
685                let mask = self.mask.ok_or_else(|| {
686                    XmlError::Invalid(format!("node {node} is missing <Mask> value"))
687                })?;
688                if mask == 0 {
689                    return Err(XmlError::Invalid(format!(
690                        "node {node} mask must be non-zero"
691                    )));
692                }
693                let offset = mask.trailing_zeros();
694                let length = mask.count_ones();
695                (offset, length)
696            }
697        };
698
699        if bit_length == 0 {
700            return Err(XmlError::Invalid(format!(
701                "node {node} bitfield must have positive length"
702            )));
703        }
704        if bit_length > 64 {
705            return Err(XmlError::Invalid(format!(
706                "node {node} bitfield length {bit_length} exceeds 64 bits"
707            )));
708        }
709
710        if offset_lsb > u16::MAX as u32 {
711            return Err(XmlError::Invalid(format!(
712                "node {node} bit offset {offset_lsb} exceeds u16 range"
713            )));
714        }
715
716        if bit_length > u16::MAX as u32 {
717            return Err(XmlError::Invalid(format!(
718                "node {node} bit length {bit_length} exceeds u16 range"
719            )));
720        }
721
722        if offset_lsb + bit_length > total_bits {
723            return Err(XmlError::Invalid(format!(
724                "node {node} bitfield exceeds register width"
725            )));
726        }
727
728        let offset = match byte_order {
729            ByteOrder::Little => offset_lsb,
730            ByteOrder::Big => total_bits - bit_length - offset_lsb,
731        };
732
733        Ok(Some(BitField {
734            bit_offset: u16::try_from(offset).map_err(|_| {
735                XmlError::Invalid(format!("node {node} bit offset {offset} exceeds u16 range"))
736            })?,
737            bit_length: u16::try_from(bit_length).map_err(|_| {
738                XmlError::Invalid(format!(
739                    "node {node} bit length {bit_length} exceeds u16 range"
740                ))
741            })?,
742            byte_order,
743        }))
744    }
745}
746
747fn addressing_lengths(addressing: &Addressing) -> Vec<u32> {
748    match addressing {
749        Addressing::Fixed { len, .. } => vec![*len],
750        Addressing::Indirect { len, .. } => vec![*len],
751        Addressing::BySelector { map, .. } => map.iter().map(|(_, (_, len))| *len).collect(),
752    }
753}
754
755fn parse_integer(reader: &mut Reader<&[u8]>, start: BytesStart<'_>) -> Result<NodeDecl, XmlError> {
756    let name = attribute_value_required(&start, b"Name")?;
757    let mut addressing = AddressingBuilder::default();
758    if let Some(addr) = attribute_value(&start, b"Address")? {
759        addressing.set_fixed_address(parse_u64(&addr)?);
760    }
761    if let Some(len) = attribute_value(&start, b"Length")? {
762        let value = parse_u64(&len)?;
763        let len = u32::try_from(value)
764            .map_err(|_| XmlError::Invalid(format!("length out of range for node {name}")))?;
765        addressing.set_length(len);
766    }
767    let mut access = AccessMode::RW;
768    let mut min = None;
769    let mut max = None;
770    let mut inc = None;
771    let mut unit = None;
772    let mut selectors = Vec::new();
773    let mut selected_if: Vec<(String, Vec<String>)> = Vec::new();
774    let mut last_selector: Option<usize> = None;
775    let node_name = start.name().as_ref().to_vec();
776    let mut buf = Vec::new();
777    let mut bitfield = BitfieldBuilder::default();
778    let mut pending_bit_length = false;
779
780    loop {
781        match reader.read_event_into(&mut buf) {
782            Ok(Event::Start(ref e)) => match e.name().as_ref() {
783                b"Address" => {
784                    let text = read_text_start(reader, e)?;
785                    addressing.attach_selected_address(parse_u64(&text)?, None);
786                }
787                TAG_P_ADDRESS => {
788                    let text = read_text_start(reader, e)?;
789                    let target = text.trim();
790                    if !target.is_empty() {
791                        addressing.set_p_address_node(target);
792                    }
793                }
794                b"Length" => {
795                    let text = read_text_start(reader, e)?;
796                    let value = parse_u64(&text)?;
797                    let mut handled = false;
798                    if pending_bit_length {
799                        if let Ok(bit_len) = u32::try_from(value) {
800                            bitfield.note_bit_length(bit_len);
801                            pending_bit_length = false;
802                            handled = true;
803                        } else {
804                            return Err(XmlError::Invalid(format!(
805                                "bitfield length out of range for node {name}"
806                            )));
807                        }
808                    }
809                    if !handled {
810                        let len = u32::try_from(value).map_err(|_| {
811                            XmlError::Invalid(format!("length out of range for node {name}"))
812                        })?;
813                        addressing.apply_length(len);
814                    }
815                }
816                b"AccessMode" => {
817                    let text = read_text_start(reader, e)?;
818                    access = AccessMode::parse(&text)?;
819                }
820                b"Min" => {
821                    let text = read_text_start(reader, e)?;
822                    min = Some(parse_i64(&text)?);
823                }
824                b"Max" => {
825                    let text = read_text_start(reader, e)?;
826                    max = Some(parse_i64(&text)?);
827                }
828                b"Inc" => {
829                    let text = read_text_start(reader, e)?;
830                    inc = Some(parse_i64(&text)?);
831                }
832                b"Unit" => {
833                    let text = read_text_start(reader, e)?;
834                    let trimmed = text.trim();
835                    if !trimmed.is_empty() {
836                        unit = Some(trimmed.to_string());
837                    }
838                }
839                TAG_LSB => {
840                    let text = read_text_start(reader, e)?;
841                    let value = parse_u64(&text)?;
842                    let lsb = u32::try_from(value).map_err(|_| {
843                        XmlError::Invalid(format!("<Lsb> out of range for node {name}"))
844                    })?;
845                    bitfield.note_lsb(lsb);
846                }
847                TAG_MSB => {
848                    let text = read_text_start(reader, e)?;
849                    let value = parse_u64(&text)?;
850                    let msb = u32::try_from(value).map_err(|_| {
851                        XmlError::Invalid(format!("<Msb> out of range for node {name}"))
852                    })?;
853                    bitfield.note_msb(msb);
854                }
855                TAG_BIT => {
856                    let text = read_text_start(reader, e)?;
857                    let value = parse_u64(&text)?;
858                    let bit = u32::try_from(value).map_err(|_| {
859                        XmlError::Invalid(format!("<Bit> out of range for node {name}"))
860                    })?;
861                    bitfield.note_bit(bit);
862                    pending_bit_length = true;
863                }
864                TAG_MASK => {
865                    let text = read_text_start(reader, e)?;
866                    let mask = parse_u64(&text)?;
867                    bitfield.note_mask(mask);
868                    pending_bit_length = false;
869                }
870                TAG_ENDIANNESS | TAG_ENDIANESS | TAG_BYTE_ORDER => {
871                    let text = read_text_start(reader, e)?;
872                    if let Some(order) = ByteOrder::parse(&text) {
873                        bitfield.note_byte_order(order);
874                    }
875                }
876                b"pSelected" => {
877                    let text = read_text_start(reader, e)?;
878                    let selector = text.trim().to_string();
879                    if !selector.is_empty() {
880                        selectors.push(selector.clone());
881                        selected_if.push((selector.clone(), Vec::new()));
882                        last_selector = Some(selected_if.len() - 1);
883                        addressing.register_selector(&selector);
884                    }
885                }
886                b"Selected" => {
887                    let mut value = attribute_value(e, b"Value")?;
888                    if value.is_none() {
889                        value = attribute_value(e, b"Name")?;
890                    }
891                    let text = read_text_start(reader, e)?;
892                    let trimmed = text.trim();
893                    if value.is_none() && !trimmed.is_empty() {
894                        value = Some(trimmed.to_string());
895                    }
896                    if let Some(val) = value.clone() {
897                        addressing.push_selected_value(val.clone());
898                        if let Some(address_attr) = attribute_value(e, b"Address")? {
899                            let len_override = attribute_value(e, b"Length")?
900                                .map(|len| -> Result<u32, XmlError> {
901                                    let value = parse_u64(&len)?;
902                                    u32::try_from(value).map_err(|_| {
903                                        XmlError::Invalid(format!(
904                                            "length out of range for node {name}"
905                                        ))
906                                    })
907                                })
908                                .transpose()?;
909                            addressing
910                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
911                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
912                            let value = parse_u64(&len_attr)?;
913                            let len = u32::try_from(value).map_err(|_| {
914                                XmlError::Invalid(format!("length out of range for node {name}"))
915                            })?;
916                            addressing.apply_length(len);
917                        }
918                    }
919                    if let Some(idx) = last_selector {
920                        if let Some(value) = value {
921                            let trimmed = value.trim();
922                            if !trimmed.is_empty() {
923                                selected_if[idx].1.push(trimmed.to_string());
924                            }
925                        } else if !trimmed.is_empty() {
926                            selected_if[idx].1.push(trimmed.to_string());
927                        }
928                    }
929                }
930                _ => skip_element(reader, e.name().as_ref())?,
931            },
932            Ok(Event::Empty(ref e)) => match e.name().as_ref() {
933                b"pSelected" => {
934                    if let Some(value) = attribute_value(e, b"Name")? {
935                        addressing.register_selector(&value);
936                        selectors.push(value.clone());
937                        selected_if.push((value, Vec::new()));
938                        last_selector = Some(selected_if.len() - 1);
939                    }
940                }
941                TAG_P_ADDRESS => {
942                    if let Some(value) = attribute_value(e, b"Name")? {
943                        let trimmed = value.trim();
944                        if !trimmed.is_empty() {
945                            addressing.set_p_address_node(trimmed);
946                        }
947                    }
948                }
949                TAG_LSB => {
950                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
951                        let parsed = parse_u64(&value)?;
952                        let lsb = u32::try_from(parsed).map_err(|_| {
953                            XmlError::Invalid(format!("<Lsb> out of range for node {name}"))
954                        })?;
955                        bitfield.note_lsb(lsb);
956                    }
957                }
958                TAG_MSB => {
959                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
960                        let parsed = parse_u64(&value)?;
961                        let msb = u32::try_from(parsed).map_err(|_| {
962                            XmlError::Invalid(format!("<Msb> out of range for node {name}"))
963                        })?;
964                        bitfield.note_msb(msb);
965                    }
966                }
967                TAG_BIT => {
968                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
969                        let parsed = parse_u64(&value)?;
970                        let bit = u32::try_from(parsed).map_err(|_| {
971                            XmlError::Invalid(format!("<Bit> out of range for node {name}"))
972                        })?;
973                        bitfield.note_bit(bit);
974                        pending_bit_length = true;
975                    }
976                }
977                TAG_MASK => {
978                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
979                        let mask = parse_u64(&value)?;
980                        bitfield.note_mask(mask);
981                        pending_bit_length = false;
982                    }
983                }
984                TAG_ENDIANNESS | TAG_ENDIANESS | TAG_BYTE_ORDER => {
985                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
986                        if let Some(order) = ByteOrder::parse(&value) {
987                            bitfield.note_byte_order(order);
988                        }
989                    }
990                }
991                b"Selected" => {
992                    if let Some(val) = attribute_value(e, b"Value")? {
993                        addressing.push_selected_value(val.clone());
994                        if let Some(address_attr) = attribute_value(e, b"Address")? {
995                            let len_override = attribute_value(e, b"Length")?
996                                .map(|len| -> Result<u32, XmlError> {
997                                    let value = parse_u64(&len)?;
998                                    u32::try_from(value).map_err(|_| {
999                                        XmlError::Invalid(format!(
1000                                            "length out of range for node {name}"
1001                                        ))
1002                                    })
1003                                })
1004                                .transpose()?;
1005                            addressing
1006                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
1007                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
1008                            let value = parse_u64(&len_attr)?;
1009                            let len = u32::try_from(value).map_err(|_| {
1010                                XmlError::Invalid(format!("length out of range for node {name}"))
1011                            })?;
1012                            addressing.apply_length(len);
1013                        }
1014                        if let Some(idx) = last_selector {
1015                            selected_if[idx].1.push(val);
1016                        }
1017                    }
1018                }
1019                _ => {}
1020            },
1021            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
1022            Ok(Event::Eof) => {
1023                return Err(XmlError::Invalid(format!(
1024                    "unterminated Integer node {name}"
1025                )))
1026            }
1027            Err(err) => return Err(XmlError::Xml(err.to_string())),
1028            _ => {}
1029        }
1030        buf.clear();
1031    }
1032
1033    let min =
1034        min.ok_or_else(|| XmlError::Invalid(format!("Integer node {name} is missing <Min>")))?;
1035    let max =
1036        max.ok_or_else(|| XmlError::Invalid(format!("Integer node {name} is missing <Max>")))?;
1037
1038    let addressing = addressing.finalize(&name, Some(4))?;
1039    let lengths = addressing_lengths(&addressing);
1040    let len = lengths
1041        .first()
1042        .copied()
1043        .ok_or_else(|| XmlError::Invalid(format!("node {name} is missing <Length>")))?;
1044    let bitfield = bitfield.finish(&name, &lengths)?;
1045
1046    Ok(NodeDecl::Integer {
1047        name,
1048        addressing,
1049        len,
1050        access,
1051        min,
1052        max,
1053        inc,
1054        unit,
1055        bitfield,
1056        selectors,
1057        selected_if,
1058    })
1059}
1060
1061fn parse_float(reader: &mut Reader<&[u8]>, start: BytesStart<'_>) -> Result<NodeDecl, XmlError> {
1062    let name = attribute_value_required(&start, b"Name")?;
1063    let mut addressing = AddressingBuilder::default();
1064    if let Some(addr) = attribute_value(&start, b"Address")? {
1065        addressing.set_fixed_address(parse_u64(&addr)?);
1066    }
1067    if let Some(len) = attribute_value(&start, b"Length")? {
1068        let value = parse_u64(&len)?;
1069        let len = u32::try_from(value)
1070            .map_err(|_| XmlError::Invalid(format!("length out of range for node {name}")))?;
1071        addressing.set_length(len);
1072    }
1073    let mut access = AccessMode::RW;
1074    let mut min = None;
1075    let mut max = None;
1076    let mut unit = None;
1077    let mut scale_num: Option<i64> = None;
1078    let mut scale_den: Option<i64> = None;
1079    let mut offset = None;
1080    let mut selectors = Vec::new();
1081    let mut selected_if = Vec::new();
1082    let mut last_selector = None;
1083    let node_name = start.name().as_ref().to_vec();
1084    let mut buf = Vec::new();
1085
1086    loop {
1087        match reader.read_event_into(&mut buf) {
1088            Ok(Event::Start(ref e)) => match e.name().as_ref() {
1089                b"Address" => {
1090                    let text = read_text_start(reader, e)?;
1091                    addressing.attach_selected_address(parse_u64(&text)?, None);
1092                }
1093                TAG_P_ADDRESS => {
1094                    let text = read_text_start(reader, e)?;
1095                    let target = text.trim();
1096                    if !target.is_empty() {
1097                        addressing.set_p_address_node(target);
1098                    }
1099                }
1100                b"Length" => {
1101                    let text = read_text_start(reader, e)?;
1102                    let value = parse_u64(&text)?;
1103                    let len = u32::try_from(value).map_err(|_| {
1104                        XmlError::Invalid(format!("length out of range for node {name}"))
1105                    })?;
1106                    addressing.apply_length(len);
1107                }
1108                b"AccessMode" => {
1109                    let text = read_text_start(reader, e)?;
1110                    access = AccessMode::parse(&text)?;
1111                }
1112                b"Min" => {
1113                    let text = read_text_start(reader, e)?;
1114                    min = Some(parse_f64(&text)?);
1115                }
1116                b"Max" => {
1117                    let text = read_text_start(reader, e)?;
1118                    max = Some(parse_f64(&text)?);
1119                }
1120                b"Unit" => {
1121                    let text = read_text_start(reader, e)?;
1122                    let trimmed = text.trim();
1123                    if !trimmed.is_empty() {
1124                        unit = Some(trimmed.to_string());
1125                    }
1126                }
1127                b"Scale" => {
1128                    let text = read_text_start(reader, e)?;
1129                    let (num, den) = parse_scale(&text)?;
1130                    scale_num = Some(num);
1131                    scale_den = Some(den);
1132                }
1133                b"ScaleNumerator" => {
1134                    let text = read_text_start(reader, e)?;
1135                    scale_num = Some(parse_i64(&text)?);
1136                }
1137                b"ScaleDenominator" => {
1138                    let text = read_text_start(reader, e)?;
1139                    scale_den = Some(parse_i64(&text)?);
1140                }
1141                b"Offset" => {
1142                    let text = read_text_start(reader, e)?;
1143                    offset = Some(parse_f64(&text)?);
1144                }
1145                b"pSelected" => {
1146                    let text = read_text_start(reader, e)?;
1147                    let selector = text.trim().to_string();
1148                    if !selector.is_empty() {
1149                        selectors.push(selector.clone());
1150                        selected_if.push((selector.clone(), Vec::new()));
1151                        last_selector = Some(selected_if.len() - 1);
1152                        addressing.register_selector(&selector);
1153                    }
1154                }
1155                b"Selected" => {
1156                    let mut value = attribute_value(e, b"Value")?;
1157                    if value.is_none() {
1158                        value = attribute_value(e, b"Name")?;
1159                    }
1160                    let text = read_text_start(reader, e)?;
1161                    let trimmed = text.trim();
1162                    if value.is_none() && !trimmed.is_empty() {
1163                        value = Some(trimmed.to_string());
1164                    }
1165                    if let Some(val) = value.clone() {
1166                        addressing.push_selected_value(val.clone());
1167                        if let Some(address_attr) = attribute_value(e, b"Address")? {
1168                            let len_override = attribute_value(e, b"Length")?
1169                                .map(|len| -> Result<u32, XmlError> {
1170                                    let value = parse_u64(&len)?;
1171                                    u32::try_from(value).map_err(|_| {
1172                                        XmlError::Invalid(format!(
1173                                            "length out of range for node {name}"
1174                                        ))
1175                                    })
1176                                })
1177                                .transpose()?;
1178                            addressing
1179                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
1180                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
1181                            let value = parse_u64(&len_attr)?;
1182                            let len = u32::try_from(value).map_err(|_| {
1183                                XmlError::Invalid(format!("length out of range for node {name}"))
1184                            })?;
1185                            addressing.apply_length(len);
1186                        }
1187                    }
1188                    if let Some(idx) = last_selector {
1189                        if let Some(value) = value {
1190                            let trimmed = value.trim();
1191                            if !trimmed.is_empty() {
1192                                selected_if[idx].1.push(trimmed.to_string());
1193                            }
1194                        } else if !trimmed.is_empty() {
1195                            selected_if[idx].1.push(trimmed.to_string());
1196                        }
1197                    }
1198                }
1199                _ => skip_element(reader, e.name().as_ref())?,
1200            },
1201            Ok(Event::Empty(ref e)) => match e.name().as_ref() {
1202                b"pSelected" => {
1203                    if let Some(value) = attribute_value(e, b"Name")? {
1204                        addressing.register_selector(&value);
1205                        selectors.push(value.clone());
1206                        selected_if.push((value, Vec::new()));
1207                        last_selector = Some(selected_if.len() - 1);
1208                    }
1209                }
1210                TAG_P_ADDRESS => {
1211                    if let Some(value) = attribute_value(e, b"Name")? {
1212                        let trimmed = value.trim();
1213                        if !trimmed.is_empty() {
1214                            addressing.set_p_address_node(trimmed);
1215                        }
1216                    }
1217                }
1218                b"Selected" => {
1219                    if let Some(val) = attribute_value(e, b"Value")? {
1220                        addressing.push_selected_value(val.clone());
1221                        if let Some(address_attr) = attribute_value(e, b"Address")? {
1222                            let len_override = attribute_value(e, b"Length")?
1223                                .map(|len| -> Result<u32, XmlError> {
1224                                    let value = parse_u64(&len)?;
1225                                    u32::try_from(value).map_err(|_| {
1226                                        XmlError::Invalid(format!(
1227                                            "length out of range for node {name}"
1228                                        ))
1229                                    })
1230                                })
1231                                .transpose()?;
1232                            addressing
1233                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
1234                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
1235                            let value = parse_u64(&len_attr)?;
1236                            let len = u32::try_from(value).map_err(|_| {
1237                                XmlError::Invalid(format!("length out of range for node {name}"))
1238                            })?;
1239                            addressing.apply_length(len);
1240                        }
1241                        if let Some(idx) = last_selector {
1242                            selected_if[idx].1.push(val);
1243                        }
1244                    }
1245                }
1246                _ => {}
1247            },
1248            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
1249            Ok(Event::Eof) => {
1250                return Err(XmlError::Invalid(format!("unterminated Float node {name}")))
1251            }
1252            Err(err) => return Err(XmlError::Xml(err.to_string())),
1253            _ => {}
1254        }
1255        buf.clear();
1256    }
1257
1258    let min =
1259        min.ok_or_else(|| XmlError::Invalid(format!("Float node {name} is missing <Min>")))?;
1260    let max =
1261        max.ok_or_else(|| XmlError::Invalid(format!("Float node {name} is missing <Max>")))?;
1262    let scale = match (scale_num, scale_den) {
1263        (Some(num), Some(den)) if den != 0 => Some((num, den)),
1264        (None, None) => None,
1265        (Some(num), None) => Some((num, 1)),
1266        _ => None,
1267    };
1268
1269    let addressing = addressing.finalize(&name, Some(8))?;
1270
1271    Ok(NodeDecl::Float {
1272        name,
1273        addressing,
1274        access,
1275        min,
1276        max,
1277        unit,
1278        scale,
1279        offset,
1280        selectors,
1281        selected_if,
1282    })
1283}
1284
1285fn parse_enum(reader: &mut Reader<&[u8]>, start: BytesStart<'_>) -> Result<NodeDecl, XmlError> {
1286    let name = attribute_value_required(&start, b"Name")?;
1287    let mut addressing = AddressingBuilder::default();
1288    if let Some(addr) = attribute_value(&start, b"Address")? {
1289        addressing.set_fixed_address(parse_u64(&addr)?);
1290    }
1291    if let Some(len) = attribute_value(&start, b"Length")? {
1292        let value = parse_u64(&len)?;
1293        let len = u32::try_from(value)
1294            .map_err(|_| XmlError::Invalid(format!("length out of range for node {name}")))?;
1295        addressing.set_length(len);
1296    }
1297    let mut access = AccessMode::RW;
1298    let mut entries = Vec::new();
1299    let mut default = None;
1300    let mut selectors = Vec::new();
1301    let mut selected_if = Vec::new();
1302    let mut last_selector = None;
1303    let node_name = start.name().as_ref().to_vec();
1304    let mut buf = Vec::new();
1305
1306    loop {
1307        match reader.read_event_into(&mut buf) {
1308            Ok(Event::Start(ref e)) => match e.name().as_ref() {
1309                b"Address" => {
1310                    let text = read_text_start(reader, e)?;
1311                    addressing.attach_selected_address(parse_u64(&text)?, None);
1312                }
1313                TAG_P_ADDRESS => {
1314                    let text = read_text_start(reader, e)?;
1315                    let target = text.trim();
1316                    if !target.is_empty() {
1317                        addressing.set_p_address_node(target);
1318                    }
1319                }
1320                b"Length" => {
1321                    let text = read_text_start(reader, e)?;
1322                    let value = parse_u64(&text)?;
1323                    let len = u32::try_from(value).map_err(|_| {
1324                        XmlError::Invalid(format!("length out of range for node {name}"))
1325                    })?;
1326                    addressing.apply_length(len);
1327                }
1328                b"AccessMode" => {
1329                    let text = read_text_start(reader, e)?;
1330                    access = AccessMode::parse(&text)?;
1331                }
1332                b"EnumEntry" => {
1333                    let entry = parse_enum_entry(reader, e.clone())?;
1334                    entries.push(entry);
1335                }
1336                b"pSelected" => {
1337                    let text = read_text_start(reader, e)?;
1338                    let selector = text.trim().to_string();
1339                    if !selector.is_empty() {
1340                        selectors.push(selector.clone());
1341                        selected_if.push((selector.clone(), Vec::new()));
1342                        last_selector = Some(selected_if.len() - 1);
1343                        addressing.register_selector(&selector);
1344                    }
1345                }
1346                b"Selected" => {
1347                    let mut value = attribute_value(e, b"Value")?;
1348                    if value.is_none() {
1349                        value = attribute_value(e, b"Name")?;
1350                    }
1351                    let text = read_text_start(reader, e)?;
1352                    let trimmed = text.trim();
1353                    if value.is_none() && !trimmed.is_empty() {
1354                        value = Some(trimmed.to_string());
1355                    }
1356                    if let Some(val) = value.clone() {
1357                        addressing.push_selected_value(val.clone());
1358                        if let Some(address_attr) = attribute_value(e, b"Address")? {
1359                            let len_override = attribute_value(e, b"Length")?
1360                                .map(|len| -> Result<u32, XmlError> {
1361                                    let value = parse_u64(&len)?;
1362                                    u32::try_from(value).map_err(|_| {
1363                                        XmlError::Invalid(format!(
1364                                            "length out of range for node {name}"
1365                                        ))
1366                                    })
1367                                })
1368                                .transpose()?;
1369                            addressing
1370                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
1371                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
1372                            let value = parse_u64(&len_attr)?;
1373                            let len = u32::try_from(value).map_err(|_| {
1374                                XmlError::Invalid(format!("length out of range for node {name}"))
1375                            })?;
1376                            addressing.apply_length(len);
1377                        }
1378                    }
1379                    if let Some(idx) = last_selector {
1380                        if let Some(value) = value {
1381                            let trimmed = value.trim();
1382                            if !trimmed.is_empty() {
1383                                selected_if[idx].1.push(trimmed.to_string());
1384                            }
1385                        } else if !trimmed.is_empty() {
1386                            selected_if[idx].1.push(trimmed.to_string());
1387                        }
1388                    }
1389                }
1390                b"pValueDefault" => {
1391                    let text = read_text_start(reader, e)?;
1392                    let trimmed = text.trim();
1393                    if !trimmed.is_empty() {
1394                        default = Some(trimmed.to_string());
1395                    }
1396                }
1397                _ => skip_element(reader, e.name().as_ref())?,
1398            },
1399            Ok(Event::Empty(ref e)) => match e.name().as_ref() {
1400                b"EnumEntry" => {
1401                    let entry = parse_enum_entry_empty(e)?;
1402                    entries.push(entry);
1403                }
1404                b"pSelected" => {
1405                    if let Some(value) = attribute_value(e, b"Name")? {
1406                        addressing.register_selector(&value);
1407                        selectors.push(value.clone());
1408                        selected_if.push((value, Vec::new()));
1409                        last_selector = Some(selected_if.len() - 1);
1410                    }
1411                }
1412                b"Selected" => {
1413                    if let Some(val) = attribute_value(e, b"Value")? {
1414                        addressing.push_selected_value(val.clone());
1415                        if let Some(address_attr) = attribute_value(e, b"Address")? {
1416                            let len_override = attribute_value(e, b"Length")?
1417                                .map(|len| -> Result<u32, XmlError> {
1418                                    let value = parse_u64(&len)?;
1419                                    u32::try_from(value).map_err(|_| {
1420                                        XmlError::Invalid(format!(
1421                                            "length out of range for node {name}"
1422                                        ))
1423                                    })
1424                                })
1425                                .transpose()?;
1426                            addressing
1427                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
1428                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
1429                            let value = parse_u64(&len_attr)?;
1430                            let len = u32::try_from(value).map_err(|_| {
1431                                XmlError::Invalid(format!("length out of range for node {name}"))
1432                            })?;
1433                            addressing.apply_length(len);
1434                        }
1435                        if let Some(idx) = last_selector {
1436                            selected_if[idx].1.push(val);
1437                        }
1438                    }
1439                }
1440                _ => {}
1441            },
1442            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
1443            Ok(Event::Eof) => {
1444                return Err(XmlError::Invalid(format!(
1445                    "unterminated Enumeration node {name}"
1446                )))
1447            }
1448            Err(err) => return Err(XmlError::Xml(err.to_string())),
1449            _ => {}
1450        }
1451        buf.clear();
1452    }
1453
1454    if entries.is_empty() {
1455        return Err(XmlError::Invalid(format!(
1456            "Enumeration node {name} declares no <EnumEntry> elements"
1457        )));
1458    }
1459
1460    let addressing = addressing.finalize(&name, Some(4))?;
1461
1462    Ok(NodeDecl::Enum {
1463        name,
1464        addressing,
1465        access,
1466        entries,
1467        default,
1468        selectors,
1469        selected_if,
1470    })
1471}
1472
1473fn parse_boolean(reader: &mut Reader<&[u8]>, start: BytesStart<'_>) -> Result<NodeDecl, XmlError> {
1474    let name = attribute_value_required(&start, b"Name")?;
1475    let mut addressing = AddressingBuilder::default();
1476    if let Some(addr) = attribute_value(&start, b"Address")? {
1477        addressing.set_fixed_address(parse_u64(&addr)?);
1478    }
1479    if let Some(len) = attribute_value(&start, b"Length")? {
1480        let value = parse_u64(&len)?;
1481        let len = u32::try_from(value)
1482            .map_err(|_| XmlError::Invalid(format!("length out of range for node {name}")))?;
1483        addressing.set_length(len);
1484    }
1485    let mut access = AccessMode::RW;
1486    let mut selectors = Vec::new();
1487    let mut selected_if = Vec::new();
1488    let mut last_selector = None;
1489    let node_name = start.name().as_ref().to_vec();
1490    let mut buf = Vec::new();
1491    let mut bitfield = BitfieldBuilder::default();
1492    let mut pending_bit_length = false;
1493
1494    loop {
1495        match reader.read_event_into(&mut buf) {
1496            Ok(Event::Start(ref e)) => match e.name().as_ref() {
1497                b"Address" => {
1498                    let text = read_text_start(reader, e)?;
1499                    addressing.attach_selected_address(parse_u64(&text)?, None);
1500                }
1501                TAG_P_ADDRESS => {
1502                    let text = read_text_start(reader, e)?;
1503                    let target = text.trim();
1504                    if !target.is_empty() {
1505                        addressing.set_p_address_node(target);
1506                    }
1507                }
1508                b"Length" => {
1509                    let text = read_text_start(reader, e)?;
1510                    let value = parse_u64(&text)?;
1511                    let mut handled = false;
1512                    if pending_bit_length {
1513                        if let Ok(bit_len) = u32::try_from(value) {
1514                            bitfield.note_bit_length(bit_len);
1515                            pending_bit_length = false;
1516                            handled = true;
1517                        } else {
1518                            return Err(XmlError::Invalid(format!(
1519                                "bitfield length out of range for node {name}"
1520                            )));
1521                        }
1522                    }
1523                    if !handled {
1524                        let len = u32::try_from(value).map_err(|_| {
1525                            XmlError::Invalid(format!("length out of range for node {name}"))
1526                        })?;
1527                        addressing.apply_length(len);
1528                    }
1529                }
1530                b"AccessMode" => {
1531                    let text = read_text_start(reader, e)?;
1532                    access = AccessMode::parse(&text)?;
1533                }
1534                TAG_LSB => {
1535                    let text = read_text_start(reader, e)?;
1536                    let value = parse_u64(&text)?;
1537                    let lsb = u32::try_from(value).map_err(|_| {
1538                        XmlError::Invalid(format!("<Lsb> out of range for node {name}"))
1539                    })?;
1540                    bitfield.note_lsb(lsb);
1541                }
1542                TAG_MSB => {
1543                    let text = read_text_start(reader, e)?;
1544                    let value = parse_u64(&text)?;
1545                    let msb = u32::try_from(value).map_err(|_| {
1546                        XmlError::Invalid(format!("<Msb> out of range for node {name}"))
1547                    })?;
1548                    bitfield.note_msb(msb);
1549                }
1550                TAG_BIT => {
1551                    let text = read_text_start(reader, e)?;
1552                    let value = parse_u64(&text)?;
1553                    let bit = u32::try_from(value).map_err(|_| {
1554                        XmlError::Invalid(format!("<Bit> out of range for node {name}"))
1555                    })?;
1556                    bitfield.note_bit(bit);
1557                    pending_bit_length = true;
1558                }
1559                TAG_MASK => {
1560                    let text = read_text_start(reader, e)?;
1561                    let mask = parse_u64(&text)?;
1562                    bitfield.note_mask(mask);
1563                    pending_bit_length = false;
1564                }
1565                TAG_ENDIANNESS | TAG_ENDIANESS | TAG_BYTE_ORDER => {
1566                    let text = read_text_start(reader, e)?;
1567                    if let Some(order) = ByteOrder::parse(&text) {
1568                        bitfield.note_byte_order(order);
1569                    }
1570                }
1571                b"pSelected" => {
1572                    let text = read_text_start(reader, e)?;
1573                    let selector = text.trim().to_string();
1574                    if !selector.is_empty() {
1575                        selectors.push(selector.clone());
1576                        selected_if.push((selector.clone(), Vec::new()));
1577                        last_selector = Some(selected_if.len() - 1);
1578                        addressing.register_selector(&selector);
1579                    }
1580                }
1581                b"Selected" => {
1582                    let mut value = attribute_value(e, b"Value")?;
1583                    if value.is_none() {
1584                        value = attribute_value(e, b"Name")?;
1585                    }
1586                    let text = read_text_start(reader, e)?;
1587                    let trimmed = text.trim();
1588                    if value.is_none() && !trimmed.is_empty() {
1589                        value = Some(trimmed.to_string());
1590                    }
1591                    if let Some(val) = value.clone() {
1592                        addressing.push_selected_value(val.clone());
1593                        if let Some(address_attr) = attribute_value(e, b"Address")? {
1594                            let len_override = attribute_value(e, b"Length")?
1595                                .map(|len| -> Result<u32, XmlError> {
1596                                    let value = parse_u64(&len)?;
1597                                    u32::try_from(value).map_err(|_| {
1598                                        XmlError::Invalid(format!(
1599                                            "length out of range for node {name}"
1600                                        ))
1601                                    })
1602                                })
1603                                .transpose()?;
1604                            addressing
1605                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
1606                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
1607                            let value = parse_u64(&len_attr)?;
1608                            let len = u32::try_from(value).map_err(|_| {
1609                                XmlError::Invalid(format!("length out of range for node {name}"))
1610                            })?;
1611                            addressing.apply_length(len);
1612                        }
1613                    }
1614                    if let Some(idx) = last_selector {
1615                        if let Some(value) = value {
1616                            let trimmed = value.trim();
1617                            if !trimmed.is_empty() {
1618                                selected_if[idx].1.push(trimmed.to_string());
1619                            }
1620                        } else if !trimmed.is_empty() {
1621                            selected_if[idx].1.push(trimmed.to_string());
1622                        }
1623                    }
1624                }
1625                _ => skip_element(reader, e.name().as_ref())?,
1626            },
1627            Ok(Event::Empty(ref e)) => match e.name().as_ref() {
1628                b"pSelected" => {
1629                    if let Some(value) = attribute_value(e, b"Name")? {
1630                        addressing.register_selector(&value);
1631                        selectors.push(value.clone());
1632                        selected_if.push((value, Vec::new()));
1633                        last_selector = Some(selected_if.len() - 1);
1634                    }
1635                }
1636                TAG_P_ADDRESS => {
1637                    if let Some(value) = attribute_value(e, b"Name")? {
1638                        let trimmed = value.trim();
1639                        if !trimmed.is_empty() {
1640                            addressing.set_p_address_node(trimmed);
1641                        }
1642                    }
1643                }
1644                TAG_LSB => {
1645                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
1646                        let parsed = parse_u64(&value)?;
1647                        let lsb = u32::try_from(parsed).map_err(|_| {
1648                            XmlError::Invalid(format!("<Lsb> out of range for node {name}"))
1649                        })?;
1650                        bitfield.note_lsb(lsb);
1651                    }
1652                }
1653                TAG_MSB => {
1654                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
1655                        let parsed = parse_u64(&value)?;
1656                        let msb = u32::try_from(parsed).map_err(|_| {
1657                            XmlError::Invalid(format!("<Msb> out of range for node {name}"))
1658                        })?;
1659                        bitfield.note_msb(msb);
1660                    }
1661                }
1662                TAG_BIT => {
1663                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
1664                        let parsed = parse_u64(&value)?;
1665                        let bit = u32::try_from(parsed).map_err(|_| {
1666                            XmlError::Invalid(format!("<Bit> out of range for node {name}"))
1667                        })?;
1668                        bitfield.note_bit(bit);
1669                        pending_bit_length = true;
1670                    }
1671                }
1672                TAG_MASK => {
1673                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
1674                        let mask = parse_u64(&value)?;
1675                        bitfield.note_mask(mask);
1676                        pending_bit_length = false;
1677                    }
1678                }
1679                TAG_ENDIANNESS | TAG_ENDIANESS | TAG_BYTE_ORDER => {
1680                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
1681                        if let Some(order) = ByteOrder::parse(&value) {
1682                            bitfield.note_byte_order(order);
1683                        }
1684                    }
1685                }
1686                b"Selected" => {
1687                    if let Some(val) = attribute_value(e, b"Value")? {
1688                        addressing.push_selected_value(val.clone());
1689                        if let Some(address_attr) = attribute_value(e, b"Address")? {
1690                            let len_override = attribute_value(e, b"Length")?
1691                                .map(|len| -> Result<u32, XmlError> {
1692                                    let value = parse_u64(&len)?;
1693                                    u32::try_from(value).map_err(|_| {
1694                                        XmlError::Invalid(format!(
1695                                            "length out of range for node {name}"
1696                                        ))
1697                                    })
1698                                })
1699                                .transpose()?;
1700                            addressing
1701                                .attach_selected_address(parse_u64(&address_attr)?, len_override);
1702                        } else if let Some(len_attr) = attribute_value(e, b"Length")? {
1703                            let value = parse_u64(&len_attr)?;
1704                            let len = u32::try_from(value).map_err(|_| {
1705                                XmlError::Invalid(format!("length out of range for node {name}"))
1706                            })?;
1707                            addressing.apply_length(len);
1708                        }
1709                        if let Some(idx) = last_selector {
1710                            selected_if[idx].1.push(val);
1711                        }
1712                    }
1713                }
1714                _ => {}
1715            },
1716            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
1717            Ok(Event::Eof) => {
1718                return Err(XmlError::Invalid(format!(
1719                    "unterminated Boolean node {name}"
1720                )))
1721            }
1722            Err(err) => return Err(XmlError::Xml(err.to_string())),
1723            _ => {}
1724        }
1725        buf.clear();
1726    }
1727
1728    let addressing = addressing.finalize(&name, Some(4))?;
1729    let lengths = addressing_lengths(&addressing);
1730    let len = lengths
1731        .first()
1732        .copied()
1733        .ok_or_else(|| XmlError::Invalid(format!("node {name} is missing <Length>")))?;
1734    let bitfield = match bitfield.finish(&name, &lengths)? {
1735        Some(field) => field,
1736        None if len == 1 => BitField {
1737            bit_offset: 0,
1738            bit_length: 1,
1739            byte_order: ByteOrder::Little,
1740        },
1741        None => {
1742            return Err(XmlError::Invalid(format!(
1743                "Boolean node {name} requires explicit bitfield metadata"
1744            )))
1745        }
1746    };
1747
1748    Ok(NodeDecl::Boolean {
1749        name,
1750        addressing,
1751        len,
1752        access,
1753        bitfield,
1754        selectors,
1755        selected_if,
1756    })
1757}
1758
1759fn parse_command(reader: &mut Reader<&[u8]>, start: BytesStart<'_>) -> Result<NodeDecl, XmlError> {
1760    let name = attribute_value_required(&start, b"Name")?;
1761    let mut address = None;
1762    let mut length = None;
1763    let node_name = start.name().as_ref().to_vec();
1764    let mut buf = Vec::new();
1765
1766    loop {
1767        match reader.read_event_into(&mut buf) {
1768            Ok(Event::Start(ref e)) => match e.name().as_ref() {
1769                b"Address" => {
1770                    let text = read_text_start(reader, e)?;
1771                    address = Some(parse_u64(&text)?);
1772                }
1773                b"Length" => {
1774                    let text = read_text_start(reader, e)?;
1775                    let value = parse_u64(&text)?;
1776                    length = Some(u32::try_from(value).map_err(|_| {
1777                        XmlError::Invalid(format!("length out of range for node {name}"))
1778                    })?);
1779                }
1780                _ => skip_element(reader, e.name().as_ref())?,
1781            },
1782            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
1783            Ok(Event::Eof) => {
1784                return Err(XmlError::Invalid(format!(
1785                    "unterminated Command node {name}"
1786                )))
1787            }
1788            Err(err) => return Err(XmlError::Xml(err.to_string())),
1789            _ => {}
1790        }
1791        buf.clear();
1792    }
1793
1794    let address = address
1795        .ok_or_else(|| XmlError::Invalid(format!("Command node {name} is missing <Address>")))?;
1796    let length = length.unwrap_or(1);
1797
1798    Ok(NodeDecl::Command {
1799        name,
1800        address,
1801        len: length,
1802    })
1803}
1804
1805fn parse_command_empty(start: &BytesStart<'_>) -> Result<NodeDecl, XmlError> {
1806    let name = attribute_value_required(start, b"Name")?;
1807    let address = attribute_value_required(start, b"Address")?;
1808    let address = parse_u64(&address)?;
1809    let length = attribute_value(start, b"Length")?;
1810    let length = match length {
1811        Some(value) => {
1812            let raw = parse_u64(&value)?;
1813            u32::try_from(raw)
1814                .map_err(|_| XmlError::Invalid("command length out of range".into()))?
1815        }
1816        None => 1,
1817    };
1818    Ok(NodeDecl::Command {
1819        name,
1820        address,
1821        len: length,
1822    })
1823}
1824
1825fn parse_category(reader: &mut Reader<&[u8]>, start: BytesStart<'_>) -> Result<NodeDecl, XmlError> {
1826    let name = attribute_value_required(&start, b"Name")?;
1827    let node_name = start.name().as_ref().to_vec();
1828    let mut children = Vec::new();
1829    let mut buf = Vec::new();
1830
1831    loop {
1832        match reader.read_event_into(&mut buf) {
1833            Ok(Event::Start(ref e)) => match e.name().as_ref() {
1834                b"pFeature" => {
1835                    let text = read_text_start(reader, e)?;
1836                    let trimmed = text.trim();
1837                    if !trimmed.is_empty() {
1838                        children.push(trimmed.to_string());
1839                    }
1840                }
1841                _ => skip_element(reader, e.name().as_ref())?,
1842            },
1843            Ok(Event::Empty(ref e)) if e.name().as_ref() == b"pFeature" => {
1844                if let Some(value) = attribute_value(e, b"Name")? {
1845                    if !value.is_empty() {
1846                        children.push(value);
1847                    }
1848                }
1849            }
1850            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
1851            Ok(Event::Eof) => {
1852                return Err(XmlError::Invalid(format!(
1853                    "unterminated Category node {name}"
1854                )))
1855            }
1856            Err(err) => return Err(XmlError::Xml(err.to_string())),
1857            _ => {}
1858        }
1859        buf.clear();
1860    }
1861
1862    Ok(NodeDecl::Category { name, children })
1863}
1864
1865fn parse_category_empty(start: &BytesStart<'_>) -> Result<NodeDecl, XmlError> {
1866    let name = attribute_value_required(start, b"Name")?;
1867    Ok(NodeDecl::Category {
1868        name,
1869        children: Vec::new(),
1870    })
1871}
1872
1873fn parse_swissknife(
1874    reader: &mut Reader<&[u8]>,
1875    start: BytesStart<'_>,
1876) -> Result<NodeDecl, XmlError> {
1877    let name = attribute_value_required(&start, b"Name")?;
1878    let mut expr: Option<String> = None;
1879    let mut variables: Vec<(String, String)> = Vec::new();
1880    let mut output = SkOutput::Float;
1881    let node_name = start.name().as_ref().to_vec();
1882    let mut buf = Vec::new();
1883
1884    loop {
1885        match reader.read_event_into(&mut buf) {
1886            Ok(Event::Start(ref e)) => match e.name().as_ref() {
1887                b"Expression" => {
1888                    let text = read_text_start(reader, e)?;
1889                    let trimmed = text.trim();
1890                    if trimmed.is_empty() {
1891                        return Err(XmlError::Invalid(format!(
1892                            "SwissKnife node {name} has empty <Expression>"
1893                        )));
1894                    }
1895                    expr = Some(trimmed.to_string());
1896                }
1897                b"pVariable" => {
1898                    let var_name = attribute_value_required(e, b"Name")?;
1899                    let text = read_text_start(reader, e)?;
1900                    let target = text.trim();
1901                    if target.is_empty() {
1902                        return Err(XmlError::Invalid(format!(
1903                            "SwissKnife node {name} has empty <pVariable>"
1904                        )));
1905                    }
1906                    variables.push((var_name, target.to_string()));
1907                }
1908                b"Output" => {
1909                    let text = read_text_start(reader, e)?;
1910                    if let Some(kind) = SkOutput::parse(&text) {
1911                        output = kind;
1912                    }
1913                }
1914                _ => skip_element(reader, e.name().as_ref())?,
1915            },
1916            Ok(Event::Empty(ref e)) => match e.name().as_ref() {
1917                b"pVariable" => {
1918                    let var_name = attribute_value_required(e, b"Name")?;
1919                    if let Some(target) = attribute_value(e, TAG_VALUE)? {
1920                        if target.is_empty() {
1921                            return Err(XmlError::Invalid(format!(
1922                                "SwissKnife node {name} has empty <pVariable/>"
1923                            )));
1924                        }
1925                        variables.push((var_name, target));
1926                    } else {
1927                        return Err(XmlError::Invalid(format!(
1928                            "SwissKnife node {name} missing variable target"
1929                        )));
1930                    }
1931                }
1932                b"Expression" => {
1933                    let text = attribute_value_required(e, TAG_VALUE)?;
1934                    let trimmed = text.trim();
1935                    if trimmed.is_empty() {
1936                        return Err(XmlError::Invalid(format!(
1937                            "SwissKnife node {name} has empty <Expression/>"
1938                        )));
1939                    }
1940                    expr = Some(trimmed.to_string());
1941                }
1942                b"Output" => {
1943                    if let Some(value) = attribute_value(e, TAG_VALUE)? {
1944                        if let Some(kind) = SkOutput::parse(&value) {
1945                            output = kind;
1946                        }
1947                    }
1948                }
1949                _ => {}
1950            },
1951            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
1952            Ok(Event::Eof) => {
1953                return Err(XmlError::Invalid(format!(
1954                    "unterminated SwissKnife node {name}"
1955                )))
1956            }
1957            Err(err) => return Err(XmlError::Xml(err.to_string())),
1958            _ => {}
1959        }
1960        buf.clear();
1961    }
1962
1963    let expr = expr.ok_or_else(|| {
1964        XmlError::Invalid(format!("SwissKnife node {name} is missing <Expression>"))
1965    })?;
1966    if variables.is_empty() {
1967        return Err(XmlError::Invalid(format!(
1968            "SwissKnife node {name} must declare at least one <pVariable>"
1969        )));
1970    }
1971
1972    Ok(NodeDecl::SwissKnife(SwissKnifeDecl {
1973        name,
1974        expr,
1975        variables,
1976        output,
1977    }))
1978}
1979
1980fn parse_enum_entry(
1981    reader: &mut Reader<&[u8]>,
1982    start: BytesStart<'_>,
1983) -> Result<EnumEntryDecl, XmlError> {
1984    let mut name = attribute_value_required(&start, b"Name")?;
1985    let mut literal = attribute_value(&start, TAG_VALUE)?;
1986    let mut provider = attribute_value(&start, TAG_P_VALUE)?;
1987    let mut display_name = attribute_value(&start, TAG_DISPLAY_NAME)?;
1988    let node_name = start.name().as_ref().to_vec();
1989    let mut buf = Vec::new();
1990
1991    loop {
1992        match reader.read_event_into(&mut buf) {
1993            Ok(Event::Start(ref e)) => match e.name().as_ref() {
1994                TAG_VALUE => {
1995                    let text = read_text_start(reader, e)?;
1996                    let trimmed = text.trim();
1997                    if !trimmed.is_empty() {
1998                        literal = Some(trimmed.to_string());
1999                    }
2000                }
2001                TAG_P_VALUE => {
2002                    let text = read_text_start(reader, e)?;
2003                    let trimmed = text.trim();
2004                    if !trimmed.is_empty() {
2005                        provider = Some(trimmed.to_string());
2006                    }
2007                }
2008                TAG_DISPLAY_NAME => {
2009                    let text = read_text_start(reader, e)?;
2010                    let trimmed = text.trim();
2011                    if !trimmed.is_empty() {
2012                        display_name = Some(trimmed.to_string());
2013                    }
2014                }
2015                b"Name" => {
2016                    let text = read_text_start(reader, e)?;
2017                    let trimmed = text.trim();
2018                    if !trimmed.is_empty() {
2019                        name = trimmed.to_string();
2020                    }
2021                }
2022                _ => skip_element(reader, e.name().as_ref())?,
2023            },
2024            Ok(Event::End(ref e)) if e.name().as_ref() == node_name.as_slice() => break,
2025            Ok(Event::Eof) => {
2026                return Err(XmlError::Invalid("unterminated EnumEntry element".into()))
2027            }
2028            Err(err) => return Err(XmlError::Xml(err.to_string())),
2029            _ => {}
2030        }
2031        buf.clear();
2032    }
2033
2034    build_enum_entry(name, literal, provider, display_name)
2035}
2036
2037fn parse_enum_entry_empty(start: &BytesStart<'_>) -> Result<EnumEntryDecl, XmlError> {
2038    let name = attribute_value_required(start, b"Name")?;
2039    let literal = attribute_value(start, TAG_VALUE)?;
2040    let provider = attribute_value(start, TAG_P_VALUE)?;
2041    let display_name = attribute_value(start, TAG_DISPLAY_NAME)?;
2042    build_enum_entry(name, literal, provider, display_name)
2043}
2044
2045fn build_enum_entry(
2046    name: String,
2047    literal: Option<String>,
2048    provider: Option<String>,
2049    display_name: Option<String>,
2050) -> Result<EnumEntryDecl, XmlError> {
2051    let literal = literal.and_then(|value| {
2052        let trimmed = value.trim();
2053        if trimmed.is_empty() {
2054            None
2055        } else {
2056            Some(trimmed.to_string())
2057        }
2058    });
2059    let provider = provider.and_then(|value| {
2060        let trimmed = value.trim();
2061        if trimmed.is_empty() {
2062            None
2063        } else {
2064            Some(trimmed.to_string())
2065        }
2066    });
2067
2068    if literal.is_some() && provider.is_some() {
2069        warn!(
2070            entry = %name,
2071            "EnumEntry specifies both <Value> and <pValue>; preferring provider"
2072        );
2073    }
2074
2075    let value = if let Some(node) = provider {
2076        EnumValueSrc::FromNode(node)
2077    } else if let Some(value) = literal {
2078        EnumValueSrc::Literal(parse_i64(&value)?)
2079    } else {
2080        return Err(XmlError::Invalid(format!(
2081            "EnumEntry {name} is missing <Value> or <pValue>"
2082        )));
2083    };
2084
2085    Ok(EnumEntryDecl {
2086        name,
2087        value,
2088        display_name,
2089    })
2090}
2091
2092fn parse_scale(text: &str) -> Result<(i64, i64), XmlError> {
2093    let trimmed = text.trim();
2094    if trimmed.is_empty() {
2095        return Err(XmlError::Invalid("empty scale value".into()));
2096    }
2097    if let Some((num, den)) = trimmed.split_once('/') {
2098        let num = parse_i64(num)?;
2099        let den = parse_i64(den)?;
2100        if den == 0 {
2101            return Err(XmlError::Invalid("scale denominator is zero".into()));
2102        }
2103        Ok((num, den))
2104    } else {
2105        let value = parse_f64(trimmed)?;
2106        if value == 0.0 {
2107            return Err(XmlError::Invalid("scale value is zero".into()));
2108        }
2109        // Approximate decimal scale as a rational using denominator 1_000_000.
2110        let den = 1_000_000i64;
2111        let num = (value * den as f64).round() as i64;
2112        Ok((num, den))
2113    }
2114}
2115
2116fn handle_start(
2117    event: &BytesStart<'_>,
2118    depth: usize,
2119    schema_version: &mut Option<String>,
2120    top_level: &mut Vec<String>,
2121) -> Result<(), XmlError> {
2122    if depth == 1 && schema_version.is_none() {
2123        *schema_version = extract_schema_version(event);
2124    } else if depth == 2 {
2125        if let Some(name) = attribute_value(event, b"Name")? {
2126            top_level.push(name);
2127        } else {
2128            top_level.push(String::from_utf8_lossy(event.name().as_ref()).to_string());
2129        }
2130    }
2131    Ok(())
2132}
2133
2134fn extract_schema_version(event: &BytesStart<'_>) -> Option<String> {
2135    let major = attribute_value(event, b"SchemaMajorVersion").ok().flatten();
2136    let minor = attribute_value(event, b"SchemaMinorVersion").ok().flatten();
2137    let sub = attribute_value(event, b"SchemaSubMinorVersion")
2138        .ok()
2139        .flatten();
2140    if major.is_none() && minor.is_none() && sub.is_none() {
2141        None
2142    } else {
2143        let major = major.unwrap_or_else(|| "0".to_string());
2144        let minor = minor.unwrap_or_else(|| "0".to_string());
2145        let sub = sub.unwrap_or_else(|| "0".to_string());
2146        Some(format!("{major}.{minor}.{sub}"))
2147    }
2148}
2149
2150fn read_text_start(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<String, XmlError> {
2151    let end_buf = start.name().as_ref().to_vec();
2152    reader
2153        .read_text(QName(&end_buf))
2154        .map(|cow| cow.into_owned())
2155        .map_err(|err| XmlError::Xml(err.to_string()))
2156}
2157
2158fn attribute_value(event: &BytesStart<'_>, name: &[u8]) -> Result<Option<String>, XmlError> {
2159    for attr in event.attributes() {
2160        let attr = attr.map_err(|err| XmlError::Xml(err.to_string()))?;
2161        if attr.key.as_ref() == name {
2162            let value = attr
2163                .unescape_value()
2164                .map_err(|err| XmlError::Xml(err.to_string()))?;
2165            let trimmed = value.trim().to_string();
2166            if trimmed.is_empty() {
2167                return Ok(None);
2168            }
2169            return Ok(Some(trimmed));
2170        }
2171    }
2172    Ok(None)
2173}
2174
2175fn attribute_value_required(event: &BytesStart<'_>, name: &[u8]) -> Result<String, XmlError> {
2176    attribute_value(event, name)?.ok_or_else(|| {
2177        XmlError::Invalid(format!(
2178            "missing attribute {}",
2179            String::from_utf8_lossy(name)
2180        ))
2181    })
2182}
2183
2184fn parse_u64(value: &str) -> Result<u64, XmlError> {
2185    let trimmed = value.trim();
2186    if let Some(hex) = trimmed.strip_prefix("0x") {
2187        let hex = hex.replace('_', "");
2188        u64::from_str_radix(&hex, 16)
2189            .map_err(|err| XmlError::Invalid(format!("invalid hex value: {err}")))
2190    } else {
2191        let dec = trimmed.replace('_', "");
2192        dec.parse()
2193            .map_err(|err| XmlError::Invalid(format!("invalid integer: {err}")))
2194    }
2195}
2196
2197fn parse_i64(value: &str) -> Result<i64, XmlError> {
2198    let trimmed = value.trim();
2199    if let Some(hex) = trimmed.strip_prefix("0x") {
2200        let hex = hex.replace('_', "");
2201        i64::from_str_radix(&hex, 16)
2202            .map_err(|err| XmlError::Invalid(format!("invalid hex value: {err}")))
2203    } else {
2204        let dec = trimmed.replace('_', "");
2205        dec.parse()
2206            .map_err(|err| XmlError::Invalid(format!("invalid integer: {err}")))
2207    }
2208}
2209
2210fn parse_f64(value: &str) -> Result<f64, XmlError> {
2211    value
2212        .trim()
2213        .parse()
2214        .map_err(|err| XmlError::Invalid(format!("invalid float: {err}")))
2215}
2216
2217fn skip_element(reader: &mut Reader<&[u8]>, name: &[u8]) -> Result<(), XmlError> {
2218    let mut depth = 1usize;
2219    let mut buf = Vec::new();
2220    while depth > 0 {
2221        match reader.read_event_into(&mut buf) {
2222            Ok(Event::Start(_)) => depth += 1,
2223            Ok(Event::End(ref e)) => {
2224                if e.name().as_ref() == name {
2225                    depth -= 1;
2226                }
2227            }
2228            Ok(Event::Eof) => {
2229                return Err(XmlError::Invalid("unexpected end of file".into()));
2230            }
2231            Err(err) => return Err(XmlError::Xml(err.to_string())),
2232            _ => {}
2233        }
2234        buf.clear();
2235    }
2236    Ok(())
2237}
2238
2239#[derive(Debug, Clone, PartialEq, Eq)]
2240pub struct MinimalXmlInfo {
2241    pub schema_version: Option<String>,
2242    pub top_level_features: Vec<String>,
2243}
2244
2245fn first_cstring(bytes: &[u8]) -> Option<String> {
2246    let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
2247    let slice = &bytes[..end];
2248    let value = String::from_utf8_lossy(slice).trim().to_string();
2249    if value.is_empty() {
2250        None
2251    } else {
2252        Some(value)
2253    }
2254}
2255
2256#[derive(Debug)]
2257enum UrlLocation {
2258    Local { address: u64, length: usize },
2259    LocalNamed(String),
2260    Http(String),
2261    File(String),
2262}
2263
2264impl UrlLocation {
2265    fn parse(url: &str) -> Result<Self, XmlError> {
2266        if let Some(rest) = url.strip_prefix("local:") {
2267            parse_local_url(rest)
2268        } else if url.starts_with("http://") || url.starts_with("https://") {
2269            Ok(UrlLocation::Http(url.to_string()))
2270        } else if url.starts_with("file://") {
2271            Ok(UrlLocation::File(url.to_string()))
2272        } else {
2273            Err(XmlError::Unsupported(format!("unknown URL scheme: {url}")))
2274        }
2275    }
2276}
2277
2278fn parse_local_url(rest: &str) -> Result<UrlLocation, XmlError> {
2279    let trimmed = rest.trim();
2280    if trimmed.is_empty() {
2281        return Err(XmlError::Invalid("empty local URL".into()));
2282    }
2283    let mut address = None;
2284    let mut length = None;
2285    for part in trimmed.split([';', ',']) {
2286        let token = part.trim();
2287        if token.is_empty() {
2288            continue;
2289        }
2290        if let Some((key, value)) = token.split_once('=') {
2291            let key = key.trim().to_ascii_lowercase();
2292            let value = value.trim();
2293            match key.as_str() {
2294                "address" | "addr" | "offset" => {
2295                    address = Some(parse_u64(value)?);
2296                }
2297                "length" | "size" => {
2298                    let len = parse_u64(value)?;
2299                    length = Some(
2300                        len.try_into()
2301                            .map_err(|_| XmlError::Invalid("length does not fit usize".into()))?,
2302                    );
2303                }
2304                _ => {}
2305            }
2306        } else if token.starts_with("0x") {
2307            address = Some(parse_u64(token)?);
2308        } else {
2309            return Ok(UrlLocation::LocalNamed(token.to_string()));
2310        }
2311    }
2312    match (address, length) {
2313        (Some(address), Some(length)) => Ok(UrlLocation::Local { address, length }),
2314        _ => Err(XmlError::Invalid(format!("unsupported local URL: {rest}"))),
2315    }
2316}
2317
2318#[cfg(test)]
2319mod tests {
2320    use super::*;
2321
2322    const FIXTURE: &str = r#"
2323        <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="2" SchemaSubMinorVersion="3">
2324            <Category Name="Root">
2325                <pFeature>Gain</pFeature>
2326                <pFeature>GainSelector</pFeature>
2327            </Category>
2328            <Integer Name="Width">
2329                <Address>0x0000_0100</Address>
2330                <Length>4</Length>
2331                <AccessMode>RW</AccessMode>
2332                <Min>16</Min>
2333                <Max>4096</Max>
2334                <Inc>2</Inc>
2335            </Integer>
2336            <Float Name="ExposureTime">
2337                <Address>0x0000_0200</Address>
2338                <Length>4</Length>
2339                <AccessMode>RW</AccessMode>
2340                <Min>10.0</Min>
2341                <Max>200000.0</Max>
2342                <Scale>1/1000</Scale>
2343                <Offset>0.0</Offset>
2344            </Float>
2345            <Enumeration Name="GainSelector">
2346                <Address>0x0000_0300</Address>
2347                <Length>2</Length>
2348                <AccessMode>RW</AccessMode>
2349                <EnumEntry Name="AnalogAll" Value="0" />
2350                <EnumEntry Name="DigitalAll" Value="1" />
2351            </Enumeration>
2352            <Integer Name="Gain">
2353                <Address>0x0000_0304</Address>
2354                <Length>2</Length>
2355                <AccessMode>RW</AccessMode>
2356                <Min>0</Min>
2357                <Max>48</Max>
2358                <pSelected>GainSelector</pSelected>
2359                <Selected>AnalogAll</Selected>
2360            </Integer>
2361            <Boolean Name="GammaEnable">
2362                <Address>0x0000_0400</Address>
2363                <Length>1</Length>
2364                <AccessMode>RW</AccessMode>
2365            </Boolean>
2366            <Command Name="AcquisitionStart">
2367                <Address>0x0000_0500</Address>
2368                <Length>4</Length>
2369            </Command>
2370        </RegisterDescription>
2371    "#;
2372
2373    #[tokio::test]
2374    async fn parse_minimal_xml() {
2375        let info = parse_into_minimal_nodes(FIXTURE).expect("parse xml");
2376        assert_eq!(info.schema_version.as_deref(), Some("1.2.3"));
2377        assert_eq!(info.top_level_features.len(), 7);
2378        assert_eq!(info.top_level_features[0], "Root");
2379
2380        let data = b"local:address=0x10;length=0x3\0".to_vec();
2381        let xml_payload = b"<a/>".to_vec();
2382        let loaded = fetch_and_load_xml(|addr, len| {
2383            let data = data.clone();
2384            let xml_payload = xml_payload.clone();
2385            async move {
2386                if addr == FIRST_URL_ADDRESS {
2387                    Ok(data)
2388                } else if addr == 0x10 && len == 0x3 {
2389                    Ok(xml_payload)
2390                } else {
2391                    Err(XmlError::Transport("unexpected read".into()))
2392                }
2393            }
2394        })
2395        .await
2396        .expect("load xml");
2397        assert_eq!(loaded, "<a/>");
2398    }
2399
2400    #[test]
2401    fn parse_fixture_model() {
2402        let model = parse(FIXTURE).expect("parse fixture");
2403        assert_eq!(model.version, "1.2.3");
2404        assert_eq!(model.nodes.len(), 7);
2405        match &model.nodes[0] {
2406            NodeDecl::Category { name, children } => {
2407                assert_eq!(name, "Root");
2408                assert_eq!(
2409                    children,
2410                    &vec!["Gain".to_string(), "GainSelector".to_string()]
2411                );
2412            }
2413            other => panic!("unexpected node: {other:?}"),
2414        }
2415        match &model.nodes[1] {
2416            NodeDecl::Integer {
2417                name,
2418                min,
2419                max,
2420                inc,
2421                ..
2422            } => {
2423                assert_eq!(name, "Width");
2424                assert_eq!(*min, 16);
2425                assert_eq!(*max, 4096);
2426                assert_eq!(*inc, Some(2));
2427            }
2428            other => panic!("unexpected node: {other:?}"),
2429        }
2430        match &model.nodes[2] {
2431            NodeDecl::Float {
2432                name,
2433                scale,
2434                offset,
2435                ..
2436            } => {
2437                assert_eq!(name, "ExposureTime");
2438                assert_eq!(*scale, Some((1, 1000)));
2439                assert_eq!(*offset, Some(0.0));
2440            }
2441            other => panic!("unexpected node: {other:?}"),
2442        }
2443        match &model.nodes[3] {
2444            NodeDecl::Enum { name, entries, .. } => {
2445                assert_eq!(name, "GainSelector");
2446                assert_eq!(entries.len(), 2);
2447                assert!(matches!(entries[0].value, EnumValueSrc::Literal(0)));
2448                assert!(matches!(entries[1].value, EnumValueSrc::Literal(1)));
2449            }
2450            other => panic!("unexpected node: {other:?}"),
2451        }
2452        match &model.nodes[4] {
2453            NodeDecl::Integer {
2454                name, selected_if, ..
2455            } => {
2456                assert_eq!(name, "Gain");
2457                assert_eq!(selected_if.len(), 1);
2458                assert_eq!(selected_if[0].0, "GainSelector");
2459                assert_eq!(selected_if[0].1, vec!["AnalogAll".to_string()]);
2460            }
2461            other => panic!("unexpected node: {other:?}"),
2462        }
2463    }
2464
2465    #[test]
2466    fn parse_swissknife_node() {
2467        const XML: &str = r#"
2468            <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
2469                <Integer Name="GainRaw">
2470                    <Address>0x3000</Address>
2471                    <Length>4</Length>
2472                    <AccessMode>RW</AccessMode>
2473                    <Min>0</Min>
2474                    <Max>1000</Max>
2475                </Integer>
2476                <Float Name="Offset">
2477                    <Address>0x3008</Address>
2478                    <Length>4</Length>
2479                    <AccessMode>RW</AccessMode>
2480                    <Min>-100.0</Min>
2481                    <Max>100.0</Max>
2482                </Float>
2483                <SwissKnife Name="ComputedGain">
2484                    <Expression>(GainRaw * 0.5) + Offset</Expression>
2485                    <pVariable Name="GainRaw">GainRaw</pVariable>
2486                    <pVariable Name="Offset">Offset</pVariable>
2487                    <Output>Float</Output>
2488                </SwissKnife>
2489            </RegisterDescription>
2490        "#;
2491
2492        let model = parse(XML).expect("parse swissknife xml");
2493        assert_eq!(model.nodes.len(), 3);
2494        let swiss = model
2495            .nodes
2496            .iter()
2497            .find_map(|decl| match decl {
2498                NodeDecl::SwissKnife(node) => Some(node),
2499                _ => None,
2500            })
2501            .expect("swissknife present");
2502        assert_eq!(swiss.name, "ComputedGain");
2503        assert_eq!(swiss.expr, "(GainRaw * 0.5) + Offset");
2504        assert_eq!(swiss.output, SkOutput::Float);
2505        assert_eq!(swiss.variables.len(), 2);
2506        assert_eq!(
2507            swiss.variables[0],
2508            ("GainRaw".to_string(), "GainRaw".to_string())
2509        );
2510        assert_eq!(
2511            swiss.variables[1],
2512            ("Offset".to_string(), "Offset".to_string())
2513        );
2514    }
2515
2516    #[test]
2517    fn parse_enum_entry_with_pvalue() {
2518        const XML: &str = r#"
2519            <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
2520                <Enumeration Name="Mode">
2521                    <Address>0x0000_4000</Address>
2522                    <Length>4</Length>
2523                    <AccessMode>RW</AccessMode>
2524                    <EnumEntry Name="Fixed10">
2525                        <Value>10</Value>
2526                    </EnumEntry>
2527                    <EnumEntry Name="DynFromReg">
2528                        <pValue>RegModeVal</pValue>
2529                    </EnumEntry>
2530                </Enumeration>
2531                <Integer Name="RegModeVal">
2532                    <Address>0x0000_4100</Address>
2533                    <Length>4</Length>
2534                    <AccessMode>RW</AccessMode>
2535                    <Min>0</Min>
2536                    <Max>65535</Max>
2537                </Integer>
2538            </RegisterDescription>
2539        "#;
2540
2541        let model = parse(XML).expect("parse enum pvalue");
2542        assert_eq!(model.nodes.len(), 2);
2543        match &model.nodes[0] {
2544            NodeDecl::Enum { entries, .. } => {
2545                assert_eq!(entries.len(), 2);
2546                assert!(matches!(entries[0].value, EnumValueSrc::Literal(10)));
2547                match &entries[1].value {
2548                    EnumValueSrc::FromNode(node) => assert_eq!(node, "RegModeVal"),
2549                    other => panic!("unexpected entry value: {other:?}"),
2550                }
2551            }
2552            other => panic!("unexpected node: {other:?}"),
2553        }
2554    }
2555
2556    #[test]
2557    fn parse_indirect_addressing() {
2558        const XML: &str = r#"
2559            <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
2560                <Integer Name="RegAddr">
2561                    <Address>0x2000</Address>
2562                    <Length>4</Length>
2563                    <AccessMode>RW</AccessMode>
2564                    <Min>0</Min>
2565                    <Max>65535</Max>
2566                </Integer>
2567                <Integer Name="Gain" Address="0xFFFF">
2568                    <pAddress>RegAddr</pAddress>
2569                    <Length>4</Length>
2570                    <AccessMode>RW</AccessMode>
2571                    <Min>0</Min>
2572                    <Max>255</Max>
2573                </Integer>
2574            </RegisterDescription>
2575        "#;
2576
2577        let model = parse(XML).expect("parse indirect xml");
2578        assert_eq!(model.nodes.len(), 2);
2579        match &model.nodes[0] {
2580            NodeDecl::Integer {
2581                name, addressing, ..
2582            } => {
2583                assert_eq!(name, "RegAddr");
2584                assert!(
2585                    matches!(addressing, Addressing::Fixed { address, len } if *address == 0x2000 && *len == 4)
2586                );
2587            }
2588            other => panic!("unexpected node: {other:?}"),
2589        }
2590        match &model.nodes[1] {
2591            NodeDecl::Integer {
2592                name, addressing, ..
2593            } => {
2594                assert_eq!(name, "Gain");
2595                match addressing {
2596                    Addressing::Indirect {
2597                        p_address_node,
2598                        len,
2599                    } => {
2600                        assert_eq!(p_address_node, "RegAddr");
2601                        assert_eq!(*len, 4);
2602                    }
2603                    other => panic!("expected indirect addressing, got {other:?}"),
2604                }
2605            }
2606            other => panic!("unexpected node: {other:?}"),
2607        }
2608    }
2609
2610    #[test]
2611    fn parse_integer_bitfield_big_endian() {
2612        const XML: &str = r#"
2613            <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
2614                <Integer Name="Packed">
2615                    <Address>0x1000</Address>
2616                    <Length>4</Length>
2617                    <AccessMode>RW</AccessMode>
2618                    <Min>0</Min>
2619                    <Max>65535</Max>
2620                    <Lsb>8</Lsb>
2621                    <Msb>15</Msb>
2622                    <Endianness>BigEndian</Endianness>
2623                </Integer>
2624            </RegisterDescription>
2625        "#;
2626
2627        let model = parse(XML).expect("parse big-endian bitfield");
2628        assert_eq!(model.nodes.len(), 1);
2629        match &model.nodes[0] {
2630            NodeDecl::Integer { len, bitfield, .. } => {
2631                assert_eq!(*len, 4);
2632                let field = bitfield.expect("bitfield present");
2633                assert_eq!(field.byte_order, ByteOrder::Big);
2634                assert_eq!(field.bit_length, 8);
2635                assert_eq!(field.bit_offset, 16);
2636            }
2637            other => panic!("unexpected node: {other:?}"),
2638        }
2639    }
2640
2641    #[test]
2642    fn parse_boolean_bitfield_default_length() {
2643        const XML: &str = r#"
2644            <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
2645                <Boolean Name="Flag">
2646                    <Address>0x2000</Address>
2647                    <Length>1</Length>
2648                    <AccessMode>RW</AccessMode>
2649                    <Bit>3</Bit>
2650                </Boolean>
2651            </RegisterDescription>
2652        "#;
2653
2654        let model = parse(XML).expect("parse boolean bitfield");
2655        assert_eq!(model.nodes.len(), 1);
2656        match &model.nodes[0] {
2657            NodeDecl::Boolean { len, bitfield, .. } => {
2658                assert_eq!(*len, 1);
2659                assert_eq!(bitfield.byte_order, ByteOrder::Little);
2660                assert_eq!(bitfield.bit_length, 1);
2661                assert_eq!(bitfield.bit_offset, 3);
2662            }
2663            other => panic!("unexpected node: {other:?}"),
2664        }
2665    }
2666
2667    #[test]
2668    fn parse_integer_bitfield_mask() {
2669        const XML: &str = r#"
2670            <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
2671                <Integer Name="Masked">
2672                    <Address>0x3000</Address>
2673                    <Length>4</Length>
2674                    <AccessMode>RW</AccessMode>
2675                    <Min>0</Min>
2676                    <Max>65535</Max>
2677                    <Mask>0x0000FF00</Mask>
2678                </Integer>
2679            </RegisterDescription>
2680        "#;
2681
2682        let model = parse(XML).expect("parse mask bitfield");
2683        assert_eq!(model.nodes.len(), 1);
2684        match &model.nodes[0] {
2685            NodeDecl::Integer { bitfield, .. } => {
2686                let field = bitfield.expect("bitfield present");
2687                assert_eq!(field.byte_order, ByteOrder::Little);
2688                assert_eq!(field.bit_length, 8);
2689                assert_eq!(field.bit_offset, 8);
2690            }
2691            other => panic!("unexpected node: {other:?}"),
2692        }
2693    }
2694}