1use std::collections::{HashMap, HashSet};
60use std::net::Ipv4Addr;
61use std::time::{Duration, Instant};
62
63use crate::gvcp_server::FAKE_MAC;
64
65pub const VERSION: u64 = 0x0000;
67pub const DEVICE_MODE: u64 = 0x0004;
68pub const DEVICE_MAC_HIGH: u64 = 0x0008;
70pub const DEVICE_MAC_LOW: u64 = 0x000C;
72pub const SUPPORTED_IP_CONFIG: u64 = 0x0010;
73pub const CURRENT_IP_CONFIG: u64 = 0x0014;
74pub const CURRENT_IP_ADDRESS: u64 = 0x0024;
75pub const CURRENT_SUBNET_MASK: u64 = 0x0034;
76pub const CURRENT_DEFAULT_GATEWAY: u64 = 0x0044;
77pub const NUMBER_OF_MESSAGE_CHANNELS: u64 = 0x0900;
79pub const NUMBER_OF_STREAM_CHANNELS: u64 = 0x0904;
81
82pub const DEVICE_MODE_VALUE: u32 = 0x8000_0001;
85pub const PERSISTENT_IP_ADDRESS: u64 = 0x064C;
86pub const PERSISTENT_SUBNET_MASK: u64 = 0x065C;
87pub const PERSISTENT_DEFAULT_GATEWAY: u64 = 0x066C;
88pub const CCP: u64 = 0x0a00;
89pub const HEARTBEAT_TIMEOUT: u64 = 0x0938;
90pub const MESSAGE_CHANNEL_PORT: u64 = 0x0b00;
92pub const MESSAGE_CHANNEL_ADDRESS: u64 = 0x0b10;
94pub const STREAM_CHANNEL_BASE: u64 = 0x0d00;
95pub const STREAM_CHANNEL_STRIDE: u64 = 0x40;
97pub const SCP_HOST_PORT: u64 = 0x00;
98pub const SCP_PACKET_SIZE: u64 = 0x04;
99pub const SCP_PACKET_DELAY: u64 = 0x08;
100pub const SCP_DEST_ADDR: u64 = 0x18;
101
102pub const FIRST_URL_REG: u64 = 0x0200;
104pub const URL_REG_LEN: usize = 512;
105
106pub const XML_BLOB_BASE: u64 = 0x1_0000;
108
109pub const REG_WIDTH: u64 = 0x20000;
113pub const REG_HEIGHT: u64 = 0x20004;
114pub const REG_PIXEL_FORMAT: u64 = 0x20008;
115pub const REG_OFFSET_X: u64 = 0x2000c;
116pub const REG_OFFSET_Y: u64 = 0x20010;
117pub const REG_SENSOR_WIDTH: u64 = 0x20014;
118pub const REG_SENSOR_HEIGHT: u64 = 0x20018;
119
120pub const REG_ACQ_MODE: u64 = 0x20020;
122pub const REG_ACQ_START: u64 = 0x20024;
123pub const REG_ACQ_STOP: u64 = 0x20028;
124pub const REG_ACQ_FRAME_RATE: u64 = 0x2002c;
125
126pub const REG_EXPOSURE_TIME: u64 = 0x20030;
128pub const REG_EXPOSURE_AUTO: u64 = 0x20038;
129pub const REG_GAIN: u64 = 0x20040;
130pub const REG_GAIN_AUTO: u64 = 0x20048;
131pub const REG_BLACK_LEVEL: u64 = 0x20050;
132
133pub const REG_ACQ_FRAME_RATE_ENABLE: u64 = 0x20054;
142pub const REG_SENSOR_TYPE: u64 = 0x20058;
143
144pub const REG_DEVICE_CAPS: u64 = 0x2005c;
148
149pub const REG_STREAM_CHANNEL_SELECTOR: u64 = 0x20090;
154
155pub const REG_TIMESTAMP_FREQ: u64 = 0x20060;
157pub const REG_TIMESTAMP_VALUE: u64 = 0x20068;
158pub const REG_TIMESTAMP_LATCH: u64 = 0x20070;
159
160pub const REG_CHUNK_MODE_ACTIVE: u64 = 0x20080;
162pub const REG_CHUNK_SELECTOR: u64 = 0x20084;
163pub const REG_CHUNK_ENABLE: u64 = 0x20088;
164
165pub const REG_EVENT_SELECTOR: u64 = 0x200a0;
167pub const REG_EVENT_NOTIFICATION: u64 = 0x200a4;
174
175pub const REG_WIDTH_MIN: u64 = 0x20100;
177pub const REG_WIDTH_MAX: u64 = 0x20104;
178pub const REG_HEIGHT_MIN: u64 = 0x20108;
179pub const REG_HEIGHT_MAX: u64 = 0x2010c;
180
181pub const REG_DEVICE_MODEL_NAME: u64 = 0x20200;
183pub const REG_DEVICE_VENDOR_NAME: u64 = 0x20220;
184pub const REG_DEVICE_SERIAL_NUMBER: u64 = 0x20240;
185pub const REG_DEVICE_FIRMWARE_VERSION: u64 = 0x20260;
186pub const REG_DEVICE_ID: u64 = 0x20280;
187
188pub const FAKE_XML: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
207<RegisterDescription
208 ModelName="VivaCam Fake"
209 VendorName="vitavision.dev"
210 ToolTip="Simulated GigE Vision camera for testing"
211 StandardNameSpace="GEV"
212 SchemaMajorVersion="1"
213 SchemaMinorVersion="1"
214 SchemaSubMinorVersion="0"
215 MajorVersion="1"
216 MinorVersion="0"
217 SubMinorVersion="0"
218 ProductGuid="76697661-6361-6d00-0000-000000000000"
219 VersionGuid="76697661-6361-6d00-0000-000000000001">
220
221 <!-- ════════════════════════════════════════════════════════════════════
222 Category Hierarchy (SFNC Standard)
223 ════════════════════════════════════════════════════════════════════ -->
224
225 <Category Name="Root" NameSpace="Standard">
226 <pFeature>DeviceControl</pFeature>
227 <pFeature>ImageFormatControl</pFeature>
228 <pFeature>AcquisitionControl</pFeature>
229 <pFeature>AnalogControl</pFeature>
230 <pFeature>TransportLayerControl</pFeature>
231 <pFeature>ChunkDataControl</pFeature>
232 </Category>
233
234 <Category Name="DeviceControl">
235 <DisplayName>Device Control</DisplayName>
236 <pFeature>DeviceVendorName</pFeature>
237 <pFeature>DeviceModelName</pFeature>
238 <pFeature>DeviceSerialNumber</pFeature>
239 <pFeature>DeviceFirmwareVersion</pFeature>
240 <pFeature>DeviceID</pFeature>
241 </Category>
242
243 <Category Name="ImageFormatControl">
244 <DisplayName>Image Format Control</DisplayName>
245 <pFeature>SensorType</pFeature>
246 <pFeature>SensorWidth</pFeature>
247 <pFeature>SensorHeight</pFeature>
248 <pFeature>Width</pFeature>
249 <pFeature>Height</pFeature>
250 <pFeature>OffsetX</pFeature>
251 <pFeature>OffsetY</pFeature>
252 <pFeature>PixelFormat</pFeature>
253 </Category>
254
255 <Category Name="AcquisitionControl">
256 <DisplayName>Acquisition Control</DisplayName>
257 <pFeature>AcquisitionMode</pFeature>
258 <pFeature>AcquisitionStart</pFeature>
259 <pFeature>AcquisitionStop</pFeature>
260 <pFeature>AcquisitionFrameRateEnable</pFeature>
261 <pFeature>AcquisitionFrameRate</pFeature>
262 <pFeature>ExposureTime</pFeature>
263 <pFeature>ExposureAuto</pFeature>
264 </Category>
265
266 <Category Name="AnalogControl">
267 <DisplayName>Analog Control</DisplayName>
268 <pFeature>Gain</pFeature>
269 <pFeature>GainAuto</pFeature>
270 <pFeature>BlackLevel</pFeature>
271 </Category>
272
273 <Category Name="TransportLayerControl">
274 <DisplayName>Transport Layer Control</DisplayName>
275 <pFeature>GevTimestampTickFrequency</pFeature>
276 <pFeature>GevTimestampValue</pFeature>
277 <pFeature>TimestampLatch</pFeature>
278 </Category>
279
280 <Category Name="ChunkDataControl">
281 <DisplayName>Chunk Data Control</DisplayName>
282 <pFeature>ChunkModeActive</pFeature>
283 <pFeature>ChunkSelector</pFeature>
284 <pFeature>ChunkEnable</pFeature>
285 </Category>
286
287 <Category Name="EventControl">
288 <DisplayName>Event Control</DisplayName>
289 <pFeature>EventSelector</pFeature>
290 <pFeature>EventNotification</pFeature>
291 </Category>
292
293 <!-- ════════════════════════════════════════════════════════════════════
294 Device Control Features
295 ════════════════════════════════════════════════════════════════════ -->
296
297 <String Name="DeviceVendorName" NameSpace="Standard">
298 <ToolTip>Name of the device vendor</ToolTip>
299 <Address>0x20220</Address>
300 <Length>32</Length>
301 <AccessMode>RO</AccessMode>
302 </String>
303
304 <String Name="DeviceModelName" NameSpace="Standard">
305 <ToolTip>Name of the device model</ToolTip>
306 <Address>0x20200</Address>
307 <Length>32</Length>
308 <AccessMode>RO</AccessMode>
309 </String>
310
311 <String Name="DeviceSerialNumber" NameSpace="Standard">
312 <ToolTip>Serial number of the device</ToolTip>
313 <Address>0x20240</Address>
314 <Length>16</Length>
315 <AccessMode>RO</AccessMode>
316 </String>
317
318 <String Name="DeviceFirmwareVersion" NameSpace="Standard">
319 <ToolTip>Firmware version of the device</ToolTip>
320 <Address>0x20260</Address>
321 <Length>32</Length>
322 <AccessMode>RO</AccessMode>
323 </String>
324
325 <String Name="DeviceID" NameSpace="Standard">
326 <ToolTip>User-configurable device identifier</ToolTip>
327 <Address>0x20280</Address>
328 <Length>32</Length>
329 <AccessMode>RO</AccessMode>
330 </String>
331
332 <!-- ════════════════════════════════════════════════════════════════════
333 Image Format Control Features
334 ════════════════════════════════════════════════════════════════════ -->
335
336 <Integer Name="SensorWidth" NameSpace="Standard">
337 <ToolTip>Physical sensor width in pixels</ToolTip>
338 <Address>0x20014</Address>
339 <Length>4</Length>
340 <AccessMode>RO</AccessMode>
341 <Min>1</Min>
342 <Max>4096</Max>
343 <Sign>Unsigned</Sign>
344 <Endianess>BigEndian</Endianess>
345 </Integer>
346
347 <Integer Name="SensorHeight" NameSpace="Standard">
348 <ToolTip>Physical sensor height in pixels</ToolTip>
349 <Address>0x20018</Address>
350 <Length>4</Length>
351 <AccessMode>RO</AccessMode>
352 <Min>1</Min>
353 <Max>4096</Max>
354 <Sign>Unsigned</Sign>
355 <Endianess>BigEndian</Endianess>
356 </Integer>
357
358 <Integer Name="Width" NameSpace="Standard">
359 <ToolTip>Width of the image in pixels</ToolTip>
360 <Address>0x20000</Address>
361 <Length>4</Length>
362 <AccessMode>RW</AccessMode>
363 <pMin>WidthMin</pMin>
364 <pMax>WidthMax</pMax>
365 <Sign>Unsigned</Sign>
366 <Endianess>BigEndian</Endianess>
367 </Integer>
368 <IntReg Name="WidthMin"><Address>0x20100</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
369 <IntReg Name="WidthMax"><Address>0x20104</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
370
371 <Integer Name="Height" NameSpace="Standard">
372 <ToolTip>Height of the image in pixels</ToolTip>
373 <Address>0x20004</Address>
374 <Length>4</Length>
375 <AccessMode>RW</AccessMode>
376 <pMin>HeightMin</pMin>
377 <pMax>HeightMax</pMax>
378 <Sign>Unsigned</Sign>
379 <Endianess>BigEndian</Endianess>
380 </Integer>
381 <IntReg Name="HeightMin"><Address>0x20108</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
382 <IntReg Name="HeightMax"><Address>0x2010c</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
383
384 <Integer Name="OffsetX" NameSpace="Standard">
385 <ToolTip>Horizontal offset from the sensor origin</ToolTip>
386 <Address>0x2000c</Address>
387 <Length>4</Length>
388 <AccessMode>RW</AccessMode>
389 <Min>0</Min>
390 <Max>4096</Max>
391 <Sign>Unsigned</Sign>
392 <Endianess>BigEndian</Endianess>
393 </Integer>
394
395 <Integer Name="OffsetY" NameSpace="Standard">
396 <ToolTip>Vertical offset from the sensor origin</ToolTip>
397 <Address>0x20010</Address>
398 <Length>4</Length>
399 <AccessMode>RW</AccessMode>
400 <Min>0</Min>
401 <Max>4096</Max>
402 <Sign>Unsigned</Sign>
403 <Endianess>BigEndian</Endianess>
404 </Integer>
405
406 <Enumeration Name="SensorType" NameSpace="Standard">
407 <ToolTip>Sensor variant (Monochrome / BayerRG / Color). On real hardware this would be read-only sensor metadata; here it is RW so tests can reconfigure the simulator.</ToolTip>
408 <EnumEntry Name="Monochrome"><Value>0</Value></EnumEntry>
409 <EnumEntry Name="BayerRG"><Value>1</Value></EnumEntry>
410 <EnumEntry Name="Color"><Value>2</Value></EnumEntry>
411 <pValue>SensorTypeReg</pValue>
412 </Enumeration>
413 <IntReg Name="SensorTypeReg"><Address>0x20058</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
414
415 <Enumeration Name="PixelFormat" NameSpace="Standard">
416 <ToolTip>Format of the pixel data — entries are gated by SensorType</ToolTip>
417 <EnumEntry Name="Mono8" NameSpace="Standard">
418 <Value>0x01080001</Value>
419 <pIsImplemented>PfMono8Avail</pIsImplemented>
420 </EnumEntry>
421 <EnumEntry Name="Mono16" NameSpace="Standard">
422 <Value>0x01100007</Value>
423 <pIsImplemented>PfMono16Avail</pIsImplemented>
424 </EnumEntry>
425 <EnumEntry Name="RGB8" NameSpace="Standard">
426 <Value>0x02180014</Value>
427 <pIsImplemented>PfRGB8Avail</pIsImplemented>
428 </EnumEntry>
429 <EnumEntry Name="BayerRG8" NameSpace="Standard">
430 <Value>0x01080009</Value>
431 <pIsImplemented>PfBayerRG8Avail</pIsImplemented>
432 </EnumEntry>
433 <pValue>PixelFormatReg</pValue>
434 </Enumeration>
435 <IntReg Name="PixelFormatReg"><Address>0x20008</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
436
437 <!-- PixelFormat entry availability driven by SensorType:
438 Monochrome (0) → Mono8, Mono16
439 BayerRG (1) → BayerRG8
440 Color (2) → RGB8 -->
441 <IntSwissKnife Name="PfMono8Avail">
442 <Formula>ST = 0</Formula>
443 <pVariable Name="ST">SensorTypeReg</pVariable>
444 </IntSwissKnife>
445 <IntSwissKnife Name="PfMono16Avail">
446 <Formula>ST = 0</Formula>
447 <pVariable Name="ST">SensorTypeReg</pVariable>
448 </IntSwissKnife>
449 <IntSwissKnife Name="PfBayerRG8Avail">
450 <Formula>ST = 1</Formula>
451 <pVariable Name="ST">SensorTypeReg</pVariable>
452 </IntSwissKnife>
453 <IntSwissKnife Name="PfRGB8Avail">
454 <Formula>ST = 2</Formula>
455 <pVariable Name="ST">SensorTypeReg</pVariable>
456 </IntSwissKnife>
457
458 <!-- ════════════════════════════════════════════════════════════════════
459 Device capability inquiry bits
460
461 Real inquiry blocks are addressed as `<pAddress>` (the block base) plus
462 a fixed `<Address>` offset, and the individual bits come from a
463 <StructReg>. Both terms contribute: keeping only one reads the wrong
464 register, which is what made issue #35's camera misreport every
465 capability it had.
466 ════════════════════════════════════════════════════════════════════ -->
467 <IntSwissKnife Name="DeviceRegBaseAddress">
468 <Formula>0x20000</Formula>
469 </IntSwissKnife>
470 <StructReg Comment="Device capability inquiry">
471 <pAddress>DeviceRegBaseAddress</pAddress>
472 <Address>0x5C</Address>
473 <Length>4</Length>
474 <AccessMode>RO</AccessMode>
475 <Endianess>BigEndian</Endianess>
476 <StructEntry Name="FrameRateControlInq_Bit"><Bit>0</Bit></StructEntry>
477 <StructEntry Name="ChunkSupportInq_Bit"><Bit>1</Bit></StructEntry>
478 <StructEntry Name="SequencerInq_Bit"><Bit>2</Bit></StructEntry>
479 </StructReg>
480
481 <!-- ════════════════════════════════════════════════════════════════════
482 GigE Vision stream channel registers
483
484 Stream channel N lives at 0x0D00 + N * 0x40, so the packet size
485 register is a fixed <Address> plus a <pIndex> scaled by the channel
486 stride. Ignoring the <pIndex> term always addresses channel 0.
487 ════════════════════════════════════════════════════════════════════ -->
488 <Integer Name="GevStreamChannelSelector" NameSpace="Standard">
489 <ToolTip>Stream channel the GevSCP* features address</ToolTip>
490 <Address>0x20090</Address>
491 <Length>4</Length>
492 <AccessMode>RW</AccessMode>
493 <Sign>Unsigned</Sign>
494 <Min>0</Min>
495 <Max>1</Max>
496 <Endianess>BigEndian</Endianess>
497 </Integer>
498 <Integer Name="GevSCPSPacketSize" NameSpace="Standard">
499 <ToolTip>Stream channel packet size in bytes</ToolTip>
500 <Address>0x0D04</Address>
501 <pIndex Offset="0x40">GevStreamChannelSelector</pIndex>
502 <Length>4</Length>
503 <AccessMode>RW</AccessMode>
504 <Sign>Unsigned</Sign>
505 <Min>0</Min>
506 <Max>65535</Max>
507 <Endianess>BigEndian</Endianess>
508 </Integer>
509
510 <!-- ════════════════════════════════════════════════════════════════════
511 Acquisition Control Features
512 ════════════════════════════════════════════════════════════════════ -->
513
514 <Enumeration Name="AcquisitionMode" NameSpace="Standard">
515 <ToolTip>Camera acquisition mode</ToolTip>
516 <EnumEntry Name="Continuous"><Value>0</Value></EnumEntry>
517 <EnumEntry Name="SingleFrame"><Value>1</Value></EnumEntry>
518 <EnumEntry Name="MultiFrame"><Value>2</Value></EnumEntry>
519 <pValue>AcquisitionModeReg</pValue>
520 </Enumeration>
521 <IntReg Name="AcquisitionModeReg"><Address>0x20020</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
522
523 <Command Name="AcquisitionStart" NameSpace="Standard">
524 <ToolTip>Start image acquisition</ToolTip>
525 <Address>0x20024</Address>
526 <Length>4</Length>
527 <AccessMode>WO</AccessMode>
528 <CommandValue>1</CommandValue>
529 <Endianess>BigEndian</Endianess>
530 </Command>
531
532 <Command Name="AcquisitionStop" NameSpace="Standard">
533 <ToolTip>Stop image acquisition</ToolTip>
534 <Address>0x20028</Address>
535 <Length>4</Length>
536 <AccessMode>WO</AccessMode>
537 <CommandValue>1</CommandValue>
538 <Endianess>BigEndian</Endianess>
539 </Command>
540
541 <Boolean Name="AcquisitionFrameRateEnable" NameSpace="Standard">
542 <ToolTip>Enable manual control of AcquisitionFrameRate. When false, the frame rate is unavailable for read/write.</ToolTip>
543 <pValue>AcquisitionFrameRateEnableReg</pValue>
544 </Boolean>
545 <IntReg Name="AcquisitionFrameRateEnableReg"><Address>0x20054</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
546 <pIsImplemented>FrameRateControlInq_Bit</pIsImplemented>
547 </IntReg>
548
549 <Float Name="AcquisitionFrameRate" NameSpace="Standard">
550 <ToolTip>Target frame rate in Hz</ToolTip>
551 <Address>0x2002c</Address>
552 <Length>4</Length>
553 <AccessMode>RW</AccessMode>
554 <Min>1.0</Min>
555 <Max>120.0</Max>
556 <Endianess>BigEndian</Endianess>
557 <pIsAvailable>AcquisitionFrameRateEnable</pIsAvailable>
558 </Float>
559
560 <Float Name="ExposureTime" NameSpace="Standard">
561 <ToolTip>Exposure time in microseconds — locked to RO when ExposureAuto is not Off</ToolTip>
562 <Address>0x20030</Address>
563 <Length>8</Length>
564 <AccessMode>RW</AccessMode>
565 <Min>10.0</Min>
566 <Max>1000000.0</Max>
567 <Endianess>BigEndian</Endianess>
568 <pIsLocked>ExposureAutoActive</pIsLocked>
569 </Float>
570
571 <Enumeration Name="ExposureAuto" NameSpace="Standard">
572 <ToolTip>Automatic exposure control</ToolTip>
573 <EnumEntry Name="Off"><Value>0</Value></EnumEntry>
574 <EnumEntry Name="Once"><Value>1</Value></EnumEntry>
575 <EnumEntry Name="Continuous"><Value>2</Value></EnumEntry>
576 <pValue>ExposureAutoReg</pValue>
577 </Enumeration>
578 <IntReg Name="ExposureAutoReg"><Address>0x20038</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
579
580 <IntSwissKnife Name="ExposureAutoActive">
581 <Formula>EA <> 0</Formula>
582 <pVariable Name="EA">ExposureAutoReg</pVariable>
583 </IntSwissKnife>
584
585 <!-- ════════════════════════════════════════════════════════════════════
586 Analog Control Features
587 ════════════════════════════════════════════════════════════════════ -->
588
589 <Float Name="Gain" NameSpace="Standard">
590 <!-- CDATA-wrapped tooltip, as shipped by several vendors: the literal `&`
591 and `<` inside are legal here and must survive parsing (issue #45). -->
592 <ToolTip><![CDATA[Gain applied to the image in dB — locked to RO when GainAuto is not Off (0 < gain & gain < 48)]]></ToolTip>
593 <Address>0x20040</Address>
594 <Length>8</Length>
595 <AccessMode>RW</AccessMode>
596 <Min>0.0</Min>
597 <Max>48.0</Max>
598 <Endianess>BigEndian</Endianess>
599 <pIsLocked>GainAutoActive</pIsLocked>
600 </Float>
601
602 <Enumeration Name="GainAuto" NameSpace="Standard">
603 <ToolTip>Automatic gain control</ToolTip>
604 <EnumEntry Name="Off"><Value>0</Value></EnumEntry>
605 <EnumEntry Name="Once"><Value>1</Value></EnumEntry>
606 <EnumEntry Name="Continuous"><Value>2</Value></EnumEntry>
607 <pValue>GainAutoReg</pValue>
608 </Enumeration>
609 <IntReg Name="GainAutoReg"><Address>0x20048</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
610
611 <IntSwissKnife Name="GainAutoActive">
612 <Formula>GA <> 0</Formula>
613 <pVariable Name="GA">GainAutoReg</pVariable>
614 </IntSwissKnife>
615
616 <Integer Name="BlackLevel" NameSpace="Standard">
617 <ToolTip>Analog black level offset</ToolTip>
618 <Address>0x20050</Address>
619 <Length>4</Length>
620 <AccessMode>RW</AccessMode>
621 <Min>0</Min>
622 <Max>255</Max>
623 <Sign>Unsigned</Sign>
624 <Endianess>BigEndian</Endianess>
625 </Integer>
626
627 <!-- ════════════════════════════════════════════════════════════════════
628 Transport Layer Control (Timestamp)
629 ════════════════════════════════════════════════════════════════════ -->
630
631 <Integer Name="GevTimestampTickFrequency" NameSpace="Standard">
632 <ToolTip>Device timestamp tick frequency in Hz (1 GHz)</ToolTip>
633 <Address>0x20060</Address>
634 <Length>4</Length>
635 <AccessMode>RO</AccessMode>
636 <Sign>Unsigned</Sign>
637 <Endianess>BigEndian</Endianess>
638 </Integer>
639
640 <Integer Name="GevTimestampValue" NameSpace="Standard">
641 <ToolTip>Current device timestamp in ticks (latched)</ToolTip>
642 <Address>0x20068</Address>
643 <Length>8</Length>
644 <AccessMode>RO</AccessMode>
645 <Sign>Unsigned</Sign>
646 <Endianess>BigEndian</Endianess>
647 </Integer>
648
649 <Command Name="TimestampLatch" NameSpace="Standard">
650 <ToolTip>Latch the current timestamp into GevTimestampValue</ToolTip>
651 <Address>0x20070</Address>
652 <Length>4</Length>
653 <AccessMode>WO</AccessMode>
654 <CommandValue>1</CommandValue>
655 <Endianess>BigEndian</Endianess>
656 </Command>
657
658 <!-- ════════════════════════════════════════════════════════════════════
659 Chunk Data Control
660 ════════════════════════════════════════════════════════════════════ -->
661
662 <!-- SFNC defines ChunkModeActive and ChunkEnable as IBoolean, and all 23
663 vendor-corpus documents that declare ChunkModeActive use <Boolean> over
664 a backing <IntReg> - none uses <Integer>. Declaring them as <Integer>
665 here made Camera::configure_chunks, which correctly calls set_bool,
666 fail against the only camera we can test with. -->
667 <Boolean Name="ChunkModeActive" NameSpace="Standard">
668 <ToolTip>Enable chunk data in image frames</ToolTip>
669 <pValue>ChunkModeActiveReg</pValue>
670 </Boolean>
671 <IntReg Name="ChunkModeActiveReg"><Address>0x20080</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
672
673 <Enumeration Name="ChunkSelector" NameSpace="Standard">
674 <ToolTip>Select which chunk feature to configure</ToolTip>
675 <EnumEntry Name="Timestamp"><Value>1</Value></EnumEntry>
676 <EnumEntry Name="ExposureTime"><Value>2</Value></EnumEntry>
677 <EnumEntry Name="Gain"><Value>3</Value></EnumEntry>
678 <pValue>ChunkSelectorReg</pValue>
679 </Enumeration>
680 <IntReg Name="ChunkSelectorReg"><Address>0x20084</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
681
682 <Boolean Name="ChunkEnable" NameSpace="Standard">
683 <ToolTip>Enable the selected chunk feature</ToolTip>
684 <pValue>ChunkEnableReg</pValue>
685 </Boolean>
686 <IntReg Name="ChunkEnableReg"><Address>0x20088</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
687
688 <!-- Event delivery is selected through GenApi, not through a bootstrap
689 register: EventSelector picks a GigE Vision event id and
690 EventNotification turns it on. The ids are the standard ones
691 (GEV_EVENT_START_OF_TRANSFER = 0x0005, END_OF_TRANSFER = 0x0006). -->
692 <Enumeration Name="EventSelector" NameSpace="Standard">
693 <ToolTip>Select which event to configure</ToolTip>
694 <EnumEntry Name="StartOfTransfer"><Value>5</Value></EnumEntry>
695 <EnumEntry Name="EndOfTransfer"><Value>6</Value></EnumEntry>
696 <pValue>EventSelectorReg</pValue>
697 </Enumeration>
698 <IntReg Name="EventSelectorReg"><Address>0x200A0</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
699
700 <Enumeration Name="EventNotification" NameSpace="Standard">
701 <ToolTip>Enable notification for the selected event</ToolTip>
702 <EnumEntry Name="Off"><Value>0</Value></EnumEntry>
703 <EnumEntry Name="On"><Value>1</Value></EnumEntry>
704 <pValue>EventNotificationReg</pValue>
705 </Enumeration>
706 <IntReg Name="EventNotificationReg"><Address>0x200A4</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
707
708</RegisterDescription>
709"#;
710
711pub struct RegisterMap {
718 regs: HashMap<u64, Vec<u8>>,
719 xml_blob: Vec<u8>,
720 clock_origin: Instant,
721 enabled_events: HashSet<u16>,
728 last_register_command: Instant,
730 enforce_heartbeat: bool,
732 max_packet_size: Option<u32>,
746 max_on_wire: Option<u32>,
754 pending_test_packet: Option<u32>,
757}
758
759pub const SCPS_FIRE_TEST_PACKET: u32 = 0x8000_0000;
761pub const SCPS_DO_NOT_FRAGMENT: u32 = 0x4000_0000;
763pub const STREAM_PACKET_SIZE_MASK: u32 = 0xFFFF;
765
766fn zip_xml_blob(xml: &[u8]) -> Vec<u8> {
768 use std::io::Write;
769 let mut writer = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
770 let options = zip::write::SimpleFileOptions::default()
771 .compression_method(zip::CompressionMethod::Deflated);
772 writer
773 .start_file("fake.xml", options)
774 .expect("start XML zip entry");
775 writer.write_all(xml).expect("write XML zip entry");
776 writer.finish().expect("finish XML zip").into_inner()
777}
778
779impl RegisterMap {
780 pub fn new(width: u32, height: u32, pixel_format: u32, zip_xml: bool) -> Self {
787 let mut regs = HashMap::new();
788
789 regs.insert(VERSION, 0x0002_0000u32.to_be_bytes().to_vec()); regs.insert(DEVICE_MODE, DEVICE_MODE_VALUE.to_be_bytes().to_vec());
796 regs.insert(
797 DEVICE_MAC_HIGH,
798 u32::from(u16::from_be_bytes([FAKE_MAC[0], FAKE_MAC[1]]))
799 .to_be_bytes()
800 .to_vec(),
801 );
802 regs.insert(
803 DEVICE_MAC_LOW,
804 u32::from_be_bytes([FAKE_MAC[2], FAKE_MAC[3], FAKE_MAC[4], FAKE_MAC[5]])
805 .to_be_bytes()
806 .to_vec(),
807 );
808 regs.insert(SUPPORTED_IP_CONFIG, 0x8000_0007u32.to_be_bytes().to_vec());
810 regs.insert(CURRENT_IP_ADDRESS, Ipv4Addr::LOCALHOST.octets().to_vec());
811 regs.insert(CURRENT_SUBNET_MASK, [255, 0, 0, 0].to_vec());
812 regs.insert(CURRENT_DEFAULT_GATEWAY, vec![0, 0, 0, 0]);
813 regs.insert(NUMBER_OF_MESSAGE_CHANNELS, 1u32.to_be_bytes().to_vec());
814 regs.insert(NUMBER_OF_STREAM_CHANNELS, 1u32.to_be_bytes().to_vec());
815 regs.insert(CCP, vec![0, 0, 0, 0]);
816 regs.insert(HEARTBEAT_TIMEOUT, 3000u32.to_be_bytes().to_vec());
817 regs.insert(MESSAGE_CHANNEL_PORT, 0u32.to_be_bytes().to_vec());
818 regs.insert(MESSAGE_CHANNEL_ADDRESS, vec![0, 0, 0, 0]);
819
820 regs.insert(CURRENT_IP_CONFIG, 0x0000_0005u32.to_be_bytes().to_vec());
822 regs.insert(PERSISTENT_IP_ADDRESS, vec![0, 0, 0, 0]);
823 regs.insert(PERSISTENT_SUBNET_MASK, vec![0, 0, 0, 0]);
824 regs.insert(PERSISTENT_DEFAULT_GATEWAY, vec![0, 0, 0, 0]);
825
826 let base = STREAM_CHANNEL_BASE;
828 regs.insert(base + SCP_HOST_PORT, vec![0, 0, 0, 0]);
829 regs.insert(base + SCP_PACKET_SIZE, 1500u32.to_be_bytes().to_vec());
830 regs.insert(
833 base + STREAM_CHANNEL_STRIDE + SCP_PACKET_SIZE,
834 9000u32.to_be_bytes().to_vec(),
835 );
836 regs.insert(REG_STREAM_CHANNEL_SELECTOR, 0u32.to_be_bytes().to_vec());
837 regs.insert(base + SCP_PACKET_DELAY, vec![0, 0, 0, 0]);
838 regs.insert(base + SCP_DEST_ADDR, vec![0, 0, 0, 0]);
839
840 regs.insert(REG_DEVICE_MODEL_NAME, pad_string("VivaCam Fake", 32));
842 regs.insert(REG_DEVICE_VENDOR_NAME, pad_string("vitavision.dev", 32));
843 regs.insert(REG_DEVICE_SERIAL_NUMBER, pad_string("VIVA-FAKE-001", 16));
844 regs.insert(REG_DEVICE_FIRMWARE_VERSION, pad_string("1.0.0-fake", 32));
845 regs.insert(REG_DEVICE_ID, pad_string("VivaCam-0", 32));
846
847 regs.insert(REG_WIDTH, width.to_be_bytes().to_vec());
849 regs.insert(REG_HEIGHT, height.to_be_bytes().to_vec());
850 regs.insert(REG_PIXEL_FORMAT, pixel_format.to_be_bytes().to_vec());
851 regs.insert(REG_OFFSET_X, 0u32.to_be_bytes().to_vec());
852 regs.insert(REG_OFFSET_Y, 0u32.to_be_bytes().to_vec());
853 regs.insert(REG_SENSOR_WIDTH, 4096u32.to_be_bytes().to_vec());
854 regs.insert(REG_SENSOR_HEIGHT, 4096u32.to_be_bytes().to_vec());
855
856 regs.insert(REG_WIDTH_MIN, 16u32.to_be_bytes().to_vec());
858 regs.insert(REG_WIDTH_MAX, 4096u32.to_be_bytes().to_vec());
859 regs.insert(REG_HEIGHT_MIN, 16u32.to_be_bytes().to_vec());
860 regs.insert(REG_HEIGHT_MAX, 4096u32.to_be_bytes().to_vec());
861
862 regs.insert(REG_ACQ_MODE, 0u32.to_be_bytes().to_vec()); regs.insert(REG_ACQ_START, vec![0, 0, 0, 0]);
865 regs.insert(REG_ACQ_STOP, vec![0, 0, 0, 0]);
866 regs.insert(REG_ACQ_FRAME_RATE, 30.0f32.to_be_bytes().to_vec());
867 regs.insert(REG_EXPOSURE_TIME, 5000.0f64.to_be_bytes().to_vec());
868 regs.insert(REG_EXPOSURE_AUTO, 0u32.to_be_bytes().to_vec()); regs.insert(REG_GAIN, 0.0f64.to_be_bytes().to_vec());
872 regs.insert(REG_GAIN_AUTO, 0u32.to_be_bytes().to_vec()); regs.insert(REG_BLACK_LEVEL, 0u32.to_be_bytes().to_vec());
874
875 regs.insert(REG_ACQ_FRAME_RATE_ENABLE, 1u32.to_be_bytes().to_vec());
880 regs.insert(REG_SENSOR_TYPE, 0u32.to_be_bytes().to_vec());
881 regs.insert(REG_DEVICE_CAPS, 0xC000_0000u32.to_be_bytes().to_vec());
884
885 regs.insert(REG_TIMESTAMP_FREQ, 1_000_000_000u32.to_be_bytes().to_vec());
887 regs.insert(REG_TIMESTAMP_VALUE, vec![0u8; 8]);
888 regs.insert(REG_TIMESTAMP_LATCH, vec![0, 0, 0, 0]);
889
890 regs.insert(REG_CHUNK_MODE_ACTIVE, 0u32.to_be_bytes().to_vec());
892 regs.insert(REG_CHUNK_SELECTOR, 1u32.to_be_bytes().to_vec()); regs.insert(REG_CHUNK_ENABLE, 0u32.to_be_bytes().to_vec());
894 regs.insert(REG_EVENT_SELECTOR, 5u32.to_be_bytes().to_vec());
895
896 let (xml_blob, xml_name) = if zip_xml {
898 (zip_xml_blob(FAKE_XML.as_bytes()), "fake.zip")
899 } else {
900 (FAKE_XML.as_bytes().to_vec(), "fake.xml")
901 };
902 let url = format!(
903 "Local:{xml_name};{:x};{:x}\0",
904 XML_BLOB_BASE,
905 xml_blob.len()
906 );
907 let mut url_bytes = vec![0u8; URL_REG_LEN];
908 let src = url.as_bytes();
909 url_bytes[..src.len()].copy_from_slice(src);
910 regs.insert(FIRST_URL_REG, url_bytes);
911
912 Self {
913 regs,
914 xml_blob,
915 clock_origin: Instant::now(),
916 enabled_events: HashSet::new(),
917 last_register_command: Instant::now(),
918 enforce_heartbeat: false,
919 max_packet_size: None,
920 max_on_wire: None,
921 pending_test_packet: None,
922 }
923 }
924
925 pub fn enforce_heartbeat(&mut self, enable: bool) {
939 self.enforce_heartbeat = enable;
940 self.last_register_command = Instant::now();
941 }
942
943 pub fn set_max_on_wire(&mut self, max: u32) {
954 self.max_on_wire = Some(max);
955 }
956
957 pub fn max_on_wire(&self) -> Option<u32> {
959 self.max_on_wire
960 }
961
962 pub fn take_pending_test_packet(&mut self) -> Option<u32> {
964 self.pending_test_packet.take()
965 }
966
967 pub fn set_max_packet_size(&mut self, max: u32) {
968 self.max_packet_size = Some(max);
969 let current = self.stream_packet_size();
972 if current > max {
973 self.write(STREAM_CHANNEL_BASE + SCP_PACKET_SIZE, &max.to_be_bytes());
974 }
975 }
976
977 pub fn note_register_command(&mut self) -> bool {
985 let elapsed = self.last_register_command.elapsed();
986 self.last_register_command = Instant::now();
987 if !self.enforce_heartbeat {
988 return false;
989 }
990 let timeout = Duration::from_millis(u64::from(self.heartbeat_timeout_ms()));
991 if timeout.is_zero() || elapsed <= timeout {
992 return false;
993 }
994 let ccp = self.read(CCP, 4);
995 if u32::from_be_bytes([ccp[0], ccp[1], ccp[2], ccp[3]]) == 0 {
996 return false;
997 }
998 self.write(CCP, &0u32.to_be_bytes());
999 true
1000 }
1001
1002 pub fn set_heartbeat_timeout_ms(&mut self, timeout_ms: u32) {
1008 self.write(HEARTBEAT_TIMEOUT, &timeout_ms.to_be_bytes());
1009 }
1010
1011 pub fn heartbeat_timeout_ms(&self) -> u32 {
1013 let data = self.read(HEARTBEAT_TIMEOUT, 4);
1014 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1015 }
1016
1017 pub fn read(&self, addr: u64, len: usize) -> Vec<u8> {
1019 if addr >= XML_BLOB_BASE {
1021 let offset = (addr - XML_BLOB_BASE) as usize;
1022 if offset < self.xml_blob.len() {
1023 let end = (offset + len).min(self.xml_blob.len());
1024 let mut result = self.xml_blob[offset..end].to_vec();
1025 result.resize(len, 0);
1026 return result;
1027 }
1028 }
1029
1030 if addr == REG_EVENT_NOTIFICATION {
1032 let on = u32::from(self.event_notification_on(self.event_selector()));
1033 let mut result = on.to_be_bytes().to_vec();
1034 result.resize(len, 0);
1035 result.truncate(len);
1036 return result;
1037 }
1038
1039 if let Some(data) = self.regs.get(&addr) {
1041 let mut result = data.clone();
1042 result.resize(len, 0);
1043 result.truncate(len);
1044 return result;
1045 }
1046
1047 for (®_addr, data) in &self.regs {
1049 if addr >= reg_addr && (addr - reg_addr) < data.len() as u64 {
1050 let offset = (addr - reg_addr) as usize;
1051 let end = (offset + len).min(data.len());
1052 let mut result = data[offset..end].to_vec();
1053 result.resize(len, 0);
1054 return result;
1055 }
1056 }
1057
1058 vec![0u8; len]
1059 }
1060
1061 pub fn write(&mut self, addr: u64, data: &[u8]) {
1063 let stripped;
1069 let mut data = data;
1070 if addr == STREAM_CHANNEL_BASE + SCP_PACKET_SIZE && data.len() >= 4 {
1071 let raw = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
1072 self.pending_test_packet =
1073 (raw & SCPS_FIRE_TEST_PACKET != 0).then_some(raw & STREAM_PACKET_SIZE_MASK);
1074 if raw & (SCPS_FIRE_TEST_PACKET | SCPS_DO_NOT_FRAGMENT) != 0 {
1075 stripped = (raw & STREAM_PACKET_SIZE_MASK).to_be_bytes();
1076 data = &stripped[..];
1077 }
1078 }
1079
1080 let clamped;
1083 let data = match self.max_packet_size {
1084 Some(max)
1085 if addr == STREAM_CHANNEL_BASE + SCP_PACKET_SIZE
1086 && data.len() >= 4
1087 && u32::from_be_bytes([data[0], data[1], data[2], data[3]]) > max =>
1088 {
1089 clamped = max.to_be_bytes();
1090 &clamped[..]
1091 }
1092 _ => data,
1093 };
1094
1095 if addr == REG_EVENT_NOTIFICATION && data.len() >= 4 {
1099 let event = self.event_selector();
1100 if u32::from_be_bytes([data[0], data[1], data[2], data[3]]) != 0 {
1101 self.enabled_events.insert(event);
1102 } else {
1103 self.enabled_events.remove(&event);
1104 }
1105 return;
1106 }
1107
1108 if let Some(existing) = self.regs.get_mut(&addr) {
1109 let len = existing.len().min(data.len());
1110 existing[..len].copy_from_slice(&data[..len]);
1111 return;
1112 }
1113
1114 let addrs: Vec<u64> = self.regs.keys().copied().collect();
1116 for reg_addr in addrs {
1117 let reg_data = self.regs.get(®_addr).unwrap();
1118 if addr >= reg_addr && (addr - reg_addr) < reg_data.len() as u64 {
1119 let offset = (addr - reg_addr) as usize;
1120 let end = (offset + data.len()).min(reg_data.len());
1121 let reg_data = self.regs.get_mut(®_addr).unwrap();
1122 reg_data[offset..end].copy_from_slice(&data[..end - offset]);
1123 return;
1124 }
1125 }
1126
1127 self.regs.insert(addr, data.to_vec());
1128 }
1129
1130 pub fn handle_special_write(&mut self, addr: u64) {
1132 if addr == REG_TIMESTAMP_LATCH {
1133 let ts = self.device_timestamp();
1134 self.regs
1135 .insert(REG_TIMESTAMP_VALUE, ts.to_be_bytes().to_vec());
1136 }
1137 }
1138
1139 pub fn device_timestamp(&self) -> u64 {
1143 self.clock_origin.elapsed().as_nanos() as u64
1144 }
1145
1146 pub fn stream_dest_ip(&self) -> Ipv4Addr {
1148 let data = self.read(STREAM_CHANNEL_BASE + SCP_DEST_ADDR, 4);
1149 Ipv4Addr::new(data[0], data[1], data[2], data[3])
1150 }
1151
1152 pub fn stream_dest_port(&self) -> u16 {
1154 let data = self.read(STREAM_CHANNEL_BASE + SCP_HOST_PORT, 4);
1155 u16::from_be_bytes([data[2], data[3]])
1156 }
1157
1158 pub fn message_dest_ip(&self) -> Ipv4Addr {
1160 let data = self.read(MESSAGE_CHANNEL_ADDRESS, 4);
1161 Ipv4Addr::new(data[0], data[1], data[2], data[3])
1162 }
1163
1164 pub fn message_dest_port(&self) -> u16 {
1166 let data = self.read(MESSAGE_CHANNEL_PORT, 4);
1167 u16::from_be_bytes([data[2], data[3]])
1168 }
1169
1170 pub fn event_selector(&self) -> u16 {
1172 let data = self.read(REG_EVENT_SELECTOR, 4);
1173 u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as u16
1174 }
1175
1176 pub fn event_notification_on(&self, event_id: u16) -> bool {
1182 self.enabled_events.contains(&event_id)
1183 }
1184
1185 pub fn stream_packet_size(&self) -> u32 {
1187 let data = self.read(STREAM_CHANNEL_BASE + SCP_PACKET_SIZE, 4);
1188 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1189 }
1190
1191 pub fn width(&self) -> u32 {
1193 let data = self.read(REG_WIDTH, 4);
1194 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1195 }
1196
1197 pub fn height(&self) -> u32 {
1199 let data = self.read(REG_HEIGHT, 4);
1200 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1201 }
1202
1203 pub fn pixel_format_code(&self) -> u32 {
1205 let data = self.read(REG_PIXEL_FORMAT, 4);
1206 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1207 }
1208
1209 pub fn chunk_mode_active(&self) -> bool {
1211 let data = self.read(REG_CHUNK_MODE_ACTIVE, 4);
1212 u32::from_be_bytes([data[0], data[1], data[2], data[3]]) != 0
1213 }
1214
1215 pub fn exposure_time(&self) -> f64 {
1217 let data = self.read(REG_EXPOSURE_TIME, 8);
1218 f64::from_be_bytes([
1219 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1220 ])
1221 }
1222}
1223
1224fn pad_string(s: &str, len: usize) -> Vec<u8> {
1226 let mut buf = vec![0u8; len];
1227 let src = s.as_bytes();
1228 let copy_len = src.len().min(len);
1229 buf[..copy_len].copy_from_slice(&src[..copy_len]);
1230 buf
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235 use super::*;
1236
1237 fn map() -> RegisterMap {
1238 RegisterMap::new(64, 48, 0x0108_0001, false)
1239 }
1240
1241 fn select_and_enable(regs: &mut RegisterMap, event: u32, on: bool) {
1242 regs.write(REG_EVENT_SELECTOR, &event.to_be_bytes());
1243 regs.write(REG_EVENT_NOTIFICATION, &u32::from(on).to_be_bytes());
1244 }
1245
1246 #[test]
1251 fn enabling_a_second_event_leaves_the_first_enabled() {
1252 let mut regs = map();
1253 select_and_enable(&mut regs, 5, true);
1254 select_and_enable(&mut regs, 6, true);
1255 assert!(regs.event_notification_on(5));
1256 assert!(regs.event_notification_on(6));
1257 }
1258
1259 #[test]
1260 fn disabling_one_event_leaves_the_others_alone() {
1261 let mut regs = map();
1262 select_and_enable(&mut regs, 5, true);
1263 select_and_enable(&mut regs, 6, true);
1264 select_and_enable(&mut regs, 5, false);
1265 assert!(!regs.event_notification_on(5));
1266 assert!(regs.event_notification_on(6));
1267 }
1268
1269 #[test]
1273 fn the_bootstrap_mac_matches_the_discovery_mac() {
1274 let regs = map();
1275 let high = regs.read(DEVICE_MAC_HIGH, 4);
1276 let low = regs.read(DEVICE_MAC_LOW, 4);
1277 assert_eq!(&high[..2], &[0, 0], "top two bytes are reserved");
1278 let mac = [high[2], high[3], low[0], low[1], low[2], low[3]];
1279 assert_eq!(mac, FAKE_MAC);
1280 }
1281
1282 #[test]
1283 fn the_device_reports_one_channel_of_each_kind() {
1284 let regs = map();
1285 assert_eq!(regs.read(NUMBER_OF_MESSAGE_CHANNELS, 4), vec![0, 0, 0, 1]);
1286 assert_eq!(regs.read(NUMBER_OF_STREAM_CHANNELS, 4), vec![0, 0, 0, 1]);
1287 }
1288
1289 #[test]
1290 fn no_event_is_enabled_by_default() {
1291 let regs = map();
1292 assert!(!regs.event_notification_on(5));
1293 assert!(!regs.event_notification_on(6));
1294 }
1295
1296 #[test]
1299 fn reading_notification_follows_the_selector() {
1300 let mut regs = map();
1301 select_and_enable(&mut regs, 5, true);
1302 regs.write(REG_EVENT_SELECTOR, &6u32.to_be_bytes());
1303 assert_eq!(regs.read(REG_EVENT_NOTIFICATION, 4), vec![0, 0, 0, 0]);
1304 regs.write(REG_EVENT_SELECTOR, &5u32.to_be_bytes());
1305 assert_eq!(regs.read(REG_EVENT_NOTIFICATION, 4), vec![0, 0, 0, 1]);
1306 }
1307}