Skip to main content

stackforge_core/layer/zwave/
builder.rs

1//! Z-Wave packet builder.
2//!
3//! Provides a fluent API for constructing Z-Wave frames.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use stackforge_core::layer::zwave::builder::ZWaveBuilder;
9//!
10//! // ACK frame
11//! let pkt = ZWaveBuilder::new().home_id(0x0161f498).src(1).dst(2).ack().build();
12//! assert_eq!(pkt.len(), 10);
13//!
14//! // REQ frame with SWITCH_BINARY command class
15//! let pkt = ZWaveBuilder::new()
16//!     .home_id(0x0161f498)
17//!     .src(1)
18//!     .dst(2)
19//!     .cmd_class(0x25)
20//!     .cmd(0x01)
21//!     .cmd_data(vec![0xFF])
22//!     .build();
23//! assert!(pkt.len() > 10);
24//! ```
25
26use super::zwave_crc;
27
28/// Builder for Z-Wave frames.
29///
30/// Produces either ACK frames (10 bytes, no payload) or REQ frames
31/// (10 + payload bytes) with auto-computed CRC.
32///
33/// Frame layout:
34///   homeId(4) + src(1) + frameCtrl(1) + beamSeqn(1) + length(1) + dst(1) + [payload] + crc(1)
35#[derive(Debug, Clone)]
36pub struct ZWaveBuilder {
37    home_id: u32,
38    src: u8,
39    dst: u8,
40    routed: bool,
41    ackreq: bool,
42    lowpower: bool,
43    speedmodified: bool,
44    headertype: u8,
45    beam_control: u8,
46    seqn: u8,
47    cmd_class_val: Option<u8>,
48    cmd_val: Option<u8>,
49    cmd_data_val: Vec<u8>,
50}
51
52impl Default for ZWaveBuilder {
53    fn default() -> Self {
54        Self {
55            home_id: 0,
56            src: 1,
57            dst: 2,
58            routed: false,
59            ackreq: true,
60            lowpower: false,
61            speedmodified: false,
62            headertype: 0,
63            beam_control: 0,
64            seqn: 0,
65            cmd_class_val: None,
66            cmd_val: None,
67            cmd_data_val: Vec::new(),
68        }
69    }
70}
71
72impl ZWaveBuilder {
73    /// Create a new Z-Wave builder with defaults.
74    #[must_use]
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    // ========== Field setters (fluent API) ==========
80
81    /// Set the 4-byte Home ID.
82    #[must_use]
83    pub fn home_id(mut self, id: u32) -> Self {
84        self.home_id = id;
85        self
86    }
87
88    /// Set the source node ID.
89    #[must_use]
90    pub fn src(mut self, src: u8) -> Self {
91        self.src = src;
92        self
93    }
94
95    /// Set the destination node ID.
96    #[must_use]
97    pub fn dst(mut self, dst: u8) -> Self {
98        self.dst = dst;
99        self
100    }
101
102    /// Set the routed flag (bit 7 of frame control).
103    #[must_use]
104    pub fn routed(mut self, v: bool) -> Self {
105        self.routed = v;
106        self
107    }
108
109    /// Set the ack request flag (bit 6 of frame control).
110    #[must_use]
111    pub fn ackreq(mut self, v: bool) -> Self {
112        self.ackreq = v;
113        self
114    }
115
116    /// Set the low power flag (bit 5 of frame control).
117    #[must_use]
118    pub fn lowpower(mut self, v: bool) -> Self {
119        self.lowpower = v;
120        self
121    }
122
123    /// Set the speed modified flag (bit 4 of frame control).
124    #[must_use]
125    pub fn speedmodified(mut self, v: bool) -> Self {
126        self.speedmodified = v;
127        self
128    }
129
130    /// Set the header type (bits 3-0 of frame control).
131    #[must_use]
132    pub fn headertype(mut self, v: u8) -> Self {
133        self.headertype = v & 0x0F;
134        self
135    }
136
137    /// Set the beam control field (bits 6-5 of beam/sequence byte).
138    #[must_use]
139    pub fn beam_control(mut self, v: u8) -> Self {
140        self.beam_control = v & 0x03;
141        self
142    }
143
144    /// Set the sequence number (bits 3-0 of beam/sequence byte).
145    #[must_use]
146    pub fn seqn(mut self, v: u8) -> Self {
147        self.seqn = v & 0x0F;
148        self
149    }
150
151    /// Set the command class byte. Setting this makes the frame a REQ.
152    #[must_use]
153    pub fn cmd_class(mut self, cc: u8) -> Self {
154        self.cmd_class_val = Some(cc);
155        self
156    }
157
158    /// Set the command byte.
159    #[must_use]
160    pub fn cmd(mut self, c: u8) -> Self {
161        self.cmd_val = Some(c);
162        self
163    }
164
165    /// Set the command data bytes.
166    #[must_use]
167    pub fn cmd_data(mut self, data: Vec<u8>) -> Self {
168        self.cmd_data_val = data;
169        self
170    }
171
172    /// Configure this builder for an ACK frame (clears any payload fields).
173    #[must_use]
174    pub fn ack(mut self) -> Self {
175        self.cmd_class_val = None;
176        self.cmd_val = None;
177        self.cmd_data_val = Vec::new();
178        self
179    }
180
181    // ========== Build ==========
182
183    /// Compute the frame control byte from the individual flags.
184    fn build_frame_ctrl(&self) -> u8 {
185        let mut fc: u8 = self.headertype & 0x0F;
186        if self.routed {
187            fc |= 0x80;
188        }
189        if self.ackreq {
190            fc |= 0x40;
191        }
192        if self.lowpower {
193            fc |= 0x20;
194        }
195        if self.speedmodified {
196            fc |= 0x10;
197        }
198        fc
199    }
200
201    /// Compute the beam/sequence byte.
202    fn build_beam_seqn(&self) -> u8 {
203        ((self.beam_control & 0x03) << 5) | (self.seqn & 0x0F)
204    }
205
206    /// Serialize the Z-Wave frame into bytes.
207    ///
208    /// If `cmd_class_val` is `None`, builds a 10-byte ACK frame.
209    /// Otherwise builds a REQ frame with `cmd_class` + cmd + data.
210    /// The CRC is computed automatically as XOR of all preceding bytes starting from 0xFF.
211    #[must_use]
212    pub fn build(&self) -> Vec<u8> {
213        let is_ack = self.cmd_class_val.is_none();
214
215        // Compute payload size
216        let payload_len = if is_ack {
217            0
218        } else {
219            // cmd_class(1) + cmd(1) + data
220            1 + usize::from(self.cmd_val.is_some()) + self.cmd_data_val.len()
221        };
222
223        let total_len = 10 + payload_len; // header(9) + crc(1) + payload
224        let mut buf = Vec::with_capacity(total_len);
225
226        // Home ID (4 bytes, big-endian)
227        buf.extend_from_slice(&self.home_id.to_be_bytes());
228
229        // Source node ID (1 byte)
230        buf.push(self.src);
231
232        // Frame control byte (1 byte)
233        buf.push(self.build_frame_ctrl());
234
235        // Beam/sequence byte (1 byte)
236        buf.push(self.build_beam_seqn());
237
238        // Length field (1 byte) - total frame length
239        buf.push(total_len as u8);
240
241        // Destination node ID (1 byte)
242        buf.push(self.dst);
243
244        // Payload (only for REQ frames)
245        if let Some(cc) = self.cmd_class_val {
246            buf.push(cc);
247            if let Some(cmd) = self.cmd_val {
248                buf.push(cmd);
249            }
250            buf.extend_from_slice(&self.cmd_data_val);
251        }
252
253        // CRC: XOR of all preceding bytes starting from 0xFF
254        let crc = zwave_crc(&buf);
255        buf.push(crc);
256
257        buf
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264    use crate::layer::zwave::{ZWAVE_MIN_HEADER_LEN, ZWaveLayer, cmd_class};
265    use crate::layer::{LayerIndex, LayerKind};
266
267    #[test]
268    fn test_build_ack_frame() {
269        let pkt = ZWaveBuilder::new()
270            .home_id(0x0161f498)
271            .src(1)
272            .dst(2)
273            .ack()
274            .build();
275        assert_eq!(pkt.len(), ZWAVE_MIN_HEADER_LEN);
276        // Verify fields
277        let idx = LayerIndex::new(LayerKind::ZWave, 0, pkt.len());
278        let zw = ZWaveLayer::new(idx);
279        assert_eq!(zw.home_id(&pkt).unwrap(), 0x0161f498);
280        assert_eq!(zw.src(&pkt).unwrap(), 1);
281        assert_eq!(zw.dst(&pkt).unwrap(), 2);
282        assert!(zw.is_ack(&pkt));
283        assert!(zw.verify_crc(&pkt));
284    }
285
286    #[test]
287    fn test_build_req_frame() {
288        let pkt = ZWaveBuilder::new()
289            .home_id(0xDEADBEEF)
290            .src(3)
291            .dst(5)
292            .cmd_class(cmd_class::SWITCH_BINARY)
293            .cmd(0x01)
294            .cmd_data(vec![0xFF])
295            .build();
296
297        // 10 (header + crc) + 3 (cmd_class + cmd + data) = 13
298        assert_eq!(pkt.len(), 13);
299
300        let idx = LayerIndex::new(LayerKind::ZWave, 0, pkt.len());
301        let zw = ZWaveLayer::new(idx);
302        assert!(!zw.is_ack(&pkt));
303        assert_eq!(zw.home_id(&pkt).unwrap(), 0xDEADBEEF);
304        assert_eq!(zw.src(&pkt).unwrap(), 3);
305        assert_eq!(zw.dst(&pkt).unwrap(), 5);
306        assert_eq!(zw.cmd_class(&pkt).unwrap(), cmd_class::SWITCH_BINARY);
307        assert_eq!(zw.cmd(&pkt).unwrap(), 0x01);
308        assert_eq!(zw.cmd_data(&pkt).unwrap(), &[0xFF]);
309        assert!(zw.verify_crc(&pkt));
310    }
311
312    #[test]
313    fn test_crc_verification() {
314        let pkt = ZWaveBuilder::new()
315            .home_id(0x01020304)
316            .src(10)
317            .dst(20)
318            .cmd_class(cmd_class::BASIC)
319            .cmd(0x01)
320            .build();
321
322        let idx = LayerIndex::new(LayerKind::ZWave, 0, pkt.len());
323        let zw = ZWaveLayer::new(idx);
324        assert!(zw.verify_crc(&pkt));
325
326        // Corrupt a byte and verify CRC fails
327        let mut bad = pkt.clone();
328        bad[4] ^= 0x01;
329        assert!(!zw.verify_crc(&bad));
330    }
331
332    #[test]
333    fn test_frame_ctrl_flags() {
334        let pkt = ZWaveBuilder::new()
335            .routed(true)
336            .ackreq(true)
337            .lowpower(true)
338            .speedmodified(true)
339            .headertype(0x03)
340            .ack()
341            .build();
342
343        let idx = LayerIndex::new(LayerKind::ZWave, 0, pkt.len());
344        let zw = ZWaveLayer::new(idx);
345        assert!(zw.routed(&pkt).unwrap());
346        assert!(zw.ackreq(&pkt).unwrap());
347        assert!(zw.lowpower(&pkt).unwrap());
348        assert!(zw.speedmodified(&pkt).unwrap());
349        assert_eq!(zw.headertype(&pkt).unwrap(), 0x03);
350    }
351
352    #[test]
353    fn test_beam_seqn() {
354        let pkt = ZWaveBuilder::new().beam_control(2).seqn(0x0A).ack().build();
355
356        let idx = LayerIndex::new(LayerKind::ZWave, 0, pkt.len());
357        let zw = ZWaveLayer::new(idx);
358        assert_eq!(zw.beam_control(&pkt).unwrap(), 2);
359        assert_eq!(zw.seqn(&pkt).unwrap(), 0x0A);
360    }
361
362    #[test]
363    fn test_defaults() {
364        let b = ZWaveBuilder::new();
365        let pkt = b.build();
366        // Default: home_id=0, src=1, dst=2, ackreq=true, no cmd_class -> ACK
367        assert_eq!(pkt.len(), 10);
368        let idx = LayerIndex::new(LayerKind::ZWave, 0, pkt.len());
369        let zw = ZWaveLayer::new(idx);
370        assert_eq!(zw.home_id(&pkt).unwrap(), 0);
371        assert_eq!(zw.src(&pkt).unwrap(), 1);
372        assert_eq!(zw.dst(&pkt).unwrap(), 2);
373        assert!(zw.ackreq(&pkt).unwrap());
374        assert!(!zw.routed(&pkt).unwrap());
375        assert!(zw.is_ack(&pkt));
376    }
377
378    #[test]
379    fn test_large_payload_round_trip() {
380        let data: Vec<u8> = (0..200).map(|i| (i & 0xFF) as u8).collect();
381        let pkt = ZWaveBuilder::new()
382            .home_id(0xCAFEBABE)
383            .src(10)
384            .dst(20)
385            .cmd_class(cmd_class::MANUFACTURER_PROPRIETARY)
386            .cmd(0x42)
387            .cmd_data(data.clone())
388            .build();
389
390        let idx = LayerIndex::new(LayerKind::ZWave, 0, pkt.len());
391        let zw = ZWaveLayer::new(idx);
392        assert_eq!(zw.home_id(&pkt).unwrap(), 0xCAFEBABE);
393        assert_eq!(zw.src(&pkt).unwrap(), 10);
394        assert_eq!(zw.dst(&pkt).unwrap(), 20);
395        assert_eq!(
396            zw.cmd_class(&pkt).unwrap(),
397            cmd_class::MANUFACTURER_PROPRIETARY
398        );
399        assert_eq!(zw.cmd(&pkt).unwrap(), 0x42);
400        assert_eq!(zw.cmd_data(&pkt).unwrap(), &data[..]);
401        assert!(zw.verify_crc(&pkt));
402    }
403
404    #[test]
405    fn test_length_field_correct() {
406        // ACK: length = 10
407        let ack = ZWaveBuilder::new().ack().build();
408        let idx = LayerIndex::new(LayerKind::ZWave, 0, ack.len());
409        let zw = ZWaveLayer::new(idx);
410        assert_eq!(zw.length(&ack).unwrap(), 10);
411
412        // REQ with 1 byte data: 10 + 3 = 13
413        let req = ZWaveBuilder::new()
414            .cmd_class(cmd_class::BASIC)
415            .cmd(0x01)
416            .cmd_data(vec![0xAA])
417            .build();
418        let idx2 = LayerIndex::new(LayerKind::ZWave, 0, req.len());
419        let zw2 = ZWaveLayer::new(idx2);
420        assert_eq!(zw2.length(&req).unwrap(), 13);
421        assert_eq!(req.len(), 13);
422    }
423}