Skip to main content

canopen_rs/
lss.rs

1//! Layer Setting Services (LSS, CiA 305) — configure a node's node-id and bit
2//! timing over the bus.
3//!
4//! A node that ships without a preset node-id comes up *unconfigured* and
5//! answers only LSS. An LSS master switches it into the *configuration* state —
6//! globally, or selectively by matching its 128-bit [`LssAddress`] (the object
7//! `0x1018` identity) — then assigns a node-id and asks it to store the setting.
8//! When the master does not know the address, [`FastscanMaster`] discovers it
9//! by bisecting the identity bit by bit (CiA 305 §3.7).
10//!
11//! LSS uses two fixed COB-IDs: master→slave on [`LSS_MASTER_COB_ID`] (`0x7E5`)
12//! and slave→master on [`LSS_SLAVE_COB_ID`] (`0x7E4`). Every frame is eight
13//! bytes: a command specifier in byte 0, then service data.
14//!
15//! [`LssSlave`] is the node-side state machine, sans-I/O like the rest of the
16//! stack; the `encode_*` / `decode_*` functions build and read the master's
17//! frames.
18//!
19//! ```
20//! use canopen_rs::lss::{encode_configure_node_id, encode_switch_global, LssAddress, LssSlave, LssState};
21//!
22//! let address = LssAddress { vendor_id: 0x1F, product_code: 0x2A, revision_number: 1, serial_number: 0x1234 };
23//! let mut slave = LssSlave::new(address, 0xFF); // 0xFF = unconfigured
24//!
25//! // Master switches all nodes into configuration, then assigns node-id 0x20.
26//! slave.handle(&encode_switch_global(true));
27//! assert_eq!(slave.state(), LssState::Configuration);
28//!
29//! let response = slave.handle(&encode_configure_node_id(0x20)).unwrap();
30//! assert_eq!(response, [0x11, 0x00, 0, 0, 0, 0, 0, 0]); // success
31//! slave.adopt_pending(); // applied on the node's next reset
32//! assert_eq!(slave.node_id(), 0x20);
33//! ```
34
35/// The LSS master→slave request channel (`0x7E5`).
36pub const LSS_MASTER_COB_ID: u16 = 0x7E5;
37/// The LSS slave→master response channel (`0x7E4`).
38pub const LSS_SLAVE_COB_ID: u16 = 0x7E4;
39
40/// The eight-byte LSS message data field.
41pub type LssFrame = [u8; 8];
42
43/// The node-id value denoting an unconfigured node.
44pub const UNCONFIGURED_NODE_ID: u8 = 0xFF;
45
46// --- Command specifiers (byte 0) -------------------------------------------
47const CS_SWITCH_GLOBAL: u8 = 0x04;
48const CS_CONFIGURE_NODE_ID: u8 = 0x11;
49const CS_STORE_CONFIGURATION: u8 = 0x17;
50const CS_SWITCH_SELECTIVE_VENDOR: u8 = 0x40;
51const CS_SWITCH_SELECTIVE_PRODUCT: u8 = 0x41;
52const CS_SWITCH_SELECTIVE_REVISION: u8 = 0x42;
53const CS_SWITCH_SELECTIVE_SERIAL: u8 = 0x43;
54const CS_SWITCH_SELECTIVE_RESPONSE: u8 = 0x44;
55const CS_INQUIRE_VENDOR: u8 = 0x5A;
56const CS_INQUIRE_PRODUCT: u8 = 0x5B;
57const CS_INQUIRE_REVISION: u8 = 0x5C;
58const CS_INQUIRE_SERIAL: u8 = 0x5D;
59const CS_INQUIRE_NODE_ID: u8 = 0x5E;
60const CS_IDENTIFY_SLAVE: u8 = 0x4F;
61const CS_FASTSCAN: u8 = 0x51;
62
63// Switch-state-global modes (byte 1).
64const MODE_WAITING: u8 = 0;
65const MODE_CONFIGURATION: u8 = 1;
66
67/// The `BitCheck` value (byte 5) that resets/starts a Fastscan cycle: no bits
68/// are checked, so every unconfigured node answers. Bit checks otherwise run
69/// `31..=0`.
70pub const FASTSCAN_INIT: u8 = 32;
71
72/// A node's 128-bit LSS address — the identity object (`0x1018`) that
73/// uniquely selects it on the bus.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct LssAddress {
76    /// Vendor id (`0x1018` sub 1).
77    pub vendor_id: u32,
78    /// Product code (`0x1018` sub 2).
79    pub product_code: u32,
80    /// Revision number (`0x1018` sub 3).
81    pub revision_number: u32,
82    /// Serial number (`0x1018` sub 4).
83    pub serial_number: u32,
84}
85
86/// The LSS state of a node.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum LssState {
89    /// Waiting — ignores configuration services until switched.
90    Waiting,
91    /// Configuration — accepts node-id/bit-timing/store services.
92    Configuration,
93}
94
95fn frame(cs: u8) -> LssFrame {
96    let mut f = [0u8; 8];
97    f[0] = cs;
98    f
99}
100
101fn frame_u32(cs: u8, value: u32) -> LssFrame {
102    let mut f = frame(cs);
103    f[1..5].copy_from_slice(&value.to_le_bytes());
104    f
105}
106
107fn u32_at(f: &LssFrame) -> u32 {
108    u32::from_le_bytes([f[1], f[2], f[3], f[4]])
109}
110
111// --- Master-side encoders --------------------------------------------------
112
113/// Switch every node globally to the configuration (`true`) or waiting
114/// (`false`) state.
115pub fn encode_switch_global(configuration: bool) -> LssFrame {
116    let mut f = frame(CS_SWITCH_GLOBAL);
117    f[1] = if configuration {
118        MODE_CONFIGURATION
119    } else {
120        MODE_WAITING
121    };
122    f
123}
124
125/// The four-message sequence that selectively switches the node matching
126/// `address` into the configuration state.
127pub fn encode_switch_selective(address: &LssAddress) -> [LssFrame; 4] {
128    [
129        frame_u32(CS_SWITCH_SELECTIVE_VENDOR, address.vendor_id),
130        frame_u32(CS_SWITCH_SELECTIVE_PRODUCT, address.product_code),
131        frame_u32(CS_SWITCH_SELECTIVE_REVISION, address.revision_number),
132        frame_u32(CS_SWITCH_SELECTIVE_SERIAL, address.serial_number),
133    ]
134}
135
136/// Assign `node_id` to the node currently in the configuration state.
137pub fn encode_configure_node_id(node_id: u8) -> LssFrame {
138    let mut f = frame(CS_CONFIGURE_NODE_ID);
139    f[1] = node_id;
140    f
141}
142
143/// Ask the configured node to store its configuration to non-volatile memory.
144pub fn encode_store() -> LssFrame {
145    frame(CS_STORE_CONFIGURATION)
146}
147
148/// Inquire the configured node's active node-id.
149pub fn encode_inquire_node_id() -> LssFrame {
150    frame(CS_INQUIRE_NODE_ID)
151}
152
153// --- Master-side response decoders -----------------------------------------
154
155/// Whether `response` is a selective-switch confirmation (the node entered the
156/// configuration state).
157pub fn is_switch_selective_response(response: &LssFrame) -> bool {
158    response[0] == CS_SWITCH_SELECTIVE_RESPONSE
159}
160
161/// Decode a configure-node-id response: `Ok(())` on success, `Err(error_code)`
162/// otherwise (`1` = node-id out of range). `None` if not such a response.
163pub fn decode_configure_node_id_response(response: &LssFrame) -> Option<Result<(), u8>> {
164    if response[0] != CS_CONFIGURE_NODE_ID {
165        return None;
166    }
167    Some(if response[1] == 0 {
168        Ok(())
169    } else {
170        Err(response[1])
171    })
172}
173
174/// Decode an inquire-node-id response into the reported node-id.
175pub fn decode_inquire_node_id_response(response: &LssFrame) -> Option<u8> {
176    (response[0] == CS_INQUIRE_NODE_ID).then_some(response[1])
177}
178
179// --- Fastscan (identify an unknown node by bisecting its address) -----------
180
181/// Encode an LSS Fastscan request (CiA 305 §3.7).
182///
183/// `id_number` is the candidate value being tested, `bit_check` the bit being
184/// probed ([`FASTSCAN_INIT`] to start; then `31..=0`), `lss_sub` which of the
185/// four identity values is under scan (`0` = vendor, `1` = product,
186/// `2` = revision, `3` = serial), and `lss_next` the sub to move to once this
187/// one is confirmed. Most callers drive this through [`FastscanMaster`].
188pub fn encode_fastscan(id_number: u32, bit_check: u8, lss_sub: u8, lss_next: u8) -> LssFrame {
189    let mut f = frame_u32(CS_FASTSCAN, id_number);
190    f[5] = bit_check;
191    f[6] = lss_sub;
192    f[7] = lss_next;
193    f
194}
195
196/// Whether `response` is the "identify slave" answer a node sends during
197/// Fastscan (and identify-remote-slave) — i.e. an unconfigured node is present
198/// and matches the request so far.
199pub fn is_identify_slave_response(response: &LssFrame) -> bool {
200    response[0] == CS_IDENTIFY_SLAVE
201}
202
203/// A sans-I/O LSS master that discovers an unconfigured node's 128-bit
204/// [`LssAddress`] by Fastscan, when the master does not know it up front.
205///
206/// It walks the four identity values, bisecting each from the most significant
207/// bit: for every bit it emits a [`next_request`](FastscanMaster::next_request)
208/// frame, the caller transmits it and reports — via
209/// [`on_response`](FastscanMaster::on_response) — whether an
210/// [`is_identify_slave_response`] arrived on [`LSS_SLAVE_COB_ID`] within the
211/// LSS timeout. After [`is_complete`](FastscanMaster::is_complete), the matched
212/// node is in the configuration state and its [`address`](FastscanMaster::address)
213/// is known — ready for [`encode_configure_node_id`].
214///
215/// ```
216/// use canopen_rs::lss::{FastscanMaster, LssAddress, LssSlave, UNCONFIGURED_NODE_ID};
217///
218/// let address = LssAddress { vendor_id: 0x1F, product_code: 0x2A, revision_number: 1, serial_number: 0x1234 };
219/// let mut slave = LssSlave::new(address, UNCONFIGURED_NODE_ID);
220/// let mut master = FastscanMaster::new();
221///
222/// while let Some(req) = master.next_request() {
223///     let answered = slave.handle(&req).is_some(); // stand-in for the bus round-trip
224///     master.on_response(answered);
225/// }
226/// assert!(master.found());
227/// assert_eq!(master.address(), address);
228/// ```
229#[derive(Debug)]
230pub struct FastscanMaster {
231    id: [u32; 4],
232    sub: u8, // 0..=3 while scanning; 4 once complete
233    bit: i8, // -2 = initial probe, 31..=0 = bit under test, -1 = confirm
234    found: bool,
235}
236
237impl Default for FastscanMaster {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243impl FastscanMaster {
244    /// Start a fresh Fastscan.
245    pub const fn new() -> Self {
246        Self {
247            id: [0; 4],
248            sub: 0,
249            bit: -2,
250            found: false,
251        }
252    }
253
254    /// The next Fastscan frame to transmit on [`LSS_MASTER_COB_ID`], or `None`
255    /// once discovery has finished.
256    pub fn next_request(&self) -> Option<LssFrame> {
257        if self.sub > 3 {
258            return None;
259        }
260        let sub = self.sub;
261        let idx = sub as usize;
262        Some(match self.bit {
263            -2 => encode_fastscan(0, FASTSCAN_INIT, 0, 0),
264            -1 => encode_fastscan(self.id[idx], 0, sub, (sub + 1) & 3),
265            b => encode_fastscan(self.id[idx], b as u8, sub, sub),
266        })
267    }
268
269    /// Report whether the last [`next_request`](FastscanMaster::next_request)
270    /// drew an identify-slave response, advancing the scan.
271    pub fn on_response(&mut self, responded: bool) {
272        match self.bit {
273            -2 => {
274                if responded {
275                    self.found = true;
276                    self.bit = 31;
277                } else {
278                    self.sub = 4; // no unconfigured node answered
279                }
280            }
281            -1 => {
282                // Sub confirmed; move on to the next identity value.
283                self.sub += 1;
284                self.bit = 31;
285            }
286            b => {
287                // No response means this bit of the identity is a 1.
288                if !responded {
289                    self.id[self.sub as usize] |= 1u32 << b;
290                }
291                self.bit -= 1; // 31..=0, then -1 to confirm the sub
292            }
293        }
294    }
295
296    /// Whether an unconfigured node answered the initial probe.
297    pub const fn found(&self) -> bool {
298        self.found
299    }
300
301    /// Whether the scan has run to completion.
302    pub const fn is_complete(&self) -> bool {
303        self.sub > 3
304    }
305
306    /// The discovered address — meaningful once [`is_complete`](FastscanMaster::is_complete)
307    /// and [`found`](FastscanMaster::found) are both true.
308    pub const fn address(&self) -> LssAddress {
309        LssAddress {
310            vendor_id: self.id[0],
311            product_code: self.id[1],
312            revision_number: self.id[2],
313            serial_number: self.id[3],
314        }
315    }
316}
317
318/// The node-side LSS state machine (CiA 305).
319///
320/// Feed each frame received on [`LSS_MASTER_COB_ID`] to [`LssSlave::handle`] and
321/// transmit any [`LssFrame`] it returns on [`LSS_SLAVE_COB_ID`]. A configured
322/// node-id set here becomes active on the node's next reset — call
323/// [`LssSlave::adopt_pending`] then.
324#[derive(Debug)]
325pub struct LssSlave {
326    address: LssAddress,
327    node_id: u8,
328    pending_node_id: u8,
329    state: LssState,
330    selective: u8,
331    fs_sub: u8,
332}
333
334impl LssSlave {
335    /// Create a slave with the given identity and current node-id
336    /// ([`UNCONFIGURED_NODE_ID`] if it has none yet).
337    pub const fn new(address: LssAddress, node_id: u8) -> Self {
338        Self {
339            address,
340            node_id,
341            pending_node_id: node_id,
342            state: LssState::Waiting,
343            selective: 0,
344            fs_sub: 0,
345        }
346    }
347
348    /// The identity value for a Fastscan sub (0 = vendor .. 3 = serial).
349    fn identity(&self, sub: u8) -> u32 {
350        match sub {
351            0 => self.address.vendor_id,
352            1 => self.address.product_code,
353            2 => self.address.revision_number,
354            _ => self.address.serial_number,
355        }
356    }
357
358    /// Handle an LSS Fastscan request, bisecting this node's identity so a
359    /// master can discover its [`LssAddress`] without knowing it in advance.
360    /// Only an unconfigured node still in the waiting state participates; a full
361    /// match on the serial number (the last sub) switches it into the
362    /// configuration state, exactly as a selective switch would.
363    fn handle_fastscan(&mut self, req: &LssFrame) -> Option<LssFrame> {
364        if self.node_id != UNCONFIGURED_NODE_ID || self.state != LssState::Waiting {
365            return None;
366        }
367        let id_number = u32_at(req);
368        let bit_check = req[5];
369        let lss_sub = req[6];
370        let lss_next = req[7];
371
372        if bit_check >= FASTSCAN_INIT {
373            // Initial probe: restart the scan and announce our presence.
374            self.fs_sub = 0;
375            return Some(frame(CS_IDENTIFY_SLAVE));
376        }
377        if lss_sub != self.fs_sub {
378            return None;
379        }
380        // Check only the bits the master has determined so far ([`bit_check`..=31]).
381        let mask = 0xFFFF_FFFFu32 << bit_check;
382        if (id_number ^ self.identity(lss_sub)) & mask != 0 {
383            return None;
384        }
385        // A full match with a changing `lss_next` confirms this sub is done.
386        if bit_check == 0 && lss_next != lss_sub {
387            if lss_sub >= 3 {
388                self.state = LssState::Configuration;
389            } else {
390                self.fs_sub = lss_next;
391            }
392        }
393        Some(frame(CS_IDENTIFY_SLAVE))
394    }
395
396    /// The active node-id (`0xFF` while unconfigured).
397    pub const fn node_id(&self) -> u8 {
398        self.node_id
399    }
400
401    /// The node-id set by the master but not yet active (applied on reset).
402    pub const fn pending_node_id(&self) -> u8 {
403        self.pending_node_id
404    }
405
406    /// The current LSS state.
407    pub const fn state(&self) -> LssState {
408        self.state
409    }
410
411    /// This node's LSS address.
412    pub const fn address(&self) -> LssAddress {
413        self.address
414    }
415
416    /// Adopt the pending node-id as the active one — call on the reset that
417    /// follows an LSS configuration.
418    pub fn adopt_pending(&mut self) {
419        self.node_id = self.pending_node_id;
420    }
421
422    /// Process an LSS master frame, returning the response to transmit, if any.
423    pub fn handle(&mut self, req: &LssFrame) -> Option<LssFrame> {
424        let configuring = self.state == LssState::Configuration;
425        match req[0] {
426            CS_SWITCH_GLOBAL => {
427                self.selective = 0;
428                self.state = if req[1] == MODE_CONFIGURATION {
429                    LssState::Configuration
430                } else {
431                    LssState::Waiting
432                };
433                None
434            }
435            CS_SWITCH_SELECTIVE_VENDOR => {
436                self.selective = u8::from(u32_at(req) == self.address.vendor_id);
437                None
438            }
439            CS_SWITCH_SELECTIVE_PRODUCT => {
440                self.selective = if self.selective == 1 && u32_at(req) == self.address.product_code
441                {
442                    2
443                } else {
444                    0
445                };
446                None
447            }
448            CS_SWITCH_SELECTIVE_REVISION => {
449                self.selective =
450                    if self.selective == 2 && u32_at(req) == self.address.revision_number {
451                        3
452                    } else {
453                        0
454                    };
455                None
456            }
457            CS_SWITCH_SELECTIVE_SERIAL => {
458                if self.selective == 3 && u32_at(req) == self.address.serial_number {
459                    self.selective = 0;
460                    self.state = LssState::Configuration;
461                    Some(frame(CS_SWITCH_SELECTIVE_RESPONSE))
462                } else {
463                    self.selective = 0;
464                    None
465                }
466            }
467            CS_CONFIGURE_NODE_ID if configuring => {
468                let id = req[1];
469                let error = if (1..=127).contains(&id) || id == UNCONFIGURED_NODE_ID {
470                    self.pending_node_id = id;
471                    0
472                } else {
473                    1 // node-id out of range
474                };
475                let mut f = frame(CS_CONFIGURE_NODE_ID);
476                f[1] = error;
477                Some(f)
478            }
479            CS_STORE_CONFIGURATION if configuring => Some(frame(CS_STORE_CONFIGURATION)),
480            CS_INQUIRE_NODE_ID if configuring => {
481                let mut f = frame(CS_INQUIRE_NODE_ID);
482                f[1] = self.node_id;
483                Some(f)
484            }
485            CS_INQUIRE_VENDOR if configuring => {
486                Some(frame_u32(CS_INQUIRE_VENDOR, self.address.vendor_id))
487            }
488            CS_INQUIRE_PRODUCT if configuring => {
489                Some(frame_u32(CS_INQUIRE_PRODUCT, self.address.product_code))
490            }
491            CS_INQUIRE_REVISION if configuring => {
492                Some(frame_u32(CS_INQUIRE_REVISION, self.address.revision_number))
493            }
494            CS_INQUIRE_SERIAL if configuring => {
495                Some(frame_u32(CS_INQUIRE_SERIAL, self.address.serial_number))
496            }
497            CS_FASTSCAN => self.handle_fastscan(req),
498            _ => None,
499        }
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    fn address() -> LssAddress {
508        LssAddress {
509            vendor_id: 0x0000_001F,
510            product_code: 0x0000_002A,
511            revision_number: 0x0000_0001,
512            serial_number: 0x0000_1234,
513        }
514    }
515
516    fn slave() -> LssSlave {
517        LssSlave::new(address(), UNCONFIGURED_NODE_ID)
518    }
519
520    // --- Master encoders: known-good frames --------------------------------
521    #[test]
522    fn switch_global_frames() {
523        assert_eq!(encode_switch_global(true), [0x04, 0x01, 0, 0, 0, 0, 0, 0]);
524        assert_eq!(encode_switch_global(false), [0x04, 0x00, 0, 0, 0, 0, 0, 0]);
525    }
526
527    #[test]
528    fn configure_node_id_frame() {
529        assert_eq!(
530            encode_configure_node_id(0x7F),
531            [0x11, 0x7F, 0, 0, 0, 0, 0, 0]
532        );
533    }
534
535    #[test]
536    fn switch_selective_sequence() {
537        let f = encode_switch_selective(&address());
538        assert_eq!(f[0], [0x40, 0x1F, 0x00, 0x00, 0x00, 0, 0, 0]); // vendor id LE
539        assert_eq!(f[3], [0x43, 0x34, 0x12, 0x00, 0x00, 0, 0, 0]); // serial LE
540    }
541
542    // --- Slave state machine ----------------------------------------------
543    #[test]
544    fn switch_global_changes_state() {
545        let mut s = slave();
546        assert_eq!(s.state(), LssState::Waiting);
547        assert!(s.handle(&encode_switch_global(true)).is_none());
548        assert_eq!(s.state(), LssState::Configuration);
549        s.handle(&encode_switch_global(false));
550        assert_eq!(s.state(), LssState::Waiting);
551    }
552
553    #[test]
554    fn selective_switch_matches_address() {
555        let mut s = slave();
556        let seq = encode_switch_selective(&address());
557        assert!(s.handle(&seq[0]).is_none());
558        assert!(s.handle(&seq[1]).is_none());
559        assert!(s.handle(&seq[2]).is_none());
560        let resp = s.handle(&seq[3]).expect("selective response");
561        assert!(is_switch_selective_response(&resp));
562        assert_eq!(s.state(), LssState::Configuration);
563    }
564
565    #[test]
566    fn selective_switch_rejects_wrong_address() {
567        let mut s = slave();
568        let mut seq = encode_switch_selective(&address());
569        seq[3][1] = 0xFF; // corrupt the serial number
570        for f in &seq {
571            assert!(s.handle(f).is_none());
572        }
573        assert_eq!(s.state(), LssState::Waiting);
574    }
575
576    #[test]
577    fn configure_node_id_only_in_configuration() {
578        let mut s = slave();
579        // Ignored while waiting.
580        assert!(s.handle(&encode_configure_node_id(0x20)).is_none());
581
582        s.handle(&encode_switch_global(true));
583        let resp = s.handle(&encode_configure_node_id(0x20)).unwrap();
584        assert_eq!(decode_configure_node_id_response(&resp), Some(Ok(())));
585        assert_eq!(s.pending_node_id(), 0x20);
586        assert_eq!(s.node_id(), UNCONFIGURED_NODE_ID); // not active until reset
587        s.adopt_pending();
588        assert_eq!(s.node_id(), 0x20);
589    }
590
591    #[test]
592    fn configure_node_id_rejects_out_of_range() {
593        let mut s = slave();
594        s.handle(&encode_switch_global(true));
595        let resp = s.handle(&encode_configure_node_id(200)).unwrap();
596        assert_eq!(decode_configure_node_id_response(&resp), Some(Err(1)));
597    }
598
599    #[test]
600    fn inquiries_report_identity_in_configuration() {
601        let mut s = slave();
602        s.handle(&encode_switch_global(true));
603
604        let node = s.handle(&encode_inquire_node_id()).unwrap();
605        assert_eq!(
606            decode_inquire_node_id_response(&node),
607            Some(UNCONFIGURED_NODE_ID)
608        );
609
610        let vendor = s.handle(&frame(CS_INQUIRE_VENDOR)).unwrap();
611        assert_eq!(vendor, [0x5A, 0x1F, 0x00, 0x00, 0x00, 0, 0, 0]);
612    }
613
614    #[test]
615    fn store_acknowledges() {
616        let mut s = slave();
617        s.handle(&encode_switch_global(true));
618        assert_eq!(
619            s.handle(&encode_store()).unwrap(),
620            [0x17, 0, 0, 0, 0, 0, 0, 0]
621        );
622    }
623
624    // --- Fastscan ----------------------------------------------------------
625    #[test]
626    fn fastscan_frame_layout() {
627        // CS=0x51, id LE, bit_check, lss_sub, lss_next.
628        assert_eq!(
629            encode_fastscan(0x1234_5678, 31, 0, 0),
630            [0x51, 0x78, 0x56, 0x34, 0x12, 31, 0, 0]
631        );
632        assert_eq!(
633            encode_fastscan(0, FASTSCAN_INIT, 0, 0),
634            [0x51, 0, 0, 0, 0, 32, 0, 0]
635        );
636    }
637
638    #[test]
639    fn fastscan_probe_only_answered_while_unconfigured() {
640        let mut s = slave();
641        let probe = encode_fastscan(0, FASTSCAN_INIT, 0, 0);
642        assert!(is_identify_slave_response(&s.handle(&probe).unwrap()));
643
644        // A configured node ignores Fastscan entirely.
645        let mut configured = LssSlave::new(address(), 0x20);
646        assert!(configured.handle(&probe).is_none());
647    }
648
649    /// Drive the sans-I/O master against the slave: it should recover the exact
650    /// address and leave the slave in the configuration state.
651    fn run_fastscan(addr: LssAddress) -> (FastscanMaster, LssState) {
652        let mut slave = LssSlave::new(addr, UNCONFIGURED_NODE_ID);
653        let mut master = FastscanMaster::new();
654        let mut steps = 0;
655        while let Some(req) = master.next_request() {
656            let answered = slave.handle(&req).is_some();
657            master.on_response(answered);
658            steps += 1;
659            assert!(steps < 200, "fastscan did not converge");
660        }
661        (master, slave.state())
662    }
663
664    #[test]
665    fn fastscan_discovers_the_address() {
666        let (master, slave_state) = run_fastscan(address());
667        assert!(master.found());
668        assert!(master.is_complete());
669        assert_eq!(master.address(), address());
670        assert_eq!(slave_state, LssState::Configuration);
671    }
672
673    #[test]
674    fn fastscan_recovers_extreme_identities() {
675        for addr in [
676            LssAddress {
677                vendor_id: 0xFFFF_FFFF,
678                product_code: 0,
679                revision_number: 0x8000_0000,
680                serial_number: 0x0000_0001,
681            },
682            LssAddress {
683                vendor_id: 0xDEAD_BEEF,
684                product_code: 0xCAFE_F00D,
685                revision_number: 0x0BAD_C0DE,
686                serial_number: 0xFEED_FACE,
687            },
688        ] {
689            let (master, slave_state) = run_fastscan(addr);
690            assert_eq!(master.address(), addr);
691            assert_eq!(slave_state, LssState::Configuration);
692        }
693    }
694
695    #[test]
696    fn fastscan_reports_no_node() {
697        // With no slave answering, the probe fails and the scan ends unfound.
698        let mut master = FastscanMaster::new();
699        let _req = master.next_request().unwrap();
700        master.on_response(false);
701        assert!(!master.found());
702        assert!(master.is_complete());
703        assert!(master.next_request().is_none());
704    }
705
706    #[test]
707    fn fastscan_ignores_non_matching_slave_then_configures_match() {
708        // Two unconfigured nodes; the master converges on one exact address,
709        // and only that node ends up configured.
710        let a = address();
711        let b = LssAddress {
712            serial_number: 0x9999,
713            ..address()
714        };
715        let mut sa = LssSlave::new(a, UNCONFIGURED_NODE_ID);
716        let mut sb = LssSlave::new(b, UNCONFIGURED_NODE_ID);
717        let mut master = FastscanMaster::new();
718        while let Some(req) = master.next_request() {
719            // Either node answering is enough for the master to see a response.
720            let ra = sa.handle(&req).is_some();
721            let rb = sb.handle(&req).is_some();
722            master.on_response(ra || rb);
723        }
724        assert_eq!(master.address(), a);
725        assert_eq!(sa.state(), LssState::Configuration);
726        assert_eq!(sb.state(), LssState::Waiting); // diverged, never selected
727    }
728
729    /// A tiny linear-congruential generator (glibc constants) so the sweep is
730    /// deterministic and dependency-free — no external `rand` crate.
731    fn lcg(state: &mut u32) -> u32 {
732        *state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
733        *state
734    }
735
736    #[test]
737    fn fastscan_recovers_pathological_bit_patterns() {
738        // All-zeros, all-ones, and the two alternating-bit patterns in every
739        // field — the cases most likely to expose a bit 31 vs bit 0 masking or
740        // shift error in the bisection.
741        for &v in &[0x0000_0000u32, 0xFFFF_FFFF, 0xAAAA_AAAA, 0x5555_5555] {
742            let addr = LssAddress {
743                vendor_id: v,
744                product_code: v,
745                revision_number: v,
746                serial_number: v,
747            };
748            let (master, slave_state) = run_fastscan(addr);
749            assert!(master.found());
750            assert_eq!(master.address(), addr, "failed to recover {addr:?}");
751            assert_eq!(slave_state, LssState::Configuration);
752        }
753
754        // A different pattern in each field, so the four subs cannot alias.
755        let mixed = LssAddress {
756            vendor_id: 0x0000_0000,
757            product_code: 0xFFFF_FFFF,
758            revision_number: 0xAAAA_AAAA,
759            serial_number: 0x5555_5555,
760        };
761        let (master, slave_state) = run_fastscan(mixed);
762        assert_eq!(master.address(), mixed);
763        assert_eq!(slave_state, LssState::Configuration);
764    }
765
766    #[test]
767    fn fastscan_recovers_pseudo_random_addresses() {
768        // A deterministic pseudo-random sweep: every recovered address must
769        // equal the slave's true one, and the slave must end configured.
770        let mut seed = 0x1234_5678u32;
771        for _ in 0..512 {
772            let addr = LssAddress {
773                vendor_id: lcg(&mut seed),
774                product_code: lcg(&mut seed),
775                revision_number: lcg(&mut seed),
776                serial_number: lcg(&mut seed),
777            };
778            let (master, slave_state) = run_fastscan(addr);
779            assert!(master.found());
780            assert_eq!(master.address(), addr, "failed to recover {addr:?}");
781            assert_eq!(slave_state, LssState::Configuration);
782        }
783    }
784
785    #[test]
786    fn fastscan_converges_on_one_of_many_nodes() {
787        // Several unconfigured slaves with distinct identities share the bus.
788        // The master sees a response if *any* node answers, yet it must settle
789        // on exactly one real address; every other node stays in Waiting.
790        let addrs = [
791            LssAddress {
792                vendor_id: 0x1F,
793                product_code: 0x2A,
794                revision_number: 1,
795                serial_number: 0x1000,
796            },
797            // Shares vendor/product/revision with the first — diverges only in
798            // the serial (the last sub), the hardest case for the bisection.
799            LssAddress {
800                vendor_id: 0x1F,
801                product_code: 0x2A,
802                revision_number: 1,
803                serial_number: 0x2000,
804            },
805            LssAddress {
806                vendor_id: 0x1F,
807                product_code: 0x2A,
808                revision_number: 2,
809                serial_number: 0x3000,
810            },
811            LssAddress {
812                vendor_id: 0x20,
813                product_code: 0x01,
814                revision_number: 9,
815                serial_number: 0x4000,
816            },
817            LssAddress {
818                vendor_id: 0xDEAD_BEEF,
819                product_code: 0x0000_FEED,
820                revision_number: 0,
821                serial_number: 0xFFFF_FFFF,
822            },
823        ];
824        let mut slaves = [
825            LssSlave::new(addrs[0], UNCONFIGURED_NODE_ID),
826            LssSlave::new(addrs[1], UNCONFIGURED_NODE_ID),
827            LssSlave::new(addrs[2], UNCONFIGURED_NODE_ID),
828            LssSlave::new(addrs[3], UNCONFIGURED_NODE_ID),
829            LssSlave::new(addrs[4], UNCONFIGURED_NODE_ID),
830        ];
831        let mut master = FastscanMaster::new();
832        while let Some(req) = master.next_request() {
833            let mut answered = false;
834            for s in &mut slaves {
835                answered |= s.handle(&req).is_some();
836            }
837            master.on_response(answered);
838        }
839
840        assert!(master.found());
841        let found = master.address();
842        assert!(
843            addrs.contains(&found),
844            "converged on a phantom address {found:?}"
845        );
846        // Exactly one node — the recovered one — ended up configured.
847        let mut configured = 0;
848        for (a, s) in addrs.iter().zip(&slaves) {
849            if *a == found {
850                assert_eq!(s.state(), LssState::Configuration);
851                configured += 1;
852            } else {
853                assert_eq!(s.state(), LssState::Waiting);
854            }
855        }
856        assert_eq!(configured, 1);
857    }
858
859    #[test]
860    fn fastscan_init_probe_resets_an_in_progress_scan() {
861        // The reset probe (bit_check >= FASTSCAN_INIT) must restart the slave's
862        // scan, whatever sub it had reached.
863        let mut s = slave();
864        // Confirm sub 0, advancing the slave to sub 1.
865        let confirm_sub0 = encode_fastscan(address().vendor_id, 0, 0, 1);
866        assert!(is_identify_slave_response(
867            &s.handle(&confirm_sub0).unwrap()
868        ));
869        // A sub-0 bit frame is now ignored (the slave is scanning sub 1).
870        assert!(s.handle(&encode_fastscan(0, 31, 0, 0)).is_none());
871
872        // The reset probe answers and restarts at sub 0…
873        let probe = encode_fastscan(0, FASTSCAN_INIT, 0, 0);
874        assert!(is_identify_slave_response(&s.handle(&probe).unwrap()));
875        // …so the sub-0 bit frame is answered again.
876        assert!(s.handle(&encode_fastscan(0, 31, 0, 0)).is_some());
877    }
878
879    #[test]
880    fn fastscan_never_converges_on_a_phantom_address() {
881        // Randomised multi-node stress: for many bus populations, the master —
882        // which only sees "did anyone answer?" — must always land on one of the
883        // *real* addresses (never a bitwise blend of several) and configure
884        // exactly that node.
885        let mut seed = 0x0BAD_F00Du32;
886        let rand_addr = |seed: &mut u32| LssAddress {
887            vendor_id: lcg(seed),
888            product_code: lcg(seed),
889            revision_number: lcg(seed),
890            serial_number: lcg(seed),
891        };
892        for _ in 0..256 {
893            let addrs = [
894                rand_addr(&mut seed),
895                rand_addr(&mut seed),
896                rand_addr(&mut seed),
897                rand_addr(&mut seed),
898            ];
899            let mut slaves = [
900                LssSlave::new(addrs[0], UNCONFIGURED_NODE_ID),
901                LssSlave::new(addrs[1], UNCONFIGURED_NODE_ID),
902                LssSlave::new(addrs[2], UNCONFIGURED_NODE_ID),
903                LssSlave::new(addrs[3], UNCONFIGURED_NODE_ID),
904            ];
905            let mut master = FastscanMaster::new();
906            while let Some(req) = master.next_request() {
907                let mut answered = false;
908                for s in &mut slaves {
909                    answered |= s.handle(&req).is_some();
910                }
911                master.on_response(answered);
912            }
913            let found = master.address();
914            assert!(
915                addrs.contains(&found),
916                "phantom address {found:?} for population {addrs:?}"
917            );
918            let configured = slaves
919                .iter()
920                .filter(|s| s.state() == LssState::Configuration)
921                .count();
922            assert_eq!(configured, 1, "population {addrs:?}");
923        }
924    }
925
926    #[test]
927    fn fastscan_bit_check_at_boundaries() {
928        // bit_check = 31 checks only the MSB; bit_check = 0 checks all 32 bits.
929        // Both must stay within `0xFFFF_FFFF << bit_check` (no shift overflow).
930        let addr = LssAddress {
931            vendor_id: 0x8000_0001, // MSB and LSB set, nothing between
932            ..address()
933        };
934        let mut s = LssSlave::new(addr, UNCONFIGURED_NODE_ID);
935        // MSB set: a candidate with bit 31 clear must NOT match at bit_check 31.
936        assert!(s.handle(&encode_fastscan(0x0000_0000, 31, 0, 0)).is_none());
937        // A candidate with bit 31 set matches when only bit 31 is checked.
938        assert!(s.handle(&encode_fastscan(0x8000_0000, 31, 0, 0)).is_some());
939        // At bit_check 0 the whole word must match exactly.
940        assert!(s.handle(&encode_fastscan(0x8000_0000, 0, 0, 0)).is_none());
941        assert!(s.handle(&encode_fastscan(0x8000_0001, 0, 0, 0)).is_some());
942    }
943}