1#![cfg_attr(docsrs, feature(doc_cfg))]
2use std::cell::{Cell, RefCell};
5use std::collections::{hash_map::Entry as HashMapEntry, HashMap, HashSet};
6
7pub use genapi_xml::SkOutput;
8use genapi_xml::{
9 AccessMode, Addressing, BitField, EnumEntryDecl, EnumValueSrc, NodeDecl, XmlModel,
10};
11use thiserror::Error;
12use tracing::{debug, trace, warn};
13
14mod bitops;
15use crate::bitops::{extract, insert, BitOpsError};
16mod swissknife;
17use crate::swissknife::{
18 collect_identifiers, evaluate as eval_ast, parse_expression, AstNode, EvalError as SkEvalError,
19};
20
21#[derive(Debug, Error)]
23pub enum GenApiError {
24 #[error("node not found: {0}")]
26 NodeNotFound(String),
27 #[error("type mismatch for node: {0}")]
29 Type(String),
30 #[error("access denied for node: {0}")]
32 Access(String),
33 #[error("range error for node: {0}")]
35 Range(String),
36 #[error("node unavailable: {0}")]
38 Unavailable(String),
39 #[error("io error: {0}")]
41 Io(String),
42 #[error("parse error: {0}")]
44 Parse(String),
45 #[error("failed to parse expression for {name}: {msg}")]
47 ExprParse { name: String, msg: String },
48 #[error("failed to evaluate expression for {name}: {msg}")]
50 ExprEval { name: String, msg: String },
51 #[error("unknown variable '{var}' referenced by {name}")]
53 UnknownVariable { name: String, var: String },
54 #[error("enum {node} has no entry for raw value {value}")]
56 EnumValueUnknown { node: String, value: i64 },
57 #[error("enum {node} has no entry named {entry}")]
59 EnumNoSuchEntry { node: String, entry: String },
60 #[error("node {name} resolved invalid indirect address {addr:#X}")]
62 BadIndirectAddress { name: String, addr: i64 },
63 #[error(
65 "bitfield for node {name} exceeds register length {len} (offset {bit_offset}, length {bit_length})"
66 )]
67 BitfieldOutOfRange {
68 name: String,
69 bit_offset: u16,
70 bit_length: u16,
71 len: usize,
72 },
73 #[error("value {value} too wide for {bit_length}-bit field on node {name}")]
75 ValueTooWide {
76 name: String,
77 value: i64,
78 bit_length: u16,
79 },
80}
81
82pub trait RegisterIo {
84 fn read(&self, addr: u64, len: usize) -> Result<Vec<u8>, GenApiError>;
86 fn write(&self, addr: u64, data: &[u8]) -> Result<(), GenApiError>;
88}
89
90#[derive(Debug)]
92pub enum Node {
93 Integer(IntegerNode),
95 Float(FloatNode),
97 Enum(EnumNode),
99 Boolean(BooleanNode),
101 Command(CommandNode),
103 Category(CategoryNode),
105 SwissKnife(SkNode),
107}
108
109impl Node {
110 fn invalidate_cache(&self) {
111 match self {
112 Node::Integer(node) => {
113 node.cache.replace(None);
114 node.raw_cache.replace(None);
115 }
116 Node::Float(node) => {
117 node.cache.replace(None);
118 }
119 Node::Enum(node) => node.invalidate(),
120 Node::Boolean(node) => {
121 node.cache.replace(None);
122 node.raw_cache.replace(None);
123 }
124 Node::SwissKnife(node) => {
125 node.cache.replace(None);
126 }
127 Node::Command(_) | Node::Category(_) => {}
128 }
129 }
130}
131
132fn register_addressing_dependency(
133 dependents: &mut HashMap<String, Vec<String>>,
134 node_name: &str,
135 addressing: &Addressing,
136) {
137 match addressing {
138 Addressing::Fixed { .. } => {}
139 Addressing::BySelector { selector, .. } => {
140 dependents
141 .entry(selector.clone())
142 .or_default()
143 .push(node_name.to_string());
144 }
145 Addressing::Indirect { p_address_node, .. } => {
146 dependents
147 .entry(p_address_node.clone())
148 .or_default()
149 .push(node_name.to_string());
150 }
151 }
152}
153
154#[derive(Debug)]
156pub struct IntegerNode {
157 pub name: String,
159 pub addressing: Addressing,
161 pub len: u32,
163 pub access: AccessMode,
165 pub min: i64,
167 pub max: i64,
169 pub inc: Option<i64>,
171 pub unit: Option<String>,
173 pub bitfield: Option<BitField>,
175 pub selectors: Vec<String>,
177 pub selected_if: Vec<(String, Vec<String>)>,
179 cache: RefCell<Option<i64>>,
180 raw_cache: RefCell<Option<Vec<u8>>>,
181}
182
183#[derive(Debug)]
185pub struct FloatNode {
186 pub name: String,
187 pub addressing: Addressing,
188 pub access: AccessMode,
189 pub min: f64,
190 pub max: f64,
191 pub unit: Option<String>,
192 pub scale: Option<(i64, i64)>,
194 pub offset: Option<f64>,
196 pub selectors: Vec<String>,
197 pub selected_if: Vec<(String, Vec<String>)>,
198 cache: RefCell<Option<f64>>,
199}
200
201#[derive(Debug)]
203pub struct EnumNode {
204 pub name: String,
205 pub addressing: Addressing,
206 pub access: AccessMode,
207 pub entries: Vec<EnumEntryDecl>,
208 pub default: Option<String>,
209 pub selectors: Vec<String>,
210 pub selected_if: Vec<(String, Vec<String>)>,
211 pub providers: Vec<String>,
212 value_cache: RefCell<Option<String>>,
213 mapping_cache: RefCell<Option<EnumMapping>>,
214}
215
216#[derive(Debug, Clone)]
217struct EnumMapping {
218 by_value: HashMap<i64, String>,
219 by_name: HashMap<String, i64>,
220}
221
222#[derive(Debug)]
224pub struct BooleanNode {
225 pub name: String,
226 pub addressing: Addressing,
227 pub len: u32,
228 pub access: AccessMode,
229 pub bitfield: BitField,
230 pub selectors: Vec<String>,
231 pub selected_if: Vec<(String, Vec<String>)>,
232 cache: RefCell<Option<bool>>,
233 raw_cache: RefCell<Option<Vec<u8>>>,
234}
235
236#[derive(Debug)]
241pub struct SkNode {
242 pub name: String,
244 pub output: SkOutput,
246 pub ast: AstNode,
248 pub vars: Vec<(String, String)>,
250 pub cache: RefCell<Option<(f64, u64)>>,
252}
253
254impl EnumNode {
255 fn invalidate(&self) {
256 self.value_cache.replace(None);
257 self.mapping_cache.replace(None);
258 }
259}
260
261#[derive(Debug)]
263pub struct CommandNode {
264 pub name: String,
265 pub address: u64,
266 pub len: u32,
267}
268
269#[derive(Debug)]
271pub struct CategoryNode {
272 pub name: String,
273 pub children: Vec<String>,
274}
275
276#[derive(Debug)]
279pub struct NodeMap {
280 version: String,
281 nodes: HashMap<String, Node>,
282 dependents: HashMap<String, Vec<String>>,
283 generation: Cell<u64>,
284}
285
286impl NodeMap {
287 pub fn version(&self) -> &str {
289 &self.version
290 }
291
292 pub fn node(&self, name: &str) -> Option<&Node> {
294 self.nodes.get(name)
295 }
296
297 pub fn try_from_xml(model: XmlModel) -> Result<Self, GenApiError> {
299 let mut nodes = HashMap::new();
300 let mut dependents: HashMap<String, Vec<String>> = HashMap::new();
301 for decl in model.nodes {
302 match decl {
303 NodeDecl::Integer {
304 name,
305 addressing,
306 len,
307 access,
308 min,
309 max,
310 inc,
311 unit,
312 bitfield,
313 selectors,
314 selected_if,
315 } => {
316 register_addressing_dependency(&mut dependents, &name, &addressing);
317 for (selector, _) in &selected_if {
318 dependents
319 .entry(selector.clone())
320 .or_default()
321 .push(name.clone());
322 }
323 let node = IntegerNode {
324 name: name.clone(),
325 addressing,
326 len,
327 access,
328 min,
329 max,
330 inc,
331 unit,
332 bitfield,
333 selectors,
334 selected_if,
335 cache: RefCell::new(None),
336 raw_cache: RefCell::new(None),
337 };
338 nodes.insert(name, Node::Integer(node));
339 }
340 NodeDecl::Float {
341 name,
342 addressing,
343 access,
344 min,
345 max,
346 unit,
347 scale,
348 offset,
349 selectors,
350 selected_if,
351 } => {
352 register_addressing_dependency(&mut dependents, &name, &addressing);
353 for (selector, _) in &selected_if {
354 dependents
355 .entry(selector.clone())
356 .or_default()
357 .push(name.clone());
358 }
359 let node = FloatNode {
360 name: name.clone(),
361 addressing,
362 access,
363 min,
364 max,
365 unit,
366 scale,
367 offset,
368 selectors,
369 selected_if,
370 cache: RefCell::new(None),
371 };
372 nodes.insert(name, Node::Float(node));
373 }
374 NodeDecl::Enum {
375 name,
376 addressing,
377 access,
378 entries,
379 default,
380 selectors,
381 selected_if,
382 } => {
383 register_addressing_dependency(&mut dependents, &name, &addressing);
384 for (selector, _) in &selected_if {
385 dependents
386 .entry(selector.clone())
387 .or_default()
388 .push(name.clone());
389 }
390 let mut providers = Vec::new();
391 let mut provider_set = HashSet::new();
392 for entry in &entries {
393 if let EnumValueSrc::FromNode(node_name) = &entry.value {
394 dependents
395 .entry(node_name.clone())
396 .or_default()
397 .push(name.clone());
398 if provider_set.insert(node_name.clone()) {
399 providers.push(node_name.clone());
400 }
401 }
402 }
403 providers.sort();
404 let node = EnumNode {
405 name: name.clone(),
406 addressing,
407 access,
408 entries,
409 default,
410 selectors,
411 selected_if,
412 providers,
413 value_cache: RefCell::new(None),
414 mapping_cache: RefCell::new(None),
415 };
416 nodes.insert(name, Node::Enum(node));
417 }
418 NodeDecl::Boolean {
419 name,
420 addressing,
421 len,
422 access,
423 bitfield,
424 selectors,
425 selected_if,
426 } => {
427 register_addressing_dependency(&mut dependents, &name, &addressing);
428 for (selector, _) in &selected_if {
429 dependents
430 .entry(selector.clone())
431 .or_default()
432 .push(name.clone());
433 }
434 let node = BooleanNode {
435 name: name.clone(),
436 addressing,
437 len,
438 access,
439 bitfield,
440 selectors,
441 selected_if,
442 cache: RefCell::new(None),
443 raw_cache: RefCell::new(None),
444 };
445 nodes.insert(name, Node::Boolean(node));
446 }
447 NodeDecl::Command { name, address, len } => {
448 let node = CommandNode {
449 name: name.clone(),
450 address,
451 len,
452 };
453 nodes.insert(name, Node::Command(node));
454 }
455 NodeDecl::Category { name, children } => {
456 let node = CategoryNode {
457 name: name.clone(),
458 children,
459 };
460 nodes.insert(name, Node::Category(node));
461 }
462 NodeDecl::SwissKnife(decl) => {
463 let name = decl.name;
464 let expr = decl.expr;
465 let variables = decl.variables;
466 let output = decl.output;
467 let ast = parse_expression(&expr).map_err(|err| GenApiError::ExprParse {
468 name: name.clone(),
469 msg: err.to_string(),
470 })?;
471 let mut used = HashSet::new();
472 collect_identifiers(&ast, &mut used);
473 for ident in &used {
474 if !variables.iter().any(|(var, _)| var == ident) {
475 return Err(GenApiError::UnknownVariable {
476 name: name.clone(),
477 var: ident.clone(),
478 });
479 }
480 }
481 for (_, provider) in &variables {
482 dependents
483 .entry(provider.clone())
484 .or_default()
485 .push(name.clone());
486 }
487 let node = SkNode {
488 name: name.clone(),
489 output,
490 ast,
491 vars: variables,
492 cache: RefCell::new(None),
493 };
494 nodes.insert(name, Node::SwissKnife(node));
495 }
496 }
497 }
498
499 Ok(NodeMap {
500 version: model.version,
501 nodes,
502 dependents,
503 generation: Cell::new(0),
504 })
505 }
506
507 pub fn get_integer(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
509 if let Some(output) = self.nodes.get(name).and_then(|node| match node {
510 Node::SwissKnife(sk) => Some(sk.output),
511 _ => None,
512 }) {
513 return match output {
514 SkOutput::Integer => {
515 let node = match self.nodes.get(name) {
516 Some(Node::SwissKnife(node)) => node,
517 _ => unreachable!("node vanished during lookup"),
518 };
519 let mut stack = HashSet::new();
520 let value = self.evaluate_swissknife(node, io, &mut stack)?;
521 round_to_i64(name, value)
522 }
523 SkOutput::Float => Err(GenApiError::Type(name.to_string())),
524 };
525 }
526 let node = self.get_integer_node(name)?;
527 ensure_readable(&node.access, name)?;
528 self.ensure_selectors(name, &node.selected_if, io)?;
529 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
530 if let Some(value) = *node.cache.borrow() {
531 return Ok(value);
532 }
533 let raw = io.read(address, len as usize).map_err(|err| match err {
534 GenApiError::Io(_) => err,
535 other => other,
536 })?;
537 let value = if let Some(bitfield) = node.bitfield {
538 let extracted = extract(&raw, bitfield).map_err(|err| map_bitops_error(name, err))?;
539 interpret_bitfield_value(name, extracted, bitfield.bit_length, node.min < 0)?
540 } else {
541 bytes_to_i64(name, &raw)?
542 };
543 debug!(node = %name, raw = value, "read integer feature");
544 node.cache.replace(Some(value));
545 node.raw_cache.replace(Some(raw));
546 Ok(value)
547 }
548
549 pub fn set_integer(
551 &mut self,
552 name: &str,
553 value: i64,
554 io: &dyn RegisterIo,
555 ) -> Result<(), GenApiError> {
556 let node = self.get_integer_node(name)?;
557 ensure_writable(&node.access, name)?;
558 self.ensure_selectors(name, &node.selected_if, io)?;
559 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
560 if value < node.min || value > node.max {
561 return Err(GenApiError::Range(name.to_string()));
562 }
563 if let Some(inc) = node.inc {
564 if inc != 0 && (value - node.min) % inc != 0 {
565 return Err(GenApiError::Range(name.to_string()));
566 }
567 }
568 if let Some(bitfield) = node.bitfield {
569 let encoded = encode_bitfield_value(name, value, bitfield.bit_length, node.min < 0)?;
570 let cached = node.raw_cache.borrow().clone();
571 let mut raw = if let Some(bytes) = cached {
572 if bytes.len() == len as usize {
573 bytes
574 } else {
575 io.read(address, len as usize).map_err(|err| match err {
576 GenApiError::Io(_) => err,
577 other => other,
578 })?
579 }
580 } else {
581 io.read(address, len as usize).map_err(|err| match err {
582 GenApiError::Io(_) => err,
583 other => other,
584 })?
585 };
586 insert(&mut raw, bitfield, encoded).map_err(|err| map_bitops_error(name, err))?;
587 debug!(node = %name, raw = value, "write integer feature");
588 io.write(address, &raw).map_err(|err| match err {
589 GenApiError::Io(_) => err,
590 other => other,
591 })?;
592 node.cache.replace(Some(value));
593 node.raw_cache.replace(Some(raw));
594 } else {
595 let bytes = i64_to_bytes(name, value, len)?;
596 debug!(node = %name, raw = value, "write integer feature");
597 io.write(address, &bytes).map_err(|err| match err {
598 GenApiError::Io(_) => err,
599 other => other,
600 })?;
601 node.cache.replace(Some(value));
602 node.raw_cache.replace(Some(bytes));
603 }
604 self.invalidate_dependents(name);
605 Ok(())
606 }
607
608 pub fn get_float(&self, name: &str, io: &dyn RegisterIo) -> Result<f64, GenApiError> {
610 if let Some(output) = self.nodes.get(name).and_then(|node| match node {
611 Node::SwissKnife(sk) => Some(sk.output),
612 _ => None,
613 }) {
614 return match output {
615 SkOutput::Float => {
616 let node = match self.nodes.get(name) {
617 Some(Node::SwissKnife(node)) => node,
618 _ => unreachable!("node vanished during lookup"),
619 };
620 let mut stack = HashSet::new();
621 let value = self.evaluate_swissknife(node, io, &mut stack)?;
622 Ok(value)
623 }
624 SkOutput::Integer => self.get_integer(name, io).map(|v| v as f64),
625 };
626 }
627 let node = self.get_float_node(name)?;
628 ensure_readable(&node.access, name)?;
629 self.ensure_selectors(name, &node.selected_if, io)?;
630 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
631 if let Some(value) = *node.cache.borrow() {
632 return Ok(value);
633 }
634 let raw = io.read(address, len as usize).map_err(|err| match err {
635 GenApiError::Io(_) => err,
636 other => other,
637 })?;
638 let raw_value = bytes_to_i64(name, &raw)?;
639 let value = apply_scale(node, raw_value as f64);
640 debug!(node = %name, raw = raw_value, value, "read float feature");
641 node.cache.replace(Some(value));
642 Ok(value)
643 }
644
645 pub fn set_float(
647 &mut self,
648 name: &str,
649 value: f64,
650 io: &dyn RegisterIo,
651 ) -> Result<(), GenApiError> {
652 let node = self.get_float_node(name)?;
653 ensure_writable(&node.access, name)?;
654 self.ensure_selectors(name, &node.selected_if, io)?;
655 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
656 if value < node.min || value > node.max {
657 return Err(GenApiError::Range(name.to_string()));
658 }
659 let raw = encode_float(node, value)?;
660 let bytes = i64_to_bytes(name, raw, len)?;
661 debug!(node = %name, raw, value, "write float feature");
662 io.write(address, &bytes).map_err(|err| match err {
663 GenApiError::Io(_) => err,
664 other => other,
665 })?;
666 node.cache.replace(Some(value));
667 self.invalidate_dependents(name);
668 Ok(())
669 }
670
671 pub fn get_enum(&self, name: &str, io: &dyn RegisterIo) -> Result<String, GenApiError> {
673 let node = self.get_enum_node(name)?;
674 ensure_readable(&node.access, name)?;
675 self.ensure_selectors(name, &node.selected_if, io)?;
676 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
677 if let Some(value) = node.value_cache.borrow().clone() {
678 return Ok(value);
679 }
680 let raw = io.read(address, len as usize).map_err(|err| match err {
681 GenApiError::Io(_) => err,
682 other => other,
683 })?;
684 let raw_value = bytes_to_i64(name, &raw)?;
685 let entry = self.lookup_enum_entry(node, raw_value, io)?;
686 debug!(node = %name, raw = raw_value, entry = %entry, "read enum feature");
687 node.value_cache.replace(Some(entry.clone()));
688 Ok(entry)
689 }
690
691 pub fn set_enum(
693 &mut self,
694 name: &str,
695 entry: &str,
696 io: &dyn RegisterIo,
697 ) -> Result<(), GenApiError> {
698 let node = self.get_enum_node(name)?;
699 ensure_writable(&node.access, name)?;
700 self.ensure_selectors(name, &node.selected_if, io)?;
701 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
702 let entry_decl = node
703 .entries
704 .iter()
705 .find(|candidate| candidate.name == entry)
706 .ok_or_else(|| GenApiError::EnumNoSuchEntry {
707 node: name.to_string(),
708 entry: entry.to_string(),
709 })?;
710 let raw = self.resolve_enum_entry_value(node, entry_decl, io)?;
711 let bytes = i64_to_bytes(name, raw, len)?;
712 debug!(node = %name, raw, entry, "write enum feature");
713 io.write(address, &bytes).map_err(|err| match err {
714 GenApiError::Io(_) => err,
715 other => other,
716 })?;
717 node.value_cache.replace(None);
718 self.invalidate_dependents(name);
719 Ok(())
720 }
721
722 pub fn enum_entries(&self, name: &str) -> Result<Vec<String>, GenApiError> {
724 let node = self.get_enum_node(name)?;
725 if let Some(mapping) = node.mapping_cache.borrow().as_ref() {
726 let mut names: Vec<_> = mapping.by_name.keys().cloned().collect();
727 names.sort();
728 names.dedup();
729 return Ok(names);
730 }
731 let mut names: Vec<_> = node
732 .entries
733 .iter()
734 .map(|entry| entry.name.clone())
735 .collect();
736 names.sort();
737 names.dedup();
738 Ok(names)
739 }
740
741 pub fn get_bool(&self, name: &str, io: &dyn RegisterIo) -> Result<bool, GenApiError> {
743 let node = self.get_bool_node(name)?;
744 ensure_readable(&node.access, name)?;
745 self.ensure_selectors(name, &node.selected_if, io)?;
746 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
747 if let Some(value) = *node.cache.borrow() {
748 return Ok(value);
749 }
750 let raw = io.read(address, len as usize).map_err(|err| match err {
751 GenApiError::Io(_) => err,
752 other => other,
753 })?;
754 let raw_value = extract(&raw, node.bitfield).map_err(|err| map_bitops_error(name, err))?;
755 let value = raw_value != 0;
756 debug!(node = %name, raw = raw_value, value, "read boolean feature");
757 node.cache.replace(Some(value));
758 node.raw_cache.replace(Some(raw));
759 Ok(value)
760 }
761
762 pub fn set_bool(
764 &mut self,
765 name: &str,
766 value: bool,
767 io: &dyn RegisterIo,
768 ) -> Result<(), GenApiError> {
769 let node = self.get_bool_node(name)?;
770 ensure_writable(&node.access, name)?;
771 self.ensure_selectors(name, &node.selected_if, io)?;
772 let (address, len) = self.resolve_address(name, &node.addressing, io)?;
773 let encoded = if value { 1 } else { 0 };
774 let cached = node.raw_cache.borrow().clone();
775 let mut raw = if let Some(bytes) = cached {
776 if bytes.len() == len as usize {
777 bytes
778 } else {
779 io.read(address, len as usize).map_err(|err| match err {
780 GenApiError::Io(_) => err,
781 other => other,
782 })?
783 }
784 } else {
785 io.read(address, len as usize).map_err(|err| match err {
786 GenApiError::Io(_) => err,
787 other => other,
788 })?
789 };
790 insert(&mut raw, node.bitfield, encoded).map_err(|err| map_bitops_error(name, err))?;
791 debug!(node = %name, raw = encoded, value, "write boolean feature");
792 io.write(address, &raw).map_err(|err| match err {
793 GenApiError::Io(_) => err,
794 other => other,
795 })?;
796 node.cache.replace(Some(value));
797 node.raw_cache.replace(Some(raw));
798 self.invalidate_dependents(name);
799 Ok(())
800 }
801
802 pub fn exec_command(&mut self, name: &str, io: &dyn RegisterIo) -> Result<(), GenApiError> {
804 let node = self.get_command_node(name)?;
805 if node.len == 0 {
806 return Err(GenApiError::Parse(format!(
807 "command node {name} has zero length"
808 )));
809 }
810 let mut data = vec![0u8; node.len as usize];
811 if let Some(last) = data.last_mut() {
812 *last = 1;
813 }
814 debug!(node = %name, "execute command");
815 io.write(node.address, &data).map_err(|err| match err {
816 GenApiError::Io(_) => err,
817 other => other,
818 })?;
819 self.invalidate_dependents(name);
820 Ok(())
821 }
822
823 fn get_integer_node(&self, name: &str) -> Result<&IntegerNode, GenApiError> {
824 match self.nodes.get(name) {
825 Some(Node::Integer(node)) => Ok(node),
826 Some(_) => Err(GenApiError::Type(name.to_string())),
827 None => Err(GenApiError::NodeNotFound(name.to_string())),
828 }
829 }
830
831 fn get_float_node(&self, name: &str) -> Result<&FloatNode, GenApiError> {
832 match self.nodes.get(name) {
833 Some(Node::Float(node)) => Ok(node),
834 Some(_) => Err(GenApiError::Type(name.to_string())),
835 None => Err(GenApiError::NodeNotFound(name.to_string())),
836 }
837 }
838
839 fn get_enum_node(&self, name: &str) -> Result<&EnumNode, GenApiError> {
840 match self.nodes.get(name) {
841 Some(Node::Enum(node)) => Ok(node),
842 Some(_) => Err(GenApiError::Type(name.to_string())),
843 None => Err(GenApiError::NodeNotFound(name.to_string())),
844 }
845 }
846
847 fn get_bool_node(&self, name: &str) -> Result<&BooleanNode, GenApiError> {
848 match self.nodes.get(name) {
849 Some(Node::Boolean(node)) => Ok(node),
850 Some(_) => Err(GenApiError::Type(name.to_string())),
851 None => Err(GenApiError::NodeNotFound(name.to_string())),
852 }
853 }
854
855 fn get_command_node(&self, name: &str) -> Result<&CommandNode, GenApiError> {
856 match self.nodes.get(name) {
857 Some(Node::Command(node)) => Ok(node),
858 Some(_) => Err(GenApiError::Type(name.to_string())),
859 None => Err(GenApiError::NodeNotFound(name.to_string())),
860 }
861 }
862
863 fn ensure_selectors(
864 &self,
865 node_name: &str,
866 rules: &[(String, Vec<String>)],
867 io: &dyn RegisterIo,
868 ) -> Result<(), GenApiError> {
869 for (selector, allowed) in rules {
870 if allowed.is_empty() {
871 continue;
872 }
873 let current = self.get_selector_value(selector, io)?;
874 if !allowed.iter().any(|value| value == ¤t) {
875 return Err(GenApiError::Unavailable(format!(
876 "node '{node_name}' unavailable for selector '{selector}={current}'"
877 )));
878 }
879 }
880 Ok(())
881 }
882
883 fn lookup_enum_entry(
884 &self,
885 node: &EnumNode,
886 raw_value: i64,
887 io: &dyn RegisterIo,
888 ) -> Result<String, GenApiError> {
889 {
890 let mut cache = node.mapping_cache.borrow_mut();
891 if cache.is_none() {
892 *cache = Some(self.build_enum_mapping(node, io)?);
893 }
894 if let Some(mapping) = cache.as_ref() {
895 if let Some(entry) = mapping.by_value.get(&raw_value) {
896 return Ok(entry.clone());
897 }
898 }
899 *cache = Some(self.build_enum_mapping(node, io)?);
900 if let Some(mapping) = cache.as_ref() {
901 if let Some(entry) = mapping.by_value.get(&raw_value) {
902 return Ok(entry.clone());
903 }
904 }
905 }
906 Err(GenApiError::EnumValueUnknown {
907 node: node.name.clone(),
908 value: raw_value,
909 })
910 }
911
912 fn build_enum_mapping(
913 &self,
914 node: &EnumNode,
915 io: &dyn RegisterIo,
916 ) -> Result<EnumMapping, GenApiError> {
917 let mut by_value = HashMap::new();
918 let mut by_name = HashMap::new();
919
920 for entry in &node.entries {
921 let value = self.resolve_enum_entry_value(node, entry, io)?;
922 match by_value.entry(value) {
923 HashMapEntry::Vacant(slot) => {
924 slot.insert(entry.name.clone());
925 }
926 HashMapEntry::Occupied(existing) => {
927 warn!(
928 enum_node = %node.name,
929 value,
930 kept = %existing.get(),
931 dropped = %entry.name,
932 "duplicate enum value"
933 );
934 }
935 }
936 by_name.insert(entry.name.clone(), value);
937 }
938
939 let mut summary: Vec<_> = by_value
940 .iter()
941 .map(|(value, name)| (*value, name.clone()))
942 .collect();
943 summary.sort_by_key(|(value, _)| *value);
944 debug!(node = %node.name, entries = ?summary, "build enum mapping");
945
946 Ok(EnumMapping { by_value, by_name })
947 }
948
949 fn resolve_enum_entry_value(
950 &self,
951 node: &EnumNode,
952 entry: &EnumEntryDecl,
953 io: &dyn RegisterIo,
954 ) -> Result<i64, GenApiError> {
955 match &entry.value {
956 EnumValueSrc::Literal(value) => Ok(*value),
957 EnumValueSrc::FromNode(provider) => {
958 let value = self.get_integer(provider, io)?;
959 trace!(
960 enum_node = %node.name,
961 entry = %entry.name,
962 provider = %provider,
963 value,
964 "resolved enum entry from provider"
965 );
966 Ok(value)
967 }
968 }
969 }
970
971 fn resolve_address(
972 &self,
973 node_name: &str,
974 addressing: &Addressing,
975 io: &dyn RegisterIo,
976 ) -> Result<(u64, u32), GenApiError> {
977 match addressing {
978 Addressing::Fixed { address, len } => Ok((*address, *len)),
979 Addressing::BySelector { selector, map } => {
980 let value = self.get_selector_value(selector, io)?;
981 if let Some((_, (address, len))) = map.iter().find(|(name, _)| name == &value) {
982 let addr = *address;
983 let len = *len;
984 debug!(
985 node = %node_name,
986 selector = %selector,
987 value = %value,
988 address = format_args!("0x{addr:X}"),
989 len,
990 "resolve address via selector"
991 );
992 Ok((addr, len))
993 } else {
994 Err(GenApiError::Unavailable(format!(
995 "node '{node_name}' unavailable for selector '{selector}={value}'"
996 )))
997 }
998 }
999 Addressing::Indirect {
1000 p_address_node,
1001 len,
1002 } => {
1003 let addr_value = self.get_integer(p_address_node, io)?;
1004 if addr_value <= 0 {
1005 return Err(GenApiError::BadIndirectAddress {
1006 name: node_name.to_string(),
1007 addr: addr_value,
1008 });
1009 }
1010 let addr =
1011 u64::try_from(addr_value).map_err(|_| GenApiError::BadIndirectAddress {
1012 name: node_name.to_string(),
1013 addr: addr_value,
1014 })?;
1015 if addr == 0 {
1016 return Err(GenApiError::BadIndirectAddress {
1017 name: node_name.to_string(),
1018 addr: addr_value,
1019 });
1020 }
1021 debug!(
1022 node = %node_name,
1023 source = %p_address_node,
1024 address = format_args!("0x{addr:X}"),
1025 len = *len,
1026 "resolve address via pAddress"
1027 );
1028 Ok((addr, *len))
1029 }
1030 }
1031 }
1032
1033 fn get_selector_value(
1034 &self,
1035 selector: &str,
1036 io: &dyn RegisterIo,
1037 ) -> Result<String, GenApiError> {
1038 match self.nodes.get(selector) {
1039 Some(Node::Enum(_)) => self.get_enum(selector, io),
1040 Some(Node::Boolean(_)) => Ok(self.get_bool(selector, io)?.to_string()),
1041 Some(Node::Integer(_)) => Ok(self.get_integer(selector, io)?.to_string()),
1042 Some(_) => Err(GenApiError::Parse(format!(
1043 "selector {selector} has unsupported type"
1044 ))),
1045 None => Err(GenApiError::NodeNotFound(selector.to_string())),
1046 }
1047 }
1048
1049 fn evaluate_swissknife(
1050 &self,
1051 node: &SkNode,
1052 io: &dyn RegisterIo,
1053 stack: &mut HashSet<String>,
1054 ) -> Result<f64, GenApiError> {
1055 if let Some((value, gen)) = *node.cache.borrow() {
1056 if gen == self.generation.get() {
1057 return Ok(value);
1058 }
1059 }
1060 if !stack.insert(node.name.clone()) {
1061 stack.remove(&node.name);
1062 return Err(GenApiError::ExprEval {
1063 name: node.name.clone(),
1064 msg: "cyclic dependency".into(),
1065 });
1066 }
1067 let current_gen = self.generation.get();
1068 let result = (|| {
1069 let mut values: HashMap<String, f64> = HashMap::new();
1070 let mut inputs = Vec::new();
1071 for (var, provider) in &node.vars {
1072 let value = self.resolve_numeric(provider, io, stack)?;
1073 values.insert(var.clone(), value);
1074 inputs.push((var.clone(), value));
1075 }
1076 let mut resolver = |ident: &str| -> Result<f64, SkEvalError> {
1077 values
1078 .get(ident)
1079 .copied()
1080 .ok_or_else(|| SkEvalError::UnknownVariable(ident.to_string()))
1081 };
1082 let value = match eval_ast(&node.ast, &mut resolver) {
1083 Ok(value) => value,
1084 Err(SkEvalError::UnknownVariable(var)) => {
1085 return Err(GenApiError::UnknownVariable {
1086 name: node.name.clone(),
1087 var,
1088 });
1089 }
1090 Err(SkEvalError::DivisionByZero) => {
1091 return Err(GenApiError::ExprEval {
1092 name: node.name.clone(),
1093 msg: "division by zero".into(),
1094 });
1095 }
1096 };
1097 debug!(node = %node.name, inputs = ?inputs, output = value, "evaluate SwissKnife");
1098 Ok(value)
1099 })();
1100 stack.remove(&node.name);
1101 match result {
1102 Ok(value) => {
1103 node.cache.replace(Some((value, current_gen)));
1104 Ok(value)
1105 }
1106 Err(err) => Err(err),
1107 }
1108 }
1109
1110 fn resolve_numeric(
1111 &self,
1112 provider: &str,
1113 io: &dyn RegisterIo,
1114 stack: &mut HashSet<String>,
1115 ) -> Result<f64, GenApiError> {
1116 match self.nodes.get(provider) {
1117 Some(Node::Integer(_)) => self.get_integer(provider, io).map(|v| v as f64),
1118 Some(Node::Float(_)) => self.get_float(provider, io),
1119 Some(Node::Boolean(_)) => Ok(if self.get_bool(provider, io)? {
1120 1.0
1121 } else {
1122 0.0
1123 }),
1124 Some(Node::Enum(_)) => self.get_enum_numeric(provider, io).map(|v| v as f64),
1125 Some(Node::SwissKnife(node)) => self.evaluate_swissknife(node, io, stack),
1126 Some(_) => Err(GenApiError::Type(provider.to_string())),
1127 None => Err(GenApiError::NodeNotFound(provider.to_string())),
1128 }
1129 }
1130
1131 fn get_enum_numeric(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
1132 let entry = self.get_enum(name, io)?;
1133 let node = self.get_enum_node(name)?;
1134 {
1135 let mut mapping = node.mapping_cache.borrow_mut();
1136 if mapping.is_none() {
1137 *mapping = Some(self.build_enum_mapping(node, io)?);
1138 }
1139 if let Some(map) = mapping.as_ref() {
1140 if let Some(value) = map.by_name.get(&entry) {
1141 return Ok(*value);
1142 }
1143 }
1144 }
1145 Err(GenApiError::EnumNoSuchEntry {
1146 node: name.to_string(),
1147 entry,
1148 })
1149 }
1150
1151 fn invalidate_dependents(&self, name: &str) {
1152 self.bump_generation();
1153 if let Some(children) = self.dependents.get(name) {
1154 let mut visited = HashSet::new();
1155 for child in children {
1156 self.invalidate_recursive(child, &mut visited);
1157 }
1158 }
1159 }
1160
1161 fn invalidate_recursive(&self, name: &str, visited: &mut HashSet<String>) {
1162 if !visited.insert(name.to_string()) {
1163 return;
1164 }
1165 if let Some(node) = self.nodes.get(name) {
1166 node.invalidate_cache();
1167 }
1168 if let Some(children) = self.dependents.get(name) {
1169 for child in children {
1170 self.invalidate_recursive(child, visited);
1171 }
1172 }
1173 }
1174
1175 fn bump_generation(&self) {
1176 let current = self.generation.get();
1177 self.generation.set(current.wrapping_add(1));
1178 }
1179}
1180
1181impl From<XmlModel> for NodeMap {
1182 fn from(model: XmlModel) -> Self {
1183 NodeMap::try_from_xml(model).expect("invalid GenApi model")
1184 }
1185}
1186
1187fn round_to_i64(name: &str, value: f64) -> Result<i64, GenApiError> {
1188 if !value.is_finite() {
1189 return Err(GenApiError::ExprEval {
1190 name: name.to_string(),
1191 msg: "non-finite result".into(),
1192 });
1193 }
1194 let rounded = round_ties_to_zero(value);
1195 if rounded < i64::MIN as f64 || rounded > i64::MAX as f64 {
1196 return Err(GenApiError::ExprEval {
1197 name: name.to_string(),
1198 msg: "result out of range".into(),
1199 });
1200 }
1201 let truncated = rounded.trunc();
1202 if (rounded - truncated).abs() > 1e-9 {
1203 return Err(GenApiError::ExprEval {
1204 name: name.to_string(),
1205 msg: "unable to represent integer".into(),
1206 });
1207 }
1208 Ok(truncated as i64)
1209}
1210
1211fn round_ties_to_zero(value: f64) -> f64 {
1212 if value >= 0.0 {
1213 let base = value.floor();
1214 let frac = value - base;
1215 if frac > 0.5 {
1216 base + 1.0
1217 } else {
1218 base
1219 }
1220 } else {
1221 let base = value.ceil();
1222 let frac = value - base;
1223 if frac < -0.5 {
1224 base - 1.0
1225 } else {
1226 base
1227 }
1228 }
1229}
1230
1231fn ensure_readable(access: &AccessMode, name: &str) -> Result<(), GenApiError> {
1232 if matches!(access, AccessMode::WO) {
1233 return Err(GenApiError::Access(name.to_string()));
1234 }
1235 Ok(())
1236}
1237
1238fn ensure_writable(access: &AccessMode, name: &str) -> Result<(), GenApiError> {
1239 if matches!(access, AccessMode::RO) {
1240 return Err(GenApiError::Access(name.to_string()));
1241 }
1242 Ok(())
1243}
1244
1245fn bytes_to_i64(name: &str, bytes: &[u8]) -> Result<i64, GenApiError> {
1246 if bytes.is_empty() {
1247 return Err(GenApiError::Parse(format!(
1248 "node {name} returned empty payload"
1249 )));
1250 }
1251 if bytes.len() > 8 {
1252 return Err(GenApiError::Parse(format!(
1253 "node {name} uses unsupported width {}",
1254 bytes.len()
1255 )));
1256 }
1257 let mut buf = [0u8; 8];
1258 let offset = 8 - bytes.len();
1259 buf[offset..].copy_from_slice(bytes);
1260 if !bytes.is_empty() && (bytes[0] & 0x80) != 0 {
1261 for byte in &mut buf[..offset] {
1262 *byte = 0xFF;
1263 }
1264 }
1265 Ok(i64::from_be_bytes(buf))
1266}
1267
1268fn i64_to_bytes(name: &str, value: i64, width: u32) -> Result<Vec<u8>, GenApiError> {
1269 if width == 0 || width > 8 {
1270 return Err(GenApiError::Parse(format!(
1271 "node {name} has unsupported width {width}"
1272 )));
1273 }
1274 let width = width as usize;
1275 let bytes = value.to_be_bytes();
1276 let data = bytes[8 - width..].to_vec();
1277 let roundtrip = bytes_to_i64(name, &data)?;
1278 if roundtrip != value {
1279 return Err(GenApiError::Range(format!(
1280 "value {value} does not fit {width} bytes for {name}"
1281 )));
1282 }
1283 Ok(data)
1284}
1285
1286fn interpret_bitfield_value(
1287 name: &str,
1288 raw: u64,
1289 bit_length: u16,
1290 signed: bool,
1291) -> Result<i64, GenApiError> {
1292 if signed {
1293 Ok(sign_extend(raw, bit_length))
1294 } else {
1295 i64::try_from(raw).map_err(|_| {
1296 GenApiError::Parse(format!(
1297 "bitfield value {raw} exceeds i64 range for node {name}"
1298 ))
1299 })
1300 }
1301}
1302
1303fn encode_bitfield_value(
1304 name: &str,
1305 value: i64,
1306 bit_length: u16,
1307 signed: bool,
1308) -> Result<u64, GenApiError> {
1309 if bit_length == 0 || bit_length > 64 {
1310 return Err(GenApiError::Parse(format!(
1311 "node {name} uses unsupported bitfield width {bit_length}"
1312 )));
1313 }
1314 if signed {
1315 let width = bit_length as u32;
1316 let min_allowed = -(1i128 << (width - 1));
1317 let max_allowed = (1i128 << (width - 1)) - 1;
1318 let value_i128 = value as i128;
1319 if value_i128 < min_allowed || value_i128 > max_allowed {
1320 return Err(GenApiError::ValueTooWide {
1321 name: name.to_string(),
1322 value,
1323 bit_length,
1324 });
1325 }
1326 let mask = mask_u128(bit_length) as i128;
1327 Ok((value_i128 & mask) as u64)
1328 } else {
1329 if value < 0 {
1330 return Err(GenApiError::ValueTooWide {
1331 name: name.to_string(),
1332 value,
1333 bit_length,
1334 });
1335 }
1336 let mask = mask_u128(bit_length);
1337 if (value as u128) > mask {
1338 return Err(GenApiError::ValueTooWide {
1339 name: name.to_string(),
1340 value,
1341 bit_length,
1342 });
1343 }
1344 Ok(value as u64)
1345 }
1346}
1347
1348fn mask_u128(bit_length: u16) -> u128 {
1349 if bit_length == 64 {
1350 u64::MAX as u128
1351 } else {
1352 (1u128 << bit_length) - 1
1353 }
1354}
1355
1356fn sign_extend(value: u64, bits: u16) -> i64 {
1357 let shift = 64 - bits as u32;
1358 ((value << shift) as i64) >> shift
1359}
1360
1361fn map_bitops_error(name: &str, err: BitOpsError) -> GenApiError {
1362 match err {
1363 BitOpsError::UnsupportedWidth { len } => {
1364 GenApiError::Parse(format!("node {name} uses unsupported register width {len}"))
1365 }
1366 BitOpsError::UnsupportedLength { bit_length } => GenApiError::Parse(format!(
1367 "node {name} uses unsupported bitfield length {bit_length}"
1368 )),
1369 BitOpsError::OutOfRange {
1370 len,
1371 bit_offset,
1372 bit_length,
1373 } => GenApiError::BitfieldOutOfRange {
1374 name: name.to_string(),
1375 bit_offset,
1376 bit_length,
1377 len,
1378 },
1379 BitOpsError::ValueTooWide { bit_length, value } => GenApiError::ValueTooWide {
1380 name: name.to_string(),
1381 value: i64::try_from(value).unwrap_or(i64::MAX),
1382 bit_length,
1383 },
1384 }
1385}
1386
1387fn apply_scale(node: &FloatNode, raw: f64) -> f64 {
1388 let mut value = raw;
1389 if let Some((num, den)) = node.scale {
1390 value *= num as f64 / den as f64;
1391 }
1392 if let Some(offset) = node.offset {
1393 value += offset;
1394 }
1395 value
1396}
1397
1398fn encode_float(node: &FloatNode, value: f64) -> Result<i64, GenApiError> {
1399 let mut raw = value;
1400 if let Some(offset) = node.offset {
1401 raw -= offset;
1402 }
1403 if let Some((num, den)) = node.scale {
1404 if num == 0 {
1405 return Err(GenApiError::Parse(format!(
1406 "node {} has zero scale numerator",
1407 node.name
1408 )));
1409 }
1410 raw *= den as f64 / num as f64;
1411 }
1412 let rounded = raw.round();
1413 if (raw - rounded).abs() > 1e-6 {
1414 return Err(GenApiError::Range(node.name.clone()));
1415 }
1416 let raw_i64 = rounded as i64;
1417 Ok(raw_i64)
1418}
1419
1420#[cfg(test)]
1421mod tests {
1422 use super::*;
1423
1424 const FIXTURE: &str = r#"
1425 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="2" SchemaSubMinorVersion="3">
1426 <Integer Name="Width">
1427 <Address>0x100</Address>
1428 <Length>4</Length>
1429 <AccessMode>RW</AccessMode>
1430 <Min>16</Min>
1431 <Max>4096</Max>
1432 <Inc>2</Inc>
1433 </Integer>
1434 <Float Name="ExposureTime">
1435 <Address>0x200</Address>
1436 <Length>4</Length>
1437 <AccessMode>RW</AccessMode>
1438 <Min>10.0</Min>
1439 <Max>100000.0</Max>
1440 <Scale>1/1000</Scale>
1441 </Float>
1442 <Enumeration Name="GainSelector">
1443 <Address>0x300</Address>
1444 <Length>2</Length>
1445 <AccessMode>RW</AccessMode>
1446 <EnumEntry Name="All" Value="0" />
1447 <EnumEntry Name="Red" Value="1" />
1448 <EnumEntry Name="Blue" Value="2" />
1449 </Enumeration>
1450 <Integer Name="Gain">
1451 <Length>2</Length>
1452 <AccessMode>RW</AccessMode>
1453 <Min>0</Min>
1454 <Max>48</Max>
1455 <pSelected>GainSelector</pSelected>
1456 <Selected>All</Selected>
1457 <Address>0x310</Address>
1458 <Selected>Red</Selected>
1459 <Address>0x314</Address>
1460 <Selected>Blue</Selected>
1461 </Integer>
1462 <Boolean Name="GammaEnable">
1463 <Address>0x400</Address>
1464 <Length>1</Length>
1465 <AccessMode>RW</AccessMode>
1466 </Boolean>
1467 <Command Name="AcquisitionStart">
1468 <Address>0x500</Address>
1469 <Length>4</Length>
1470 </Command>
1471 </RegisterDescription>
1472 "#;
1473
1474 const INDIRECT_FIXTURE: &str = r#"
1475 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1476 <Integer Name="RegAddr">
1477 <Address>0x2000</Address>
1478 <Length>4</Length>
1479 <AccessMode>RW</AccessMode>
1480 <Min>0</Min>
1481 <Max>65535</Max>
1482 </Integer>
1483 <Integer Name="Gain">
1484 <pAddress>RegAddr</pAddress>
1485 <Length>4</Length>
1486 <AccessMode>RW</AccessMode>
1487 <Min>0</Min>
1488 <Max>255</Max>
1489 </Integer>
1490 </RegisterDescription>
1491 "#;
1492
1493 const ENUM_PVALUE_FIXTURE: &str = r#"
1494 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1495 <Enumeration Name="Mode">
1496 <Address>0x4000</Address>
1497 <Length>4</Length>
1498 <AccessMode>RW</AccessMode>
1499 <EnumEntry Name="Fixed10">
1500 <Value>10</Value>
1501 </EnumEntry>
1502 <EnumEntry Name="DynFromReg">
1503 <pValue>RegModeVal</pValue>
1504 </EnumEntry>
1505 </Enumeration>
1506 <Integer Name="RegModeVal">
1507 <Address>0x4100</Address>
1508 <Length>4</Length>
1509 <AccessMode>RW</AccessMode>
1510 <Min>0</Min>
1511 <Max>65535</Max>
1512 </Integer>
1513 </RegisterDescription>
1514 "#;
1515
1516 const BITFIELD_FIXTURE: &str = r#"
1517 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1518 <Integer Name="LeByte">
1519 <Address>0x5000</Address>
1520 <Length>4</Length>
1521 <AccessMode>RW</AccessMode>
1522 <Min>0</Min>
1523 <Max>65535</Max>
1524 <Mask>0x0000FF00</Mask>
1525 </Integer>
1526 <Integer Name="BeBits">
1527 <Address>0x5004</Address>
1528 <Length>2</Length>
1529 <AccessMode>RW</AccessMode>
1530 <Min>0</Min>
1531 <Max>15</Max>
1532 <Lsb>13</Lsb>
1533 <Msb>15</Msb>
1534 <Endianness>BigEndian</Endianness>
1535 </Integer>
1536 <Boolean Name="PackedFlag">
1537 <Address>0x5006</Address>
1538 <Length>4</Length>
1539 <AccessMode>RW</AccessMode>
1540 <Bit>13</Bit>
1541 </Boolean>
1542 </RegisterDescription>
1543 "#;
1544
1545 const SWISSKNIFE_FIXTURE: &str = r#"
1546 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1547 <Integer Name="GainRaw">
1548 <Address>0x3000</Address>
1549 <Length>4</Length>
1550 <AccessMode>RW</AccessMode>
1551 <Min>0</Min>
1552 <Max>1000</Max>
1553 </Integer>
1554 <Float Name="Offset">
1555 <Address>0x3008</Address>
1556 <Length>4</Length>
1557 <AccessMode>RW</AccessMode>
1558 <Min>-100.0</Min>
1559 <Max>100.0</Max>
1560 </Float>
1561 <Integer Name="B">
1562 <Address>0x3010</Address>
1563 <Length>4</Length>
1564 <AccessMode>RW</AccessMode>
1565 <Min>-1000</Min>
1566 <Max>1000</Max>
1567 </Integer>
1568 <SwissKnife Name="ComputedGain">
1569 <Expression>(GainRaw * 0.5) + Offset</Expression>
1570 <pVariable Name="GainRaw">GainRaw</pVariable>
1571 <pVariable Name="Offset">Offset</pVariable>
1572 <Output>Float</Output>
1573 </SwissKnife>
1574 <SwissKnife Name="DivideInt">
1575 <Expression>GainRaw / 3</Expression>
1576 <pVariable Name="GainRaw">GainRaw</pVariable>
1577 <Output>Integer</Output>
1578 </SwissKnife>
1579 <SwissKnife Name="Unary">
1580 <Expression>-GainRaw + 10</Expression>
1581 <pVariable Name="GainRaw">GainRaw</pVariable>
1582 <Output>Integer</Output>
1583 </SwissKnife>
1584 <SwissKnife Name="DivideByZero">
1585 <Expression>GainRaw / B</Expression>
1586 <pVariable Name="GainRaw">GainRaw</pVariable>
1587 <pVariable Name="B">B</pVariable>
1588 <Output>Float</Output>
1589 </SwissKnife>
1590 </RegisterDescription>
1591 "#;
1592
1593 #[derive(Default)]
1594 struct MockIo {
1595 regs: RefCell<HashMap<u64, Vec<u8>>>,
1596 reads: RefCell<HashMap<u64, usize>>,
1597 }
1598
1599 impl MockIo {
1600 fn with_registers(entries: &[(u64, Vec<u8>)]) -> Self {
1601 let mut regs = HashMap::new();
1602 for (addr, data) in entries {
1603 regs.insert(*addr, data.clone());
1604 }
1605 MockIo {
1606 regs: RefCell::new(regs),
1607 reads: RefCell::new(HashMap::new()),
1608 }
1609 }
1610
1611 fn read_count(&self, addr: u64) -> usize {
1612 *self.reads.borrow().get(&addr).unwrap_or(&0)
1613 }
1614 }
1615
1616 impl RegisterIo for MockIo {
1617 fn read(&self, addr: u64, len: usize) -> Result<Vec<u8>, GenApiError> {
1618 let mut reads = self.reads.borrow_mut();
1619 *reads.entry(addr).or_default() += 1;
1620 let regs = self.regs.borrow();
1621 let data = regs
1622 .get(&addr)
1623 .ok_or_else(|| GenApiError::Io(format!("read miss at 0x{addr:08X}")))?;
1624 if data.len() != len {
1625 return Err(GenApiError::Io(format!(
1626 "length mismatch at 0x{addr:08X}: expected {len}, have {}",
1627 data.len()
1628 )));
1629 }
1630 Ok(data.clone())
1631 }
1632
1633 fn write(&self, addr: u64, data: &[u8]) -> Result<(), GenApiError> {
1634 self.regs.borrow_mut().insert(addr, data.to_vec());
1635 Ok(())
1636 }
1637 }
1638
1639 fn build_nodemap() -> NodeMap {
1640 let model = genapi_xml::parse(FIXTURE).expect("parse fixture");
1641 NodeMap::from(model)
1642 }
1643
1644 fn build_indirect_nodemap() -> NodeMap {
1645 let model = genapi_xml::parse(INDIRECT_FIXTURE).expect("parse indirect fixture");
1646 NodeMap::from(model)
1647 }
1648
1649 fn build_enum_pvalue_nodemap() -> NodeMap {
1650 let model = genapi_xml::parse(ENUM_PVALUE_FIXTURE).expect("parse enum pvalue fixture");
1651 NodeMap::from(model)
1652 }
1653
1654 fn build_bitfield_nodemap() -> NodeMap {
1655 let model = genapi_xml::parse(BITFIELD_FIXTURE).expect("parse bitfield fixture");
1656 NodeMap::from(model)
1657 }
1658
1659 fn build_swissknife_nodemap() -> NodeMap {
1660 let model = genapi_xml::parse(SWISSKNIFE_FIXTURE).expect("parse swissknife fixture");
1661 NodeMap::from(model)
1662 }
1663
1664 #[test]
1665 fn integer_roundtrip_and_cache() {
1666 let mut nodemap = build_nodemap();
1667 let io = MockIo::with_registers(&[(0x100, vec![0, 0, 4, 0])]);
1668 let width = nodemap.get_integer("Width", &io).expect("read width");
1669 assert_eq!(width, 1024);
1670 assert_eq!(io.read_count(0x100), 1);
1671 let width_again = nodemap.get_integer("Width", &io).expect("cached width");
1672 assert_eq!(width_again, 1024);
1673 assert_eq!(io.read_count(0x100), 1, "cached value should be reused");
1674 nodemap
1675 .set_integer("Width", 1030, &io)
1676 .expect("write width");
1677 let width = nodemap
1678 .get_integer("Width", &io)
1679 .expect("read updated width");
1680 assert_eq!(width, 1030);
1681 assert_eq!(io.read_count(0x100), 1, "write should update cache");
1682 }
1683
1684 #[test]
1685 fn float_conversion_roundtrip() {
1686 let mut nodemap = build_nodemap();
1687 let raw = 50_000i64; let io = MockIo::with_registers(&[(0x200, i64_to_bytes("ExposureTime", raw, 4).unwrap())]);
1689 let exposure = nodemap
1690 .get_float("ExposureTime", &io)
1691 .expect("read exposure");
1692 assert!((exposure - 50.0).abs() < 1e-6);
1693 nodemap
1694 .set_float("ExposureTime", 75.0, &io)
1695 .expect("write exposure");
1696 let raw_back = bytes_to_i64("ExposureTime", &io.read(0x200, 4).unwrap()).unwrap();
1697 assert_eq!(raw_back, 75_000);
1698 }
1699
1700 #[test]
1701 fn selector_address_switching() {
1702 let mut nodemap = build_nodemap();
1703 let io = MockIo::with_registers(&[
1704 (0x300, i64_to_bytes("GainSelector", 0, 2).unwrap()),
1705 (0x310, i64_to_bytes("Gain", 10, 2).unwrap()),
1706 (0x314, i64_to_bytes("Gain", 24, 2).unwrap()),
1707 ]);
1708
1709 let gain_all = nodemap.get_integer("Gain", &io).expect("gain for All");
1710 assert_eq!(gain_all, 10);
1711 assert_eq!(io.read_count(0x310), 1);
1712 assert_eq!(io.read_count(0x314), 0);
1713
1714 io.write(0x314, &i64_to_bytes("Gain", 32, 2).unwrap())
1715 .expect("update red gain");
1716 nodemap
1717 .set_enum("GainSelector", "Red", &io)
1718 .expect("set selector to red");
1719 let gain_red = nodemap.get_integer("Gain", &io).expect("gain for Red");
1720 assert_eq!(gain_red, 32);
1721 assert_eq!(
1722 io.read_count(0x310),
1723 1,
1724 "previous address should not be reread"
1725 );
1726 assert_eq!(io.read_count(0x314), 1);
1727
1728 let gain_red_cached = nodemap.get_integer("Gain", &io).expect("cached red");
1729 assert_eq!(gain_red_cached, 32);
1730 assert_eq!(io.read_count(0x314), 1, "selector cache should be reused");
1731
1732 nodemap
1733 .set_enum("GainSelector", "Blue", &io)
1734 .expect("set selector to blue");
1735 let err = nodemap.get_integer("Gain", &io).unwrap_err();
1736 match err {
1737 GenApiError::Unavailable(msg) => {
1738 assert!(msg.contains("GainSelector=Blue"));
1739 }
1740 other => panic!("unexpected error: {other:?}"),
1741 }
1742 assert_eq!(
1743 io.read_count(0x314),
1744 1,
1745 "no read expected for missing mapping"
1746 );
1747
1748 io.write(0x310, &i64_to_bytes("Gain", 12, 2).unwrap())
1749 .expect("update all gain");
1750 nodemap
1751 .set_enum("GainSelector", "All", &io)
1752 .expect("restore selector to all");
1753 let gain_all_updated = nodemap
1754 .get_integer("Gain", &io)
1755 .expect("gain for All again");
1756 assert_eq!(gain_all_updated, 12);
1757 assert_eq!(
1758 io.read_count(0x310),
1759 2,
1760 "address switch should invalidate cache"
1761 );
1762 }
1763
1764 #[test]
1765 fn range_enforcement() {
1766 let mut nodemap = build_nodemap();
1767 let io = MockIo::with_registers(&[(0x100, vec![0, 0, 0, 16])]);
1768 let err = nodemap.set_integer("Width", 17, &io).unwrap_err();
1769 assert!(matches!(err, GenApiError::Range(_)));
1770 }
1771
1772 #[test]
1773 fn command_exec() {
1774 let mut nodemap = build_nodemap();
1775 let io = MockIo::with_registers(&[]);
1776 nodemap
1777 .exec_command("AcquisitionStart", &io)
1778 .expect("exec command");
1779 let payload = io.read(0x500, 4).expect("command write");
1780 assert_eq!(payload, vec![0, 0, 0, 1]);
1781 }
1782
1783 #[test]
1784 fn indirect_address_resolution() {
1785 let mut nodemap = build_indirect_nodemap();
1786 let io = MockIo::with_registers(&[
1787 (0x2000, i64_to_bytes("RegAddr", 0x3000, 4).unwrap()),
1788 (0x3000, i64_to_bytes("Gain", 123, 4).unwrap()),
1789 (0x3100, i64_to_bytes("Gain", 77, 4).unwrap()),
1790 ]);
1791
1792 let initial = nodemap.get_integer("Gain", &io).expect("read gain");
1793 assert_eq!(initial, 123);
1794 assert_eq!(io.read_count(0x2000), 1);
1795 assert_eq!(io.read_count(0x3000), 1);
1796
1797 nodemap
1798 .set_integer("RegAddr", 0x3100, &io)
1799 .expect("set indirect address");
1800 let updated = nodemap
1801 .get_integer("Gain", &io)
1802 .expect("read gain after change");
1803 assert_eq!(updated, 77);
1804 assert_eq!(io.read_count(0x2000), 1);
1805 assert_eq!(io.read_count(0x3000), 1);
1806 assert_eq!(io.read_count(0x3100), 1);
1807 }
1808
1809 #[test]
1810 fn indirect_bad_address() {
1811 let mut nodemap = build_indirect_nodemap();
1812 let io = MockIo::with_registers(&[(0x2000, vec![0, 0, 0, 0])]);
1813
1814 nodemap
1815 .set_integer("RegAddr", 0, &io)
1816 .expect("write zero address");
1817 let err = nodemap.get_integer("Gain", &io).unwrap_err();
1818 match err {
1819 GenApiError::BadIndirectAddress { name, addr } => {
1820 assert_eq!(name, "Gain");
1821 assert_eq!(addr, 0);
1822 }
1823 other => panic!("unexpected error: {other:?}"),
1824 }
1825 assert_eq!(io.read_count(0x2000), 0);
1826 }
1827
1828 #[test]
1829 fn enum_literal_entry_read() {
1830 let nodemap = build_enum_pvalue_nodemap();
1831 let io = MockIo::with_registers(&[
1832 (0x4000, i64_to_bytes("Mode", 10, 4).unwrap()),
1833 (0x4100, i64_to_bytes("RegModeVal", 42, 4).unwrap()),
1834 ]);
1835
1836 let value = nodemap.get_enum("Mode", &io).expect("read mode");
1837 assert_eq!(value, "Fixed10");
1838 assert_eq!(
1839 io.read_count(0x4100),
1840 1,
1841 "provider should be read once for mapping"
1842 );
1843 }
1844
1845 #[test]
1846 fn enum_provider_entry_read() {
1847 let nodemap = build_enum_pvalue_nodemap();
1848 let io = MockIo::with_registers(&[
1849 (0x4000, i64_to_bytes("Mode", 42, 4).unwrap()),
1850 (0x4100, i64_to_bytes("RegModeVal", 42, 4).unwrap()),
1851 ]);
1852
1853 let value = nodemap.get_enum("Mode", &io).expect("read dynamic mode");
1854 assert_eq!(value, "DynFromReg");
1855 assert_eq!(io.read_count(0x4100), 1);
1856 }
1857
1858 #[test]
1859 fn enum_set_uses_provider_value() {
1860 let mut nodemap = build_enum_pvalue_nodemap();
1861 let io = MockIo::with_registers(&[
1862 (0x4000, i64_to_bytes("Mode", 0, 4).unwrap()),
1863 (0x4100, i64_to_bytes("RegModeVal", 42, 4).unwrap()),
1864 ]);
1865
1866 nodemap
1867 .set_enum("Mode", "DynFromReg", &io)
1868 .expect("write enum");
1869 let raw = bytes_to_i64("Mode", &io.read(0x4000, 4).unwrap()).unwrap();
1870 assert_eq!(raw, 42);
1871 assert_eq!(io.read_count(0x4100), 1);
1872 }
1873
1874 #[test]
1875 fn enum_provider_update_invalidates_mapping() {
1876 let mut nodemap = build_enum_pvalue_nodemap();
1877 let io = MockIo::with_registers(&[
1878 (0x4000, i64_to_bytes("Mode", 42, 4).unwrap()),
1879 (0x4100, i64_to_bytes("RegModeVal", 42, 4).unwrap()),
1880 ]);
1881
1882 assert_eq!(nodemap.get_enum("Mode", &io).unwrap(), "DynFromReg");
1883 assert_eq!(io.read_count(0x4100), 1);
1884
1885 nodemap
1886 .set_integer("RegModeVal", 17, &io)
1887 .expect("update provider");
1888 io.write(0x4000, &i64_to_bytes("Mode", 0, 4).unwrap())
1889 .expect("reset mode register");
1890
1891 nodemap
1892 .set_enum("Mode", "DynFromReg", &io)
1893 .expect("write enum after provider change");
1894 let raw = bytes_to_i64("Mode", &io.read(0x4000, 4).unwrap()).unwrap();
1895 assert_eq!(raw, 17);
1896 }
1897
1898 #[test]
1899 fn enum_unknown_value_error() {
1900 let nodemap = build_enum_pvalue_nodemap();
1901 let io = MockIo::with_registers(&[
1902 (0x4000, i64_to_bytes("Mode", 99, 4).unwrap()),
1903 (0x4100, i64_to_bytes("RegModeVal", 42, 4).unwrap()),
1904 ]);
1905
1906 let err = nodemap.get_enum("Mode", &io).unwrap_err();
1907 match err {
1908 GenApiError::EnumValueUnknown { node, value } => {
1909 assert_eq!(node, "Mode");
1910 assert_eq!(value, 99);
1911 }
1912 other => panic!("unexpected error: {other:?}"),
1913 }
1914 }
1915
1916 #[test]
1917 fn enum_entries_are_sorted() {
1918 let nodemap = build_enum_pvalue_nodemap();
1919 let entries = nodemap.enum_entries("Mode").expect("entries");
1920 assert_eq!(
1921 entries,
1922 vec!["DynFromReg".to_string(), "Fixed10".to_string()]
1923 );
1924 }
1925
1926 #[test]
1927 fn bitfield_le_integer_roundtrip() {
1928 let mut nodemap = build_bitfield_nodemap();
1929 let io = MockIo::with_registers(&[(0x5000, vec![0xAA, 0xBB, 0xCC, 0xDD])]);
1930
1931 let value = nodemap
1932 .get_integer("LeByte", &io)
1933 .expect("read little-endian field");
1934 assert_eq!(value, 0xBB);
1935
1936 nodemap
1937 .set_integer("LeByte", 0x55, &io)
1938 .expect("write little-endian field");
1939 let data = io.read(0x5000, 4).expect("read back register");
1940 assert_eq!(data, vec![0xAA, 0x55, 0xCC, 0xDD]);
1941 }
1942
1943 #[test]
1944 fn bitfield_be_integer_roundtrip() {
1945 let mut nodemap = build_bitfield_nodemap();
1946 let io = MockIo::with_registers(&[(0x5004, vec![0b1010_0000, 0b0000_0000])]);
1947
1948 let value = nodemap
1949 .get_integer("BeBits", &io)
1950 .expect("read big-endian bits");
1951 assert_eq!(value, 0b101);
1952
1953 nodemap
1954 .set_integer("BeBits", 0b010, &io)
1955 .expect("write big-endian bits");
1956 let data = io.read(0x5004, 2).expect("read back register");
1957 assert_eq!(data, vec![0b0100_0000, 0b0000_0000]);
1958 }
1959
1960 #[test]
1961 fn bitfield_boolean_toggle() {
1962 let mut nodemap = build_bitfield_nodemap();
1963 let io = MockIo::with_registers(&[(0x5006, vec![0x00, 0x20, 0x00, 0x00])]);
1964
1965 assert!(nodemap.get_bool("PackedFlag", &io).expect("read flag"));
1966
1967 nodemap
1968 .set_bool("PackedFlag", false, &io)
1969 .expect("clear flag");
1970 let data = io.read(0x5006, 4).expect("read cleared");
1971 assert_eq!(data, vec![0x00, 0x00, 0x00, 0x00]);
1972
1973 nodemap.set_bool("PackedFlag", true, &io).expect("set flag");
1974 let data = io.read(0x5006, 4).expect("read set");
1975 assert_eq!(data, vec![0x00, 0x20, 0x00, 0x00]);
1976 }
1977
1978 #[test]
1979 fn bitfield_value_too_wide() {
1980 let mut nodemap = build_bitfield_nodemap();
1981 let io = MockIo::with_registers(&[(0x5004, vec![0x00, 0x00])]);
1982
1983 let err = nodemap
1984 .set_integer("BeBits", 8, &io)
1985 .expect_err("value too wide");
1986 match err {
1987 GenApiError::ValueTooWide {
1988 name, bit_length, ..
1989 } => {
1990 assert_eq!(name, "BeBits");
1991 assert_eq!(bit_length, 3);
1992 }
1993 other => panic!("unexpected error: {other:?}"),
1994 }
1995 }
1996 #[test]
1997 fn swissknife_evaluates_and_invalidates() {
1998 let mut nodemap = build_swissknife_nodemap();
1999 let io = MockIo::with_registers(&[
2000 (0x3000, i64_to_bytes("GainRaw", 100, 4).unwrap()),
2001 (0x3008, i64_to_bytes("Offset", 3, 4).unwrap()),
2002 (0x3010, i64_to_bytes("B", 1, 4).unwrap()),
2003 ]);
2004
2005 let value = nodemap
2006 .get_float("ComputedGain", &io)
2007 .expect("compute gain");
2008 assert!((value - 53.0).abs() < 1e-6);
2009
2010 nodemap
2011 .set_integer("GainRaw", 120, &io)
2012 .expect("update raw gain");
2013 let updated = nodemap
2014 .get_float("ComputedGain", &io)
2015 .expect("recompute gain");
2016 assert!((updated - 63.0).abs() < 1e-6);
2017 }
2018
2019 #[test]
2020 fn swissknife_integer_rounding_and_unary() {
2021 let mut nodemap = build_swissknife_nodemap();
2022 let io = MockIo::with_registers(&[
2023 (0x3000, i64_to_bytes("GainRaw", 5, 4).unwrap()),
2024 (0x3008, i64_to_bytes("Offset", 0, 4).unwrap()),
2025 (0x3010, i64_to_bytes("B", 1, 4).unwrap()),
2026 ]);
2027
2028 let divided = nodemap
2029 .get_integer("DivideInt", &io)
2030 .expect("integer division");
2031 assert_eq!(divided, 2);
2032
2033 nodemap
2034 .set_integer("GainRaw", 3, &io)
2035 .expect("update gain raw");
2036 let unary = nodemap.get_integer("Unary", &io).expect("unary expression");
2037 assert_eq!(unary, 7);
2038 }
2039
2040 #[test]
2041 fn swissknife_unknown_variable_error() {
2042 const XML: &str = r#"
2043 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
2044 <Integer Name="A">
2045 <Address>0x2000</Address>
2046 <Length>4</Length>
2047 <AccessMode>RW</AccessMode>
2048 <Min>0</Min>
2049 <Max>100</Max>
2050 </Integer>
2051 <SwissKnife Name="Bad">
2052 <Expression>A + Missing</Expression>
2053 <pVariable Name="A">A</pVariable>
2054 </SwissKnife>
2055 </RegisterDescription>
2056 "#;
2057
2058 let model = genapi_xml::parse(XML).expect("parse invalid swissknife");
2059 let err = NodeMap::try_from_xml(model).expect_err("unknown variable");
2060 match err {
2061 GenApiError::UnknownVariable { name, var } => {
2062 assert_eq!(name, "Bad");
2063 assert_eq!(var, "Missing");
2064 }
2065 other => panic!("unexpected error: {other:?}"),
2066 }
2067 }
2068
2069 #[test]
2070 fn swissknife_division_by_zero() {
2071 let nodemap = build_swissknife_nodemap();
2072 let io = MockIo::with_registers(&[
2073 (0x3000, i64_to_bytes("GainRaw", 10, 4).unwrap()),
2074 (0x3008, i64_to_bytes("Offset", 0, 4).unwrap()),
2075 (0x3010, i64_to_bytes("B", 0, 4).unwrap()),
2076 ]);
2077
2078 let err = nodemap
2079 .get_float("DivideByZero", &io)
2080 .expect_err("division by zero");
2081 match err {
2082 GenApiError::ExprEval { name, msg } => {
2083 assert_eq!(name, "DivideByZero");
2084 assert_eq!(msg, "division by zero");
2085 }
2086 other => panic!("unexpected error: {other:?}"),
2087 }
2088 }
2089}