1#![cfg_attr(docsrs, feature(doc_cfg))]
2mod bitops;
5mod conversions;
6mod error;
7mod io;
8mod nodemap;
9mod nodes;
10pub mod swissknife;
11
12pub use error::GenApiError;
13pub use io::{NullIo, RegisterIo};
14pub use nodemap::NodeMap;
15pub use nodes::{
16 BooleanNode, CategoryNode, CommandNode, EnumNode, FloatNode, IntegerNode, Node, NodeMeta,
17 RegisterNode, Representation, SkNode, Visibility,
18};
19pub use swissknife::{AstNode, EvalMode, Value};
20pub use viva_genapi_xml::{AccessMode, SkOutput, SkippedNode};
21
22#[cfg(test)]
23mod tests {
24 use std::cell::RefCell;
25 use std::collections::HashMap;
26
27 use crate::conversions::{bytes_to_i64, i64_to_bytes};
28 use crate::{AccessMode, GenApiError, NodeMap, RegisterIo, Visibility};
29 use viva_genapi_xml::{ByteOrder, Sign};
30
31 const FIXTURE: &str = r#"
32 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="2" SchemaSubMinorVersion="3">
33 <Integer Name="Width">
34 <Address>0x100</Address>
35 <Length>4</Length>
36 <AccessMode>RW</AccessMode>
37 <Min>16</Min>
38 <Max>4096</Max>
39 <Inc>2</Inc>
40 </Integer>
41 <Float Name="ExposureTime">
42 <Address>0x200</Address>
43 <Length>4</Length>
44 <AccessMode>RW</AccessMode>
45 <Min>10.0</Min>
46 <Max>100000.0</Max>
47 <Scale>1/1000</Scale>
48 </Float>
49 <Enumeration Name="GainSelector">
50 <Address>0x300</Address>
51 <Length>2</Length>
52 <AccessMode>RW</AccessMode>
53 <EnumEntry Name="All" Value="0" />
54 <EnumEntry Name="Red" Value="1" />
55 <EnumEntry Name="Blue" Value="2" />
56 </Enumeration>
57 <Integer Name="Gain">
58 <Length>2</Length>
59 <AccessMode>RW</AccessMode>
60 <Min>0</Min>
61 <Max>48</Max>
62 <pSelected>GainSelector</pSelected>
63 <Selected>All</Selected>
64 <Address>0x310</Address>
65 <Selected>Red</Selected>
66 <Address>0x314</Address>
67 <Selected>Blue</Selected>
68 </Integer>
69 <Boolean Name="GammaEnable">
70 <Address>0x400</Address>
71 <Length>1</Length>
72 <AccessMode>RW</AccessMode>
73 </Boolean>
74 <Command Name="AcquisitionStart">
75 <Address>0x500</Address>
76 <Length>4</Length>
77 </Command>
78 </RegisterDescription>
79 "#;
80
81 const INDIRECT_FIXTURE: &str = r#"
82 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
83 <Integer Name="RegAddr">
84 <Address>0x2000</Address>
85 <Length>4</Length>
86 <AccessMode>RW</AccessMode>
87 <Min>0</Min>
88 <Max>65535</Max>
89 </Integer>
90 <Integer Name="Gain">
91 <pAddress>RegAddr</pAddress>
92 <Length>4</Length>
93 <AccessMode>RW</AccessMode>
94 <Min>0</Min>
95 <Max>255</Max>
96 </Integer>
97 </RegisterDescription>
98 "#;
99
100 const ENUM_PVALUE_FIXTURE: &str = r#"
101 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
102 <Enumeration Name="Mode">
103 <Address>0x4000</Address>
104 <Length>4</Length>
105 <AccessMode>RW</AccessMode>
106 <EnumEntry Name="Fixed10">
107 <Value>10</Value>
108 </EnumEntry>
109 <EnumEntry Name="DynFromReg">
110 <pValue>RegModeVal</pValue>
111 </EnumEntry>
112 </Enumeration>
113 <Integer Name="RegModeVal">
114 <Address>0x4100</Address>
115 <Length>4</Length>
116 <AccessMode>RW</AccessMode>
117 <Min>0</Min>
118 <Max>65535</Max>
119 </Integer>
120 </RegisterDescription>
121 "#;
122
123 const BITFIELD_FIXTURE: &str = r#"
124 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
125 <Integer Name="LeByte">
126 <Address>0x5000</Address>
127 <Length>4</Length>
128 <AccessMode>RW</AccessMode>
129 <Min>0</Min>
130 <Max>65535</Max>
131 <Mask>0x0000FF00</Mask>
132 </Integer>
133 <!-- The top three bits of a big-endian 16-bit word. Indices count
134 down from the MSB here, so the field is 0..=2 and `<LSB>` is the
135 larger of the two. This used to read `<Lsb>13</Lsb><Msb>15</Msb>`,
136 an orientation that appears nowhere in the vendor corpus and
137 encoded the issue-#120 defect rather than catching it. -->
138 <Integer Name="BeBits">
139 <Address>0x5004</Address>
140 <Length>2</Length>
141 <AccessMode>RW</AccessMode>
142 <Min>0</Min>
143 <Max>15</Max>
144 <LSB>2</LSB>
145 <MSB>0</MSB>
146 <Endianess>BigEndian</Endianess>
147 </Integer>
148 <Boolean Name="PackedFlag">
149 <Address>0x5006</Address>
150 <Length>4</Length>
151 <AccessMode>RW</AccessMode>
152 <Bit>13</Bit>
153 </Boolean>
154 </RegisterDescription>
155 "#;
156
157 const SWISSKNIFE_FIXTURE: &str = r#"
158 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
159 <Integer Name="GainRaw">
160 <Address>0x3000</Address>
161 <Length>4</Length>
162 <AccessMode>RW</AccessMode>
163 <Min>0</Min>
164 <Max>1000</Max>
165 </Integer>
166 <Float Name="Offset">
167 <Address>0x3008</Address>
168 <Length>4</Length>
169 <AccessMode>RW</AccessMode>
170 <Min>-100.0</Min>
171 <Max>100.0</Max>
172 <Scale>1</Scale>
173 </Float>
174 <Integer Name="B">
175 <Address>0x3010</Address>
176 <Length>4</Length>
177 <AccessMode>RW</AccessMode>
178 <Min>-1000</Min>
179 <Max>1000</Max>
180 </Integer>
181 <SwissKnife Name="ComputedGain">
182 <Formula>(GainRaw * 0.5) + Offset</Formula>
183 <pVariable Name="GainRaw">GainRaw</pVariable>
184 <pVariable Name="Offset">Offset</pVariable>
185 </SwissKnife>
186 <IntSwissKnife Name="DivideInt">
187 <Formula>GainRaw / 3</Formula>
188 <pVariable Name="GainRaw">GainRaw</pVariable>
189 </IntSwissKnife>
190 <IntSwissKnife Name="Unary">
191 <Formula>-GainRaw + 10</Formula>
192 <pVariable Name="GainRaw">GainRaw</pVariable>
193 </IntSwissKnife>
194 <SwissKnife Name="DivideByZero">
195 <Formula>GainRaw / B</Formula>
196 <pVariable Name="GainRaw">GainRaw</pVariable>
197 <pVariable Name="B">B</pVariable>
198 </SwissKnife>
199 </RegisterDescription>
200 "#;
201
202 #[derive(Default)]
203 struct MockIo {
204 regs: RefCell<HashMap<u64, Vec<u8>>>,
205 reads: RefCell<HashMap<u64, usize>>,
206 }
207
208 impl MockIo {
209 fn with_registers(entries: &[(u64, Vec<u8>)]) -> Self {
210 let mut regs = HashMap::new();
211 for (addr, data) in entries {
212 regs.insert(*addr, data.clone());
213 }
214 MockIo {
215 regs: RefCell::new(regs),
216 reads: RefCell::new(HashMap::new()),
217 }
218 }
219
220 fn read_count(&self, addr: u64) -> usize {
221 *self.reads.borrow().get(&addr).unwrap_or(&0)
222 }
223 }
224
225 impl RegisterIo for MockIo {
226 fn read(&self, addr: u64, len: usize) -> Result<Vec<u8>, GenApiError> {
227 let mut reads = self.reads.borrow_mut();
228 *reads.entry(addr).or_default() += 1;
229 let regs = self.regs.borrow();
230 let data = regs
231 .get(&addr)
232 .ok_or_else(|| GenApiError::Io(format!("read miss at 0x{addr:08X}")))?;
233 if data.len() != len {
234 return Err(GenApiError::Io(format!(
235 "length mismatch at 0x{addr:08X}: expected {len}, have {}",
236 data.len()
237 )));
238 }
239 Ok(data.clone())
240 }
241
242 fn write(&self, addr: u64, data: &[u8]) -> Result<(), GenApiError> {
243 self.regs.borrow_mut().insert(addr, data.to_vec());
244 Ok(())
245 }
246 }
247
248 fn build_nodemap() -> NodeMap {
249 let model = viva_genapi_xml::parse(FIXTURE).expect("parse fixture");
250 NodeMap::try_from_xml(model).expect("build nodemap")
251 }
252
253 #[test]
261 fn an_unsupported_node_type_is_visible_in_the_nodemap() {
262 const WITH_UNKNOWN: &str = r#"
265 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="0">
266 <Integer Name="Width">
267 <Address>0x100</Address>
268 <Length>4</Length>
269 <AccessMode>RW</AccessMode>
270 </Integer>
271 <ConfRom Name="DeviceConfRom">
272 <Address>0x2000</Address>
273 <Length>512</Length>
274 </ConfRom>
275 </RegisterDescription>
276 "#;
277 let model = viva_genapi_xml::parse(WITH_UNKNOWN).expect("parse");
278 assert_eq!(model.skipped.len(), 1, "XML layer records the unknown tag");
279
280 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
281 let io = MockIo::with_registers(&[(0x100, vec![0, 0, 0, 7])]);
282 assert_eq!(nodemap.get_integer("Width", &io).expect("read Width"), 7);
283
284 let skipped = nodemap.skipped();
285 assert_eq!(skipped.len(), 1, "and the nodemap carries it forward");
286 assert_eq!(skipped[0].tag, "ConfRom");
287 assert_eq!(skipped[0].name.as_deref(), Some("DeviceConfRom"));
288 }
289
290 const REGISTER_FIXTURE: &str = r#"
294 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="0">
295 <Register Name="FileAccessBuffer">
296 <Address>0x2000</Address>
297 <Length>8</Length>
298 <AccessMode>RW</AccessMode>
299 </Register>
300 <Register Name="DeviceSerialBlock">
301 <Address>0x3000</Address>
302 <Length>4</Length>
303 <AccessMode>RO</AccessMode>
304 <pPort>Device</pPort>
305 </Register>
306 <Register Name="ChunkMeasurementResults">
307 <Address>0x0</Address>
308 <Length>4</Length>
309 <AccessMode>RO</AccessMode>
310 <pPort>Chunk4007</pPort>
311 </Register>
312 <Register Name="DynamicBlock">
313 <Address>0x4000</Address>
314 <pLength>BlockLength</pLength>
315 </Register>
316 <StringReg Name="DeviceVendorName">
317 <Address>0x5000</Address>
318 <Length>4</Length>
319 <AccessMode>RO</AccessMode>
320 </StringReg>
321 </RegisterDescription>
322 "#;
323
324 fn build_register_nodemap() -> NodeMap {
325 let model = viva_genapi_xml::parse(REGISTER_FIXTURE).expect("parse register fixture");
326 NodeMap::try_from_xml(model).expect("build nodemap")
327 }
328
329 #[test]
330 fn register_reads_and_writes_raw_bytes() {
331 let nodemap = build_register_nodemap();
332 let payload = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04];
333 let io = MockIo::with_registers(&[(0x2000, payload.clone())]);
334
335 assert_eq!(
336 nodemap
337 .get_register("FileAccessBuffer", &io)
338 .expect("read register"),
339 payload
340 );
341
342 let replacement = vec![9u8; 8];
343 nodemap
344 .set_register("FileAccessBuffer", &replacement, &io)
345 .expect("write register");
346 assert_eq!(
347 nodemap
348 .get_register("FileAccessBuffer", &io)
349 .expect("re-read register"),
350 replacement
351 );
352 }
353
354 #[test]
360 fn register_write_of_the_wrong_length_is_refused() {
361 let nodemap = build_register_nodemap();
362 let io = MockIo::with_registers(&[(0x2000, vec![0u8; 8])]);
363
364 let err = nodemap
365 .set_register("FileAccessBuffer", &[1, 2, 3], &io)
366 .expect_err("a 3-byte write into an 8-byte register must fail");
367 assert!(
368 matches!(err, GenApiError::Range(_)),
369 "expected a range error, got {err:?}"
370 );
371 assert_eq!(
372 io.read(0x2000, 8).expect("register untouched"),
373 vec![0u8; 8],
374 "the refused write must not have reached the device"
375 );
376 }
377
378 #[test]
385 fn register_on_a_non_device_port_is_listed_but_not_readable() {
386 let nodemap = build_register_nodemap();
387 let io = MockIo::with_registers(&[(0x0, vec![1, 2, 3, 4])]);
388
389 assert!(
390 nodemap.node_names().any(|n| n == "ChunkMeasurementResults"),
391 "the node must still be visible for introspection"
392 );
393
394 let err = nodemap
395 .get_register("ChunkMeasurementResults", &io)
396 .expect_err("a chunk-port register must not be read through the device port");
397 let text = err.to_string();
398 assert!(
399 text.contains("Chunk4007") && text.contains("GA-12"),
400 "the error must name the port and the reason: {text}"
401 );
402
403 let io = MockIo::with_registers(&[(0x3000, vec![5, 6, 7, 8])]);
405 assert_eq!(
406 nodemap
407 .get_register("DeviceSerialBlock", &io)
408 .expect("explicit Device port reads"),
409 vec![5, 6, 7, 8]
410 );
411 }
412
413 #[test]
415 fn register_address_resolves_for_a_register_node() {
416 let nodemap = build_register_nodemap();
417 let io = MockIo::default();
418 assert_eq!(
419 nodemap
420 .register_address("FileAccessBuffer", &io)
421 .expect("resolve address"),
422 (0x2000, 8)
423 );
424 }
425
426 #[test]
427 fn register_accessors_reject_the_wrong_node_type() {
428 let nodemap = build_register_nodemap();
429 let io = MockIo::with_registers(&[(0x5000, b"AVT\0".to_vec())]);
430
431 assert!(matches!(
432 nodemap.get_register("DeviceVendorName", &io),
433 Err(GenApiError::Type(_))
434 ));
435 assert!(matches!(
436 nodemap.get_register("NoSuchNode", &io),
437 Err(GenApiError::NodeNotFound(_))
438 ));
439 }
440
441 #[test]
446 fn a_register_with_p_length_is_skipped_and_says_why() {
447 let nodemap = build_register_nodemap();
448 let skipped = nodemap.skipped();
449
450 let entry = skipped
451 .iter()
452 .find(|s| s.name.as_deref() == Some("DynamicBlock"))
453 .expect("a <pLength> register must be recorded, not silently dropped");
454 assert_eq!(entry.tag, "Register");
455 assert!(
456 entry.error.contains("<pLength>"),
457 "the skip reason must name <pLength>: {}",
458 entry.error
459 );
460
461 assert_eq!(nodemap.node_names().count(), 4);
463 }
464
465 #[test]
467 fn elements_without_a_name_are_not_reported_as_skipped() {
468 const WITH_PORT: &str = r#"
469 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="0">
470 <Port Name="Device"/>
471 <Group Comment="Standard">
472 <Integer Name="Width">
473 <Address>0x100</Address>
474 <Length>4</Length>
475 <AccessMode>RW</AccessMode>
476 </Integer>
477 </Group>
478 </RegisterDescription>
479 "#;
480 let model = viva_genapi_xml::parse(WITH_PORT).expect("parse");
481 assert!(model.skipped.is_empty(), "{:?}", model.skipped);
482 assert_eq!(model.nodes.len(), 1);
483 }
484
485 fn build_indirect_nodemap() -> NodeMap {
486 let model = viva_genapi_xml::parse(INDIRECT_FIXTURE).expect("parse indirect fixture");
487 NodeMap::try_from_xml(model).expect("build nodemap")
488 }
489
490 fn build_enum_pvalue_nodemap() -> NodeMap {
491 let model = viva_genapi_xml::parse(ENUM_PVALUE_FIXTURE).expect("parse enum pvalue fixture");
492 NodeMap::try_from_xml(model).expect("build nodemap")
493 }
494
495 fn build_bitfield_nodemap() -> NodeMap {
496 let model = viva_genapi_xml::parse(BITFIELD_FIXTURE).expect("parse bitfield fixture");
497 NodeMap::try_from_xml(model).expect("build nodemap")
498 }
499
500 fn build_swissknife_nodemap() -> NodeMap {
501 let model = viva_genapi_xml::parse(SWISSKNIFE_FIXTURE).expect("parse swissknife fixture");
502 NodeMap::try_from_xml(model).expect("build nodemap")
503 }
504
505 #[test]
506 fn integer_roundtrip_and_cache() {
507 let mut nodemap = build_nodemap();
508 let io = MockIo::with_registers(&[(0x100, vec![0, 0, 4, 0])]);
509 let width = nodemap.get_integer("Width", &io).expect("read width");
510 assert_eq!(width, 1024);
511 assert_eq!(io.read_count(0x100), 1);
512 let width_again = nodemap.get_integer("Width", &io).expect("cached width");
513 assert_eq!(width_again, 1024);
514 assert_eq!(io.read_count(0x100), 1, "cached value should be reused");
515 nodemap
516 .set_integer("Width", 1030, &io)
517 .expect("write width");
518 let width = nodemap
519 .get_integer("Width", &io)
520 .expect("read updated width");
521 assert_eq!(width, 1030);
522 assert_eq!(io.read_count(0x100), 1, "write should update cache");
523 }
524
525 const IEEE754_FIXTURE: &str = r#"
526 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
527 <FloatReg Name="FrameRate">
528 <Address>0x100</Address>
529 <Length>4</Length>
530 <AccessMode>RW</AccessMode>
531 <Min>0.0</Min>
532 <Max>1000.0</Max>
533 <Endianess>BigEndian</Endianess>
534 </FloatReg>
535 <Float Name="ExposureUs">
536 <Address>0x110</Address>
537 <Length>8</Length>
538 <AccessMode>RW</AccessMode>
539 <Min>0.0</Min>
540 <Max>1000000.0</Max>
541 <Endianess>BigEndian</Endianess>
542 </Float>
543 </RegisterDescription>
544 "#;
545
546 fn build_ieee754_nodemap() -> NodeMap {
547 NodeMap::try_from_xml(viva_genapi_xml::parse(IEEE754_FIXTURE).expect("parse ieee754"))
548 .expect("build nodemap")
549 }
550
551 #[test]
552 fn float_ieee754_f32_roundtrip() {
553 let mut nodemap = build_ieee754_nodemap();
554 let io = MockIo::with_registers(&[(0x100, 30.0f32.to_be_bytes().to_vec())]);
555 let v = nodemap.get_float("FrameRate", &io).expect("read rate");
556 assert!((v - 30.0).abs() < 1e-3, "got {v}");
557
558 nodemap
559 .set_float("FrameRate", 42.5, &io)
560 .expect("write rate");
561 let raw = io.read(0x100, 4).expect("read back");
562 assert_eq!(raw, 42.5f32.to_be_bytes());
563 }
564
565 #[test]
566 fn float_ieee754_f64_heuristic_roundtrip() {
567 let mut nodemap = build_ieee754_nodemap();
568 let io = MockIo::with_registers(&[(0x110, 6000.0f64.to_be_bytes().to_vec())]);
569 let v = nodemap.get_float("ExposureUs", &io).expect("read exposure");
570 assert!((v - 6000.0).abs() < 1e-9, "got {v}");
571
572 nodemap
573 .set_float("ExposureUs", 5000.0, &io)
574 .expect("write exposure");
575 let raw = io.read(0x110, 8).expect("read back");
576 assert_eq!(raw, 5000.0f64.to_be_bytes());
577 }
578
579 #[test]
580 fn float_scaled_integer_preserved() {
581 let nodemap = build_nodemap();
584 let raw = 50_000i64;
585 let io = MockIo::with_registers(&[(
586 0x200,
587 i64_to_bytes("ExposureTime", raw, 4, Sign::Signed, ByteOrder::Big).unwrap(),
588 )]);
589 let exposure = nodemap
590 .get_float("ExposureTime", &io)
591 .expect("read exposure");
592 assert!((exposure - 50.0).abs() < 1e-6);
593 }
594
595 const PREDICATE_FIXTURE: &str = r#"
596 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
597 <IntReg Name="CtrlReg">
598 <Address>0x400</Address>
599 <Length>4</Length>
600 <AccessMode>RW</AccessMode>
601 <Sign>Unsigned</Sign>
602 <Endianess>BigEndian</Endianess>
603 </IntReg>
604 <IntSwissKnife Name="GateImplemented">
605 <Formula>CTRL & 1</Formula>
606 <pVariable Name="CTRL">CtrlReg</pVariable>
607 <Output>Integer</Output>
608 </IntSwissKnife>
609 <IntSwissKnife Name="GateLocked">
610 <Formula>(CTRL & 2) / 2</Formula>
611 <pVariable Name="CTRL">CtrlReg</pVariable>
612 <Output>Integer</Output>
613 </IntSwissKnife>
614 <IntSwissKnife Name="Entry8Implemented">
615 <Formula>(CTRL & 4) / 4</Formula>
616 <pVariable Name="CTRL">CtrlReg</pVariable>
617 <Output>Integer</Output>
618 </IntSwissKnife>
619 <Integer Name="Gated">
620 <Address>0x410</Address>
621 <Length>4</Length>
622 <AccessMode>RW</AccessMode>
623 <Min>0</Min>
624 <Max>255</Max>
625 <Sign>Unsigned</Sign>
626 <Endianess>BigEndian</Endianess>
627 <pIsImplemented>GateImplemented</pIsImplemented>
628 <pIsLocked>GateLocked</pIsLocked>
629 </Integer>
630 <Enumeration Name="PixelFormat">
631 <EnumEntry Name="Mono8"><Value>1</Value></EnumEntry>
632 <EnumEntry Name="Mono16">
633 <Value>2</Value>
634 <pIsImplemented>Entry8Implemented</pIsImplemented>
635 </EnumEntry>
636 <pValue>PixelFormatReg</pValue>
637 </Enumeration>
638 <IntReg Name="PixelFormatReg">
639 <Address>0x420</Address>
640 <Length>4</Length>
641 <AccessMode>RW</AccessMode>
642 <Sign>Unsigned</Sign>
643 <Endianess>BigEndian</Endianess>
644 </IntReg>
645 </RegisterDescription>
646 "#;
647
648 const BIG_ENDIAN_PREDICATE_FIXTURE: &str = r#"
652 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1">
653 <Float Name="ExposureTime">
654 <pIsImplemented>ExposureTime_Imp</pIsImplemented>
655 <pIsAvailable>ExposureTime_Avl</pIsAvailable>
656 <pIsLocked>ExposureTime_Lck</pIsLocked>
657 <pValue>ExposureTime_FloatVal</pValue>
658 </Float>
659 <Converter Name="ExposureTime_FloatVal">
660 <FormulaTo>FROM</FormulaTo>
661 <FormulaFrom>TO</FormulaFrom>
662 <pValue>ExposureTime_Val</pValue>
663 <Slope>Increasing</Slope>
664 </Converter>
665 <IntReg Name="ExposureTime_Val">
666 <Address>0x000C1004</Address><Length>4</Length>
667 <AccessMode>RW</AccessMode><Sign>Unsigned</Sign>
668 <Endianess>BigEndian</Endianess>
669 </IntReg>
670 <MaskedIntReg Name="ExposureTime_Imp">
671 <Address>0x000C1000</Address><Length>4</Length>
672 <AccessMode>RO</AccessMode><Bit>0</Bit>
673 <Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
674 </MaskedIntReg>
675 <MaskedIntReg Name="ExposureTime_Avl">
676 <Address>0x000C1000</Address><Length>4</Length>
677 <AccessMode>RO</AccessMode><Bit>1</Bit>
678 <Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
679 </MaskedIntReg>
680 <MaskedIntReg Name="ExposureTime_Lck">
681 <Address>0x000C1000</Address><Length>4</Length>
682 <AccessMode>RO</AccessMode><Bit>3</Bit>
683 <Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
684 </MaskedIntReg>
685 </RegisterDescription>
686 "#;
687
688 fn big_endian_predicate_io(status: u32) -> MockIo {
689 MockIo::with_registers(&[
690 (0x000C_1000, status.to_be_bytes().to_vec()),
691 (0x000C_1004, 10_000u32.to_be_bytes().to_vec()),
692 ])
693 }
694
695 #[test]
709 fn big_endian_lsb_msb_range_decodes_the_gige_packet_size_register() {
710 const XML: &str = r#"
711 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1">
712 <MaskedIntReg Name="RegSCPSPacketSize">
713 <Address>0xD04</Address><Length>4</Length>
714 <AccessMode>RW</AccessMode>
715 <LSB>31</LSB><MSB>16</MSB>
716 <Endianess>BigEndian</Endianess>
717 </MaskedIntReg>
718 </RegisterDescription>
719 "#;
720 let nodemap =
721 NodeMap::try_from_xml(viva_genapi_xml::parse(XML).expect("parse")).expect("nodemap");
722 let io = MockIo::with_registers(&[(0x0D04, 0x4000_0578u32.to_be_bytes().to_vec())]);
723
724 assert_eq!(
725 nodemap.get_integer("RegSCPSPacketSize", &io).expect("read"),
726 1400
727 );
728 }
729
730 #[test]
739 fn struct_entry_and_masked_int_reg_agree_on_a_big_endian_bit() {
740 const XML: &str = r#"
741 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1">
742 <MaskedIntReg Name="ViaMaskedIntReg">
743 <Address>0x500</Address><Length>4</Length>
744 <AccessMode>RO</AccessMode><Bit>1</Bit>
745 <Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
746 </MaskedIntReg>
747 <StructReg Comment="same word">
748 <Address>0x500</Address><Length>4</Length>
749 <AccessMode>RO</AccessMode><Sign>Unsigned</Sign>
750 <Endianess>BigEndian</Endianess>
751 <StructEntry Name="ViaStructEntry"><Bit>1</Bit></StructEntry>
752 </StructReg>
753 </RegisterDescription>
754 "#;
755 let nodemap =
756 NodeMap::try_from_xml(viva_genapi_xml::parse(XML).expect("parse")).expect("nodemap");
757
758 for word in [0x4000_0000u32, 0x0000_0002, 0xFFFF_FFFF, 0x0000_0000] {
759 let io = MockIo::with_registers(&[(0x500, word.to_be_bytes().to_vec())]);
760 let masked = nodemap.get_integer("ViaMaskedIntReg", &io).expect("masked");
761 let entry = nodemap.get_integer("ViaStructEntry", &io).expect("entry");
762 assert_eq!(masked, entry, "0x{word:08X} decoded differently");
763 }
764 }
765
766 #[test]
774 fn big_endian_gating_bits_permit_the_write_they_describe() {
775 let mut nodemap = NodeMap::try_from_xml(
776 viva_genapi_xml::parse(BIG_ENDIAN_PREDICATE_FIXTURE).expect("parse"),
777 )
778 .expect("nodemap");
779 let io = big_endian_predicate_io(0xC000_0000);
780
781 assert!(nodemap.is_implemented("ExposureTime", &io).expect("imp"));
782 assert!(nodemap.is_available("ExposureTime", &io).expect("avl"));
783 assert_eq!(
784 nodemap
785 .effective_access_mode("ExposureTime", &io)
786 .expect("access"),
787 AccessMode::RW
788 );
789
790 nodemap
791 .set_float("ExposureTime", 12_000.0, &io)
792 .expect("write must reach the wire");
793 assert_eq!(
794 io.regs.borrow().get(&0x000C_1004).cloned(),
795 Some(12_000u32.to_be_bytes().to_vec())
796 );
797 }
798
799 #[test]
805 fn big_endian_lock_bit_is_counted_from_the_msb() {
806 let mut nodemap = NodeMap::try_from_xml(
807 viva_genapi_xml::parse(BIG_ENDIAN_PREDICATE_FIXTURE).expect("parse"),
808 )
809 .expect("nodemap");
810 let io = big_endian_predicate_io(0xD000_0000);
811
812 let err = nodemap
813 .set_float("ExposureTime", 12_000.0, &io)
814 .expect_err("locked");
815 assert!(
816 matches!(err, GenApiError::Locked { ref locked_by, .. } if locked_by == "ExposureTime_Lck"),
817 "expected Locked by ExposureTime_Lck, got {err:?}"
818 );
819 }
820
821 #[test]
825 fn big_endian_availability_bit_refuses_the_write() {
826 let mut nodemap = NodeMap::try_from_xml(
827 viva_genapi_xml::parse(BIG_ENDIAN_PREDICATE_FIXTURE).expect("parse"),
828 )
829 .expect("nodemap");
830 let io = big_endian_predicate_io(0x8000_0000);
831
832 let err = nodemap
833 .set_float("ExposureTime", 12_000.0, &io)
834 .expect_err("unavailable");
835 assert!(
836 matches!(err, GenApiError::Unavailable(ref n) if n == "ExposureTime"),
837 "expected Unavailable, got {err:?}"
838 );
839 }
840
841 fn build_predicate_nodemap() -> NodeMap {
842 NodeMap::try_from_xml(
843 viva_genapi_xml::parse(PREDICATE_FIXTURE).expect("parse predicate fixture"),
844 )
845 .expect("build nodemap")
846 }
847
848 fn predicate_io(ctrl: u32) -> MockIo {
849 MockIo::with_registers(&[
850 (0x400, ctrl.to_be_bytes().to_vec()),
851 (0x410, 0u32.to_be_bytes().to_vec()),
852 (0x420, 1u32.to_be_bytes().to_vec()),
853 ])
854 }
855
856 #[test]
857 fn predicate_is_implemented_defaults_true() {
858 let nodemap = build_predicate_nodemap();
859 let io = predicate_io(0);
860 assert!(nodemap.is_implemented("CtrlReg", &io).unwrap());
862 }
863
864 #[test]
865 fn predicate_is_implemented_follows_gate() {
866 let nm0 = build_predicate_nodemap();
867 let io0 = predicate_io(0);
868 assert!(!nm0.is_implemented("Gated", &io0).unwrap());
869 let nm1 = build_predicate_nodemap();
870 let io1 = predicate_io(1);
871 assert!(nm1.is_implemented("Gated", &io1).unwrap());
872 }
873
874 #[test]
875 fn predicate_is_available_chains_implemented() {
876 let nm0 = build_predicate_nodemap();
877 let io0 = predicate_io(0);
878 assert!(!nm0.is_available("Gated", &io0).unwrap());
879 let nm1 = build_predicate_nodemap();
880 let io1 = predicate_io(1);
881 assert!(nm1.is_available("Gated", &io1).unwrap());
882 }
883
884 #[test]
885 fn predicate_effective_access_mode_locked_downgrade() {
886 let nodemap = build_predicate_nodemap();
887 let io = predicate_io(0b11);
889 let mode = nodemap.effective_access_mode("Gated", &io).unwrap();
890 assert_eq!(mode, AccessMode::RO);
891 }
892
893 #[test]
894 fn write_to_a_locked_node_is_refused_locally() {
895 let mut nodemap = build_predicate_nodemap();
899 let io = predicate_io(0b11); let err = nodemap
901 .set_integer("Gated", 7, &io)
902 .expect_err("a locked node must not be written");
903 match err {
904 GenApiError::Locked { name, locked_by } => {
905 assert_eq!(name, "Gated");
906 assert_eq!(locked_by, "GateLocked");
908 }
909 other => panic!("expected Locked, got {other:?}"),
910 }
911 }
912
913 #[test]
914 fn write_to_an_unlocked_node_still_succeeds() {
915 let mut nodemap = build_predicate_nodemap();
916 let io = predicate_io(0b01); nodemap
918 .set_integer("Gated", 7, &io)
919 .expect("an unlocked RW node must still be writable");
920 }
921
922 #[test]
923 fn write_to_an_unimplemented_node_reports_unavailable_not_locked() {
924 let mut nodemap = build_predicate_nodemap();
927 let io = predicate_io(0b00); let err = nodemap
929 .set_integer("Gated", 7, &io)
930 .expect_err("an unavailable node must not be written");
931 assert!(
932 matches!(err, GenApiError::Unavailable(ref n) if n == "Gated"),
933 "expected Unavailable, got {err:?}"
934 );
935 }
936
937 #[test]
938 fn predicate_effective_access_mode_rw_when_unlocked() {
939 let nodemap = build_predicate_nodemap();
940 let io = predicate_io(0b01);
942 let mode = nodemap.effective_access_mode("Gated", &io).unwrap();
943 assert_eq!(mode, AccessMode::RW);
944 }
945
946 #[test]
947 fn predicate_effective_access_mode_na_for_unavailable() {
948 let nodemap = build_predicate_nodemap();
949 let io = predicate_io(0);
951 let mode = nodemap.effective_access_mode("Gated", &io).unwrap();
952 assert_eq!(mode, AccessMode::RO);
953 }
954
955 #[test]
956 fn predicate_available_enum_entries_filters() {
957 let nodemap = build_predicate_nodemap();
958 let io = predicate_io(0);
960 let entries = nodemap
961 .available_enum_entries("PixelFormat", &io)
962 .expect("enum entries");
963 assert_eq!(entries, vec!["Mono8".to_string()]);
964 }
965
966 #[test]
967 fn predicate_available_enum_entries_full_when_allowed() {
968 let nodemap = build_predicate_nodemap();
969 let io = predicate_io(0b100);
971 let mut entries = nodemap
972 .available_enum_entries("PixelFormat", &io)
973 .expect("enum entries");
974 entries.sort();
975 assert_eq!(entries, vec!["Mono16".to_string(), "Mono8".to_string()]);
976 }
977
978 #[test]
979 fn predicate_available_enum_entries_fallback_to_static() {
980 let xml = r#"
982 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
983 <Enumeration Name="Mode">
984 <EnumEntry Name="A"><Value>0</Value></EnumEntry>
985 <EnumEntry Name="B"><Value>1</Value></EnumEntry>
986 <pValue>ModeReg</pValue>
987 </Enumeration>
988 <IntReg Name="ModeReg">
989 <Address>0x500</Address>
990 <Length>4</Length>
991 <AccessMode>RW</AccessMode>
992 <Sign>Unsigned</Sign>
993 <Endianess>BigEndian</Endianess>
994 </IntReg>
995 </RegisterDescription>
996 "#;
997 let nodemap =
998 NodeMap::try_from_xml(viva_genapi_xml::parse(xml).unwrap()).expect("build nodemap");
999 let io = MockIo::with_registers(&[(0x500, 0u32.to_be_bytes().to_vec())]);
1000 let mut entries = nodemap.available_enum_entries("Mode", &io).unwrap();
1001 entries.sort();
1002 assert_eq!(entries, vec!["A".to_string(), "B".to_string()]);
1003 }
1004
1005 #[test]
1006 fn float_conversion_roundtrip() {
1007 let mut nodemap = build_nodemap();
1008 let raw = 50_000i64; let io = MockIo::with_registers(&[(
1010 0x200,
1011 i64_to_bytes("ExposureTime", raw, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1012 )]);
1013 let exposure = nodemap
1014 .get_float("ExposureTime", &io)
1015 .expect("read exposure");
1016 assert!((exposure - 50.0).abs() < 1e-6);
1017 nodemap
1018 .set_float("ExposureTime", 75.0, &io)
1019 .expect("write exposure");
1020 let raw_back = bytes_to_i64(
1021 "ExposureTime",
1022 &io.read(0x200, 4).unwrap(),
1023 Sign::Signed,
1024 ByteOrder::Big,
1025 )
1026 .unwrap();
1027 assert_eq!(raw_back, 75_000);
1028 }
1029
1030 #[test]
1031 fn selector_address_switching() {
1032 let mut nodemap = build_nodemap();
1033 let io = MockIo::with_registers(&[
1034 (
1035 0x300,
1036 i64_to_bytes("GainSelector", 0, 2, Sign::Signed, ByteOrder::Big).unwrap(),
1037 ),
1038 (
1039 0x310,
1040 i64_to_bytes("Gain", 10, 2, Sign::Signed, ByteOrder::Big).unwrap(),
1041 ),
1042 (
1043 0x314,
1044 i64_to_bytes("Gain", 24, 2, Sign::Signed, ByteOrder::Big).unwrap(),
1045 ),
1046 ]);
1047
1048 let gain_all = nodemap.get_integer("Gain", &io).expect("gain for All");
1049 assert_eq!(gain_all, 10);
1050 assert_eq!(io.read_count(0x310), 1);
1051 assert_eq!(io.read_count(0x314), 0);
1052
1053 io.write(
1054 0x314,
1055 &i64_to_bytes("Gain", 32, 2, Sign::Signed, ByteOrder::Big).unwrap(),
1056 )
1057 .expect("update red gain");
1058 nodemap
1059 .set_enum("GainSelector", "Red", &io)
1060 .expect("set selector to red");
1061 let gain_red = nodemap.get_integer("Gain", &io).expect("gain for Red");
1062 assert_eq!(gain_red, 32);
1063 assert_eq!(
1064 io.read_count(0x310),
1065 1,
1066 "previous address should not be reread"
1067 );
1068 assert_eq!(io.read_count(0x314), 1);
1069
1070 let gain_red_cached = nodemap.get_integer("Gain", &io).expect("cached red");
1071 assert_eq!(gain_red_cached, 32);
1072 assert_eq!(io.read_count(0x314), 1, "selector cache should be reused");
1073
1074 nodemap
1075 .set_enum("GainSelector", "Blue", &io)
1076 .expect("set selector to blue");
1077 let err = nodemap.get_integer("Gain", &io).unwrap_err();
1078 match err {
1079 GenApiError::Unavailable(msg) => {
1080 assert!(msg.contains("GainSelector=Blue"));
1081 }
1082 other => panic!("unexpected error: {other:?}"),
1083 }
1084 assert_eq!(
1085 io.read_count(0x314),
1086 1,
1087 "no read expected for missing mapping"
1088 );
1089
1090 io.write(
1091 0x310,
1092 &i64_to_bytes("Gain", 12, 2, Sign::Signed, ByteOrder::Big).unwrap(),
1093 )
1094 .expect("update all gain");
1095 nodemap
1096 .set_enum("GainSelector", "All", &io)
1097 .expect("restore selector to all");
1098 let gain_all_updated = nodemap
1099 .get_integer("Gain", &io)
1100 .expect("gain for All again");
1101 assert_eq!(gain_all_updated, 12);
1102 assert_eq!(
1103 io.read_count(0x310),
1104 2,
1105 "address switch should invalidate cache"
1106 );
1107 }
1108
1109 #[test]
1110 fn range_enforcement() {
1111 let mut nodemap = build_nodemap();
1112 let io = MockIo::with_registers(&[(0x100, vec![0, 0, 0, 16])]);
1113 let err = nodemap.set_integer("Width", 17, &io).unwrap_err();
1114 assert!(matches!(err, GenApiError::Range(_)));
1115 }
1116
1117 #[test]
1118 fn command_exec() {
1119 let mut nodemap = build_nodemap();
1120 let io = MockIo::with_registers(&[]);
1121 nodemap
1122 .exec_command("AcquisitionStart", &io)
1123 .expect("exec command");
1124 let payload = io.read(0x500, 4).expect("command write");
1125 assert_eq!(payload, vec![0, 0, 0, 1]);
1126 }
1127
1128 #[test]
1129 fn indirect_address_resolution() {
1130 let mut nodemap = build_indirect_nodemap();
1131 let io = MockIo::with_registers(&[
1132 (
1133 0x2000,
1134 i64_to_bytes("RegAddr", 0x3000, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1135 ),
1136 (
1137 0x3000,
1138 i64_to_bytes("Gain", 123, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1139 ),
1140 (
1141 0x3100,
1142 i64_to_bytes("Gain", 77, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1143 ),
1144 ]);
1145
1146 let initial = nodemap.get_integer("Gain", &io).expect("read gain");
1147 assert_eq!(initial, 123);
1148 assert_eq!(io.read_count(0x2000), 1);
1149 assert_eq!(io.read_count(0x3000), 1);
1150
1151 nodemap
1152 .set_integer("RegAddr", 0x3100, &io)
1153 .expect("set indirect address");
1154 let updated = nodemap
1155 .get_integer("Gain", &io)
1156 .expect("read gain after change");
1157 assert_eq!(updated, 77);
1158 assert_eq!(io.read_count(0x2000), 1);
1159 assert_eq!(io.read_count(0x3000), 1);
1160 assert_eq!(io.read_count(0x3100), 1);
1161 }
1162
1163 #[test]
1180 fn indirect_bad_address() {
1181 const XML: &str = r#"
1182 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1183 <Integer Name="RegAddr">
1184 <Address>0x2000</Address>
1185 <Length>4</Length>
1186 <AccessMode>RW</AccessMode>
1187 <Sign>Signed</Sign>
1188 <Min>-65535</Min>
1189 <Max>65535</Max>
1190 </Integer>
1191 <Integer Name="Gain">
1192 <pAddress>RegAddr</pAddress>
1193 <Length>4</Length>
1194 <AccessMode>RW</AccessMode>
1195 <Min>0</Min>
1196 <Max>255</Max>
1197 </Integer>
1198 </RegisterDescription>
1199 "#;
1200
1201 let model = viva_genapi_xml::parse(XML).expect("parse");
1202 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1203 let io = MockIo::with_registers(&[(
1204 0x2000,
1205 i64_to_bytes("RegAddr", -4, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1206 )]);
1207
1208 let err = nodemap.get_integer("Gain", &io).unwrap_err();
1209 match err {
1210 GenApiError::BadIndirectAddress { name, addr } => {
1211 assert_eq!(name, "Gain");
1212 assert_eq!(addr, -4);
1213 }
1214 other => panic!("unexpected error: {other:?}"),
1215 }
1216 }
1217
1218 #[test]
1222 fn unsigned_registers_do_not_sign_extend() {
1223 const XML: &str = r#"
1224 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1225 <Integer Name="GevCurrentIPAddress">
1226 <Address>0x1000</Address>
1227 <Length>4</Length>
1228 <AccessMode>RO</AccessMode>
1229 <Min>0</Min>
1230 <Max>4294967295</Max>
1231 </Integer>
1232 <Integer Name="TemperatureOffset">
1233 <Address>0x1004</Address>
1234 <Length>4</Length>
1235 <AccessMode>RO</AccessMode>
1236 <Sign>Signed</Sign>
1237 <Min>-2147483648</Min>
1238 <Max>2147483647</Max>
1239 </Integer>
1240 </RegisterDescription>
1241 "#;
1242
1243 let model = viva_genapi_xml::parse(XML).expect("parse");
1244 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1245 let io = MockIo::with_registers(&[
1246 (0x1000, vec![0xC0, 0xA8, 0x01, 0xA0]),
1248 (0x1004, vec![0xFF, 0xFF, 0xFF, 0xFB]),
1249 ]);
1250
1251 assert_eq!(
1252 nodemap
1253 .get_integer("GevCurrentIPAddress", &io)
1254 .expect("read address"),
1255 0xC0A8_01A0
1256 );
1257 assert_eq!(
1258 nodemap
1259 .get_integer("TemperatureOffset", &io)
1260 .expect("read offset"),
1261 -5
1262 );
1263 }
1264
1265 #[test]
1272 fn converter_directions_are_not_swapped() {
1273 const XML: &str = r#"
1274 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1275 <IntReg Name="ExposureTimeRaw">
1276 <Address>0x1000</Address>
1277 <Length>4</Length>
1278 <AccessMode>RW</AccessMode>
1279 </IntReg>
1280 <Converter Name="ExposureTime">
1281 <FormulaTo>FROM * 100</FormulaTo>
1282 <FormulaFrom>TO / 100</FormulaFrom>
1283 <pValue>ExposureTimeRaw</pValue>
1284 <Unit>us</Unit>
1285 </Converter>
1286 </RegisterDescription>
1287 "#;
1288
1289 let model = viva_genapi_xml::parse(XML).expect("parse");
1290 let mut nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1291 let io = MockIo::with_registers(&[(
1292 0x1000,
1293 i64_to_bytes("ExposureTimeRaw", 5000, 4, Sign::Unsigned, ByteOrder::Big).unwrap(),
1294 )]);
1295
1296 let value = nodemap.get_float("ExposureTime", &io).expect("read");
1298 assert!((value - 50.0).abs() < 1e-9, "got {value}");
1299
1300 nodemap.set_float("ExposureTime", 75.0, &io).expect("write");
1302 assert_eq!(
1303 nodemap
1304 .get_integer("ExposureTimeRaw", &io)
1305 .expect("read raw back"),
1306 7500
1307 );
1308 let roundtrip = nodemap.get_float("ExposureTime", &io).expect("re-read");
1309 assert!((roundtrip - 75.0).abs() < 1e-9, "got {roundtrip}");
1310 }
1311
1312 #[test]
1315 fn int_converter_write_preserves_other_bits() {
1316 const XML: &str = r#"
1317 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1318 <IntReg Name="Binning_Reg">
1319 <Address>0x1000</Address>
1320 <Length>4</Length>
1321 <AccessMode>RW</AccessMode>
1322 </IntReg>
1323 <IntConverter Name="BinningHorizontal">
1324 <FormulaTo>FROM | (OLD & 0xffff0000)</FormulaTo>
1325 <FormulaFrom>TO & 0x0000ffff</FormulaFrom>
1326 <pVariable Name="OLD">Binning_Reg</pVariable>
1327 <pValue>Binning_Reg</pValue>
1328 </IntConverter>
1329 </RegisterDescription>
1330 "#;
1331
1332 let model = viva_genapi_xml::parse(XML).expect("parse");
1333 let mut nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1334 let io = MockIo::with_registers(&[(
1335 0x1000,
1336 i64_to_bytes(
1337 "Binning_Reg",
1338 0x0004_0002,
1339 4,
1340 Sign::Unsigned,
1341 ByteOrder::Big,
1342 )
1343 .unwrap(),
1344 )]);
1345
1346 assert_eq!(
1347 nodemap
1348 .get_integer("BinningHorizontal", &io)
1349 .expect("read low half"),
1350 2
1351 );
1352
1353 nodemap
1354 .set_integer("BinningHorizontal", 3, &io)
1355 .expect("write low half");
1356 assert_eq!(
1358 nodemap
1359 .get_integer("Binning_Reg", &io)
1360 .expect("read raw back"),
1361 0x0004_0003
1362 );
1363 }
1364
1365 #[test]
1374 fn sign_does_not_depend_on_min() {
1375 const XML: &str = r#"
1376 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1377 <IntReg Name="GevCurrentIPAddress">
1378 <Address>0x1000</Address>
1379 <Length>4</Length>
1380 <AccessMode>RO</AccessMode>
1381 <Sign>Unsigned</Sign>
1382 </IntReg>
1383 <IntReg Name="DeviceTemperatureRaw">
1384 <Address>0x1004</Address>
1385 <Length>4</Length>
1386 <AccessMode>RO</AccessMode>
1387 <Sign>Signed</Sign>
1388 </IntReg>
1389 <IntReg Name="ImplicitlyUnsigned">
1390 <Address>0x1008</Address>
1391 <Length>4</Length>
1392 <AccessMode>RO</AccessMode>
1393 </IntReg>
1394 </RegisterDescription>
1395 "#;
1396
1397 let model = viva_genapi_xml::parse(XML).expect("parse");
1398 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1399 let io = MockIo::with_registers(&[
1400 (0x1000, vec![0xC0, 0xA8, 0x01, 0xA0]),
1401 (0x1004, vec![0xFF, 0xFF, 0xFF, 0xFB]),
1402 (0x1008, vec![0xFF, 0xFF, 0xFF, 0xFF]),
1403 ]);
1404
1405 assert_eq!(
1406 nodemap
1407 .get_integer("GevCurrentIPAddress", &io)
1408 .expect("read address"),
1409 0xC0A8_01A0
1410 );
1411 assert_eq!(
1412 nodemap
1413 .get_integer("DeviceTemperatureRaw", &io)
1414 .expect("read temperature"),
1415 -5
1416 );
1417 assert_eq!(
1419 nodemap
1420 .get_integer("ImplicitlyUnsigned", &io)
1421 .expect("read default-signed register"),
1422 0xFFFF_FFFFu32 as i64
1423 );
1424 }
1425
1426 #[test]
1432 fn struct_entry_bits_read_unsigned() {
1433 const XML: &str = r#"
1434 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1435 <StructReg Comment="Gain Inquiry Register">
1436 <Address>0x520</Address>
1437 <Length>4</Length>
1438 <AccessMode>RO</AccessMode>
1439 <Endianess>BigEndian</Endianess>
1440 <StructEntry Name="GainPresInq_Bit"><Bit>0</Bit></StructEntry>
1441 <StructEntry Name="GainAutoInq_Bit"><Bit>6</Bit></StructEntry>
1442 </StructReg>
1443 </RegisterDescription>
1444 "#;
1445
1446 let model = viva_genapi_xml::parse(XML).expect("parse");
1447 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1448 let io = MockIo::with_registers(&[(0x520, vec![0x82, 0x00, 0x00, 0x00])]);
1452
1453 assert_eq!(
1454 nodemap
1455 .get_integer("GainPresInq_Bit", &io)
1456 .expect("read presence bit"),
1457 1
1458 );
1459 assert_eq!(
1460 nodemap
1461 .get_integer("GainAutoInq_Bit", &io)
1462 .expect("read auto bit"),
1463 1
1464 );
1465 }
1466
1467 #[test]
1471 fn address_terms_sum() {
1472 const XML: &str = r#"
1473 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1474 <Integer Name="RegBase">
1475 <Address>0x2000</Address>
1476 <Length>4</Length>
1477 <AccessMode>RW</AccessMode>
1478 <Min>0</Min>
1479 <Max>65535</Max>
1480 </Integer>
1481 <Integer Name="Gain">
1482 <pAddress>RegBase</pAddress>
1483 <Address>0x8</Address>
1484 <Length>4</Length>
1485 <AccessMode>RW</AccessMode>
1486 <Min>0</Min>
1487 <Max>255</Max>
1488 </Integer>
1489 </RegisterDescription>
1490 "#;
1491
1492 let model = viva_genapi_xml::parse(XML).expect("parse");
1493 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1494 let io = MockIo::with_registers(&[
1495 (
1496 0x2000,
1497 i64_to_bytes("RegBase", 0x3000, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1498 ),
1499 (
1501 0x3008,
1502 i64_to_bytes("Gain", 77, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1503 ),
1504 (
1505 0x3000,
1506 i64_to_bytes("Gain", 11, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1507 ),
1508 ]);
1509
1510 assert_eq!(nodemap.get_integer("Gain", &io).expect("read gain"), 77);
1511
1512 assert_eq!(
1514 nodemap
1515 .register_address("Gain", &io)
1516 .expect("resolve summed address"),
1517 (0x3008, 4)
1518 );
1519 assert!(matches!(
1521 nodemap.register_address("NoSuchNode", &io),
1522 Err(GenApiError::NodeNotFound(_))
1523 ));
1524 }
1525
1526 #[test]
1529 fn p_index_scales_into_the_address() {
1530 const XML: &str = r#"
1531 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1532 <Integer Name="TriggerSelectorIdx">
1533 <Address>0x2000</Address>
1534 <Length>4</Length>
1535 <AccessMode>RW</AccessMode>
1536 <Min>0</Min>
1537 <Max>7</Max>
1538 </Integer>
1539 <Integer Name="TriggerInqDelay">
1540 <Address>0x13400</Address>
1541 <pIndex Offset="64">TriggerSelectorIdx</pIndex>
1542 <Length>4</Length>
1543 <AccessMode>RO</AccessMode>
1544 <Min>0</Min>
1545 <Max>255</Max>
1546 </Integer>
1547 </RegisterDescription>
1548 "#;
1549
1550 let model = viva_genapi_xml::parse(XML).expect("parse");
1551 let mut nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
1552 let io = MockIo::with_registers(&[
1553 (
1554 0x2000,
1555 i64_to_bytes("TriggerSelectorIdx", 2, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1556 ),
1557 (
1558 0x13400,
1559 i64_to_bytes("TriggerInqDelay", 1, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1560 ),
1561 (
1562 0x13480,
1563 i64_to_bytes("TriggerInqDelay", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1564 ),
1565 ]);
1566
1567 assert_eq!(
1569 nodemap
1570 .get_integer("TriggerInqDelay", &io)
1571 .expect("read indexed register"),
1572 42
1573 );
1574
1575 nodemap
1577 .set_integer("TriggerSelectorIdx", 0, &io)
1578 .expect("select index 0");
1579 assert_eq!(
1580 nodemap
1581 .get_integer("TriggerInqDelay", &io)
1582 .expect("read indexed register"),
1583 1
1584 );
1585 }
1586
1587 #[test]
1588 fn enum_literal_entry_read() {
1589 let nodemap = build_enum_pvalue_nodemap();
1590 let io = MockIo::with_registers(&[
1591 (
1592 0x4000,
1593 i64_to_bytes("Mode", 10, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1594 ),
1595 (
1596 0x4100,
1597 i64_to_bytes("RegModeVal", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1598 ),
1599 ]);
1600
1601 let value = nodemap.get_enum("Mode", &io).expect("read mode");
1602 assert_eq!(value, "Fixed10");
1603 assert_eq!(
1604 io.read_count(0x4100),
1605 1,
1606 "provider should be read once for mapping"
1607 );
1608 }
1609
1610 #[test]
1611 fn enum_provider_entry_read() {
1612 let nodemap = build_enum_pvalue_nodemap();
1613 let io = MockIo::with_registers(&[
1614 (
1615 0x4000,
1616 i64_to_bytes("Mode", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1617 ),
1618 (
1619 0x4100,
1620 i64_to_bytes("RegModeVal", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1621 ),
1622 ]);
1623
1624 let value = nodemap.get_enum("Mode", &io).expect("read dynamic mode");
1625 assert_eq!(value, "DynFromReg");
1626 assert_eq!(io.read_count(0x4100), 1);
1627 }
1628
1629 #[test]
1630 fn enum_set_uses_provider_value() {
1631 let mut nodemap = build_enum_pvalue_nodemap();
1632 let io = MockIo::with_registers(&[
1633 (
1634 0x4000,
1635 i64_to_bytes("Mode", 0, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1636 ),
1637 (
1638 0x4100,
1639 i64_to_bytes("RegModeVal", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1640 ),
1641 ]);
1642
1643 nodemap
1644 .set_enum("Mode", "DynFromReg", &io)
1645 .expect("write enum");
1646 let raw = bytes_to_i64(
1647 "Mode",
1648 &io.read(0x4000, 4).unwrap(),
1649 Sign::Signed,
1650 ByteOrder::Big,
1651 )
1652 .unwrap();
1653 assert_eq!(raw, 42);
1654 assert_eq!(io.read_count(0x4100), 1);
1655 }
1656
1657 #[test]
1658 fn enum_provider_update_invalidates_mapping() {
1659 let mut nodemap = build_enum_pvalue_nodemap();
1660 let io = MockIo::with_registers(&[
1661 (
1662 0x4000,
1663 i64_to_bytes("Mode", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1664 ),
1665 (
1666 0x4100,
1667 i64_to_bytes("RegModeVal", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1668 ),
1669 ]);
1670
1671 assert_eq!(nodemap.get_enum("Mode", &io).unwrap(), "DynFromReg");
1672 assert_eq!(io.read_count(0x4100), 1);
1673
1674 nodemap
1675 .set_integer("RegModeVal", 17, &io)
1676 .expect("update provider");
1677 io.write(
1678 0x4000,
1679 &i64_to_bytes("Mode", 0, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1680 )
1681 .expect("reset mode register");
1682
1683 nodemap
1684 .set_enum("Mode", "DynFromReg", &io)
1685 .expect("write enum after provider change");
1686 let raw = bytes_to_i64(
1687 "Mode",
1688 &io.read(0x4000, 4).unwrap(),
1689 Sign::Signed,
1690 ByteOrder::Big,
1691 )
1692 .unwrap();
1693 assert_eq!(raw, 17);
1694 }
1695
1696 #[test]
1697 fn enum_unknown_value_error() {
1698 let nodemap = build_enum_pvalue_nodemap();
1699 let io = MockIo::with_registers(&[
1700 (
1701 0x4000,
1702 i64_to_bytes("Mode", 99, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1703 ),
1704 (
1705 0x4100,
1706 i64_to_bytes("RegModeVal", 42, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1707 ),
1708 ]);
1709
1710 let err = nodemap.get_enum("Mode", &io).unwrap_err();
1711 match err {
1712 GenApiError::EnumValueUnknown { node, value } => {
1713 assert_eq!(node, "Mode");
1714 assert_eq!(value, 99);
1715 }
1716 other => panic!("unexpected error: {other:?}"),
1717 }
1718 }
1719
1720 #[test]
1721 fn enum_entries_are_sorted() {
1722 let nodemap = build_enum_pvalue_nodemap();
1723 let entries = nodemap.enum_entries("Mode").expect("entries");
1724 assert_eq!(
1725 entries,
1726 vec!["DynFromReg".to_string(), "Fixed10".to_string()]
1727 );
1728 }
1729
1730 #[test]
1731 fn bitfield_le_integer_roundtrip() {
1732 let mut nodemap = build_bitfield_nodemap();
1733 let io = MockIo::with_registers(&[(0x5000, vec![0xAA, 0xBB, 0xCC, 0xDD])]);
1734
1735 let value = nodemap
1736 .get_integer("LeByte", &io)
1737 .expect("read little-endian field");
1738 assert_eq!(value, 0xBB);
1739
1740 nodemap
1741 .set_integer("LeByte", 0x55, &io)
1742 .expect("write little-endian field");
1743 let data = io.read(0x5000, 4).expect("read back register");
1744 assert_eq!(data, vec![0xAA, 0x55, 0xCC, 0xDD]);
1745 }
1746
1747 #[test]
1748 fn bitfield_be_integer_roundtrip() {
1749 let mut nodemap = build_bitfield_nodemap();
1750 let io = MockIo::with_registers(&[(0x5004, vec![0b1010_0000, 0b0000_0000])]);
1751
1752 let value = nodemap
1753 .get_integer("BeBits", &io)
1754 .expect("read big-endian bits");
1755 assert_eq!(value, 0b101);
1756
1757 nodemap
1758 .set_integer("BeBits", 0b010, &io)
1759 .expect("write big-endian bits");
1760 let data = io.read(0x5004, 2).expect("read back register");
1761 assert_eq!(data, vec![0b0100_0000, 0b0000_0000]);
1762 }
1763
1764 #[test]
1765 fn bitfield_boolean_toggle() {
1766 let mut nodemap = build_bitfield_nodemap();
1767 let io = MockIo::with_registers(&[(0x5006, vec![0x00, 0x20, 0x00, 0x00])]);
1768
1769 assert!(nodemap.get_bool("PackedFlag", &io).expect("read flag"));
1770
1771 nodemap
1772 .set_bool("PackedFlag", false, &io)
1773 .expect("clear flag");
1774 let data = io.read(0x5006, 4).expect("read cleared");
1775 assert_eq!(data, vec![0x00, 0x00, 0x00, 0x00]);
1776
1777 nodemap.set_bool("PackedFlag", true, &io).expect("set flag");
1778 let data = io.read(0x5006, 4).expect("read set");
1779 assert_eq!(data, vec![0x00, 0x20, 0x00, 0x00]);
1780 }
1781
1782 #[test]
1783 fn bitfield_value_too_wide() {
1784 let mut nodemap = build_bitfield_nodemap();
1785 let io = MockIo::with_registers(&[(0x5004, vec![0x00, 0x00])]);
1786
1787 let err = nodemap
1788 .set_integer("BeBits", 8, &io)
1789 .expect_err("value too wide");
1790 match err {
1791 GenApiError::ValueTooWide {
1792 name, bit_length, ..
1793 } => {
1794 assert_eq!(name, "BeBits");
1795 assert_eq!(bit_length, 3);
1796 }
1797 other => panic!("unexpected error: {other:?}"),
1798 }
1799 }
1800 #[test]
1801 fn swissknife_evaluates_and_invalidates() {
1802 let mut nodemap = build_swissknife_nodemap();
1803 let io = MockIo::with_registers(&[
1804 (
1805 0x3000,
1806 i64_to_bytes("GainRaw", 100, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1807 ),
1808 (
1809 0x3008,
1810 i64_to_bytes("Offset", 3, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1811 ),
1812 (
1813 0x3010,
1814 i64_to_bytes("B", 1, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1815 ),
1816 ]);
1817
1818 let value = nodemap
1819 .get_float("ComputedGain", &io)
1820 .expect("compute gain");
1821 assert!((value - 53.0).abs() < 1e-6);
1822
1823 nodemap
1824 .set_integer("GainRaw", 120, &io)
1825 .expect("update raw gain");
1826 let updated = nodemap
1827 .get_float("ComputedGain", &io)
1828 .expect("recompute gain");
1829 assert!((updated - 63.0).abs() < 1e-6);
1830 }
1831
1832 #[test]
1833 fn swissknife_integer_rounding_and_unary() {
1834 let mut nodemap = build_swissknife_nodemap();
1835 let io = MockIo::with_registers(&[
1836 (
1837 0x3000,
1838 i64_to_bytes("GainRaw", 5, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1839 ),
1840 (
1841 0x3008,
1842 i64_to_bytes("Offset", 0, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1843 ),
1844 (
1845 0x3010,
1846 i64_to_bytes("B", 1, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1847 ),
1848 ]);
1849
1850 let divided = nodemap
1853 .get_integer("DivideInt", &io)
1854 .expect("integer division");
1855 assert_eq!(divided, 1);
1856
1857 nodemap
1858 .set_integer("GainRaw", 3, &io)
1859 .expect("update gain raw");
1860 let unary = nodemap.get_integer("Unary", &io).expect("unary expression");
1861 assert_eq!(unary, 7);
1862 }
1863
1864 #[test]
1865 fn swissknife_unknown_variable_error() {
1866 const XML: &str = r#"
1867 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1868 <Integer Name="A">
1869 <Address>0x2000</Address>
1870 <Length>4</Length>
1871 <AccessMode>RW</AccessMode>
1872 <Min>0</Min>
1873 <Max>100</Max>
1874 </Integer>
1875 <SwissKnife Name="Bad">
1876 <Expression>A + Missing</Expression>
1877 <pVariable Name="A">A</pVariable>
1878 </SwissKnife>
1879 </RegisterDescription>
1880 "#;
1881
1882 let model = viva_genapi_xml::parse(XML).expect("parse invalid swissknife");
1883 let nodemap = NodeMap::try_from_xml(model).expect("model builds despite the bad node");
1884
1885 let skipped = nodemap.skipped();
1888 assert_eq!(skipped.len(), 1, "expected exactly one dropped node");
1889 assert_eq!(skipped[0].name.as_deref(), Some("Bad"));
1890 assert!(
1891 skipped[0].error.contains("Missing"),
1892 "the record should name the unresolved variable: {}",
1893 skipped[0].error
1894 );
1895 assert!(nodemap.node("Bad").is_none());
1896
1897 assert!(nodemap.node("A").is_some());
1899 }
1900
1901 #[test]
1903 fn unparsable_formula_is_isolated() {
1904 const XML: &str = r#"
1905 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1906 <Integer Name="Width">
1907 <Address>0x2000</Address>
1908 <Length>4</Length>
1909 <AccessMode>RW</AccessMode>
1910 <Min>0</Min>
1911 <Max>4096</Max>
1912 </Integer>
1913 <IntSwissKnife Name="Broken">
1914 <Formula>Width +</Formula>
1915 <pVariable Name="Width">Width</pVariable>
1916 </IntSwissKnife>
1917 </RegisterDescription>
1918 "#;
1919
1920 let model = viva_genapi_xml::parse(XML).expect("parse model");
1921 let nodemap = NodeMap::try_from_xml(model).expect("model builds despite the bad formula");
1922 assert_eq!(nodemap.skipped().len(), 1);
1923 assert_eq!(nodemap.skipped()[0].name.as_deref(), Some("Broken"));
1924 assert!(nodemap.node("Width").is_some());
1925 }
1926
1927 #[test]
1928 fn swissknife_division_by_zero() {
1929 let nodemap = build_swissknife_nodemap();
1930 let io = MockIo::with_registers(&[
1931 (
1932 0x3000,
1933 i64_to_bytes("GainRaw", 10, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1934 ),
1935 (
1936 0x3008,
1937 i64_to_bytes("Offset", 0, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1938 ),
1939 (
1940 0x3010,
1941 i64_to_bytes("B", 0, 4, Sign::Signed, ByteOrder::Big).unwrap(),
1942 ),
1943 ]);
1944
1945 let err = nodemap
1946 .get_float("DivideByZero", &io)
1947 .expect_err("division by zero");
1948 match err {
1949 GenApiError::ExprEval { name, msg } => {
1950 assert_eq!(name, "DivideByZero");
1951 assert_eq!(msg, "division by zero");
1952 }
1953 other => panic!("unexpected error: {other:?}"),
1954 }
1955 }
1956
1957 const VISIBILITY_FIXTURE: &str = r#"
1962 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1963 <Integer Name="BeginnerNode">
1964 <Address>0x6000</Address>
1965 <Length>4</Length>
1966 <AccessMode>RW</AccessMode>
1967 <Visibility>Beginner</Visibility>
1968 <Min>0</Min>
1969 <Max>100</Max>
1970 </Integer>
1971 <Integer Name="ExpertNode">
1972 <Address>0x6010</Address>
1973 <Length>4</Length>
1974 <AccessMode>RW</AccessMode>
1975 <Visibility>Expert</Visibility>
1976 <Min>0</Min>
1977 <Max>100</Max>
1978 </Integer>
1979 <Integer Name="GuruNode">
1980 <Address>0x6020</Address>
1981 <Length>4</Length>
1982 <AccessMode>RW</AccessMode>
1983 <Visibility>Guru</Visibility>
1984 <Min>0</Min>
1985 <Max>100</Max>
1986 </Integer>
1987 <Integer Name="InvisibleNode">
1988 <Address>0x6030</Address>
1989 <Length>4</Length>
1990 <AccessMode>RW</AccessMode>
1991 <Visibility>Invisible</Visibility>
1992 <Min>0</Min>
1993 <Max>100</Max>
1994 </Integer>
1995 </RegisterDescription>
1996 "#;
1997
1998 #[test]
1999 fn nodes_at_visibility_beginner_returns_only_beginner() {
2000 let model = viva_genapi_xml::parse(VISIBILITY_FIXTURE).expect("parse visibility fixture");
2001 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
2002
2003 let visible = nodemap.nodes_at_visibility(Visibility::Beginner);
2004 assert!(
2005 visible.contains(&"BeginnerNode"),
2006 "Beginner node must be visible at Beginner level"
2007 );
2008 assert!(
2009 !visible.contains(&"ExpertNode"),
2010 "Expert node must NOT be visible at Beginner level"
2011 );
2012 assert!(
2013 !visible.contains(&"GuruNode"),
2014 "Guru node must NOT be visible at Beginner level"
2015 );
2016 assert!(
2017 !visible.contains(&"InvisibleNode"),
2018 "Invisible node must NOT be visible at Beginner level"
2019 );
2020 }
2021
2022 #[test]
2023 fn nodes_at_visibility_guru_includes_beginner_and_expert_but_not_invisible() {
2024 let model = viva_genapi_xml::parse(VISIBILITY_FIXTURE).expect("parse visibility fixture");
2025 let nodemap = NodeMap::try_from_xml(model).expect("build nodemap");
2026
2027 let visible = nodemap.nodes_at_visibility(Visibility::Guru);
2028 assert!(
2029 visible.contains(&"BeginnerNode"),
2030 "Beginner node must be visible at Guru level"
2031 );
2032 assert!(
2033 visible.contains(&"ExpertNode"),
2034 "Expert node must be visible at Guru level"
2035 );
2036 assert!(
2037 visible.contains(&"GuruNode"),
2038 "Guru node must be visible at Guru level"
2039 );
2040 assert!(
2041 !visible.contains(&"InvisibleNode"),
2042 "Invisible node must NOT be visible at Guru level"
2043 );
2044 }
2045}