Skip to main content

lrwn/applayer/clocksync/
v2.rs

1use anyhow::Result;
2
3use crate::applayer::PayloadCodec;
4
5pub enum Cid {
6    PackageVersionReq,
7    PackageVersionAns,
8    AppTimeReq,
9    AppTimeAns,
10    DeviceAppTimePeropdicityReq,
11    DeviceAppTimePeropdicityAns,
12    ForceDeviceResyncCmd,
13}
14
15impl Cid {
16    pub fn from_u8(uplink: bool, value: u8) -> Result<Cid> {
17        Ok(match uplink {
18            true => match value {
19                0x00 => Cid::PackageVersionAns,
20                0x01 => Cid::AppTimeReq,
21                0x02 => Cid::DeviceAppTimePeropdicityAns,
22                _ => return Err(anyhow!("Invalid CID: {}", value)),
23            },
24            false => match value {
25                0x00 => Cid::PackageVersionReq,
26                0x01 => Cid::AppTimeAns,
27                0x02 => Cid::DeviceAppTimePeropdicityReq,
28                0x03 => Cid::ForceDeviceResyncCmd,
29                _ => return Err(anyhow!("Invalid CID: {}", value)),
30            },
31        })
32    }
33
34    pub fn to_u8(&self) -> u8 {
35        match self {
36            Cid::PackageVersionReq | Cid::PackageVersionAns => 0x00,
37            Cid::AppTimeReq | Cid::AppTimeAns => 0x01,
38            Cid::DeviceAppTimePeropdicityReq | Cid::DeviceAppTimePeropdicityAns => 0x02,
39            Cid::ForceDeviceResyncCmd => 0x03,
40        }
41    }
42}
43
44#[derive(Debug, PartialEq)]
45pub enum Payload {
46    PackageVersionReq,
47    PackageVersionAns(PackageVersionAnsPayload),
48    AppTimeReq(AppTimeReqPayload),
49    AppTimeAns(AppTimeAnsPayload),
50    DeviceAppTimePeropdicityReq(DeviceAppTimePeriodicityReqPayload),
51    DeviceAppTimePeropdicityAns(DeviceAppTimePeriodicityAnsPayload),
52    ForceDeviceResyncCmd(ForceDeviceResyncCmdPayload),
53}
54
55impl Payload {
56    pub fn cid(&self) -> Cid {
57        match self {
58            Self::PackageVersionReq => Cid::PackageVersionReq,
59            Self::PackageVersionAns(_) => Cid::PackageVersionAns,
60            Self::AppTimeReq(_) => Cid::AppTimeReq,
61            Self::AppTimeAns(_) => Cid::AppTimeAns,
62            Self::DeviceAppTimePeropdicityReq(_) => Cid::DeviceAppTimePeropdicityReq,
63            Self::DeviceAppTimePeropdicityAns(_) => Cid::DeviceAppTimePeropdicityAns,
64            Self::ForceDeviceResyncCmd(_) => Cid::ForceDeviceResyncCmd,
65        }
66    }
67
68    pub fn from_slice(uplink: bool, b: &[u8]) -> Result<Self> {
69        if b.is_empty() {
70            return Err(anyhow!("At least one byte is expected"));
71        }
72
73        let cid = Cid::from_u8(uplink, b[0])?;
74
75        Ok(match cid {
76            Cid::PackageVersionReq => Payload::PackageVersionReq,
77            Cid::PackageVersionAns => {
78                Payload::PackageVersionAns(PackageVersionAnsPayload::decode(&b[1..])?)
79            }
80            Cid::AppTimeReq => Payload::AppTimeReq(AppTimeReqPayload::decode(&b[1..])?),
81            Cid::AppTimeAns => Payload::AppTimeAns(AppTimeAnsPayload::decode(&b[1..])?),
82            Cid::DeviceAppTimePeropdicityReq => Payload::DeviceAppTimePeropdicityReq(
83                DeviceAppTimePeriodicityReqPayload::decode(&b[1..])?,
84            ),
85            Cid::DeviceAppTimePeropdicityAns => Payload::DeviceAppTimePeropdicityAns(
86                DeviceAppTimePeriodicityAnsPayload::decode(&b[1..])?,
87            ),
88            Cid::ForceDeviceResyncCmd => {
89                Payload::ForceDeviceResyncCmd(ForceDeviceResyncCmdPayload::decode(&b[1..])?)
90            }
91        })
92    }
93
94    pub fn to_vec(&self) -> Result<Vec<u8>> {
95        let mut out = vec![self.cid().to_u8()];
96
97        match self {
98            Self::PackageVersionReq => {}
99            Self::PackageVersionAns(pl) => out.extend_from_slice(&pl.encode()?),
100            Self::AppTimeReq(pl) => out.extend_from_slice(&pl.encode()?),
101            Self::AppTimeAns(pl) => out.extend_from_slice(&pl.encode()?),
102            Self::DeviceAppTimePeropdicityReq(pl) => out.extend_from_slice(&pl.encode()?),
103            Self::DeviceAppTimePeropdicityAns(pl) => out.extend_from_slice(&pl.encode()?),
104            Self::ForceDeviceResyncCmd(pl) => out.extend_from_slice(&pl.encode()?),
105        };
106
107        Ok(out)
108    }
109}
110
111#[derive(Debug, PartialEq, Clone)]
112pub struct PackageVersionAnsPayload {
113    pub package_identifier: u8,
114    pub package_version: u8,
115}
116
117impl PayloadCodec for PackageVersionAnsPayload {
118    fn decode(b: &[u8]) -> Result<Self> {
119        if b.len() != 2 {
120            return Err(anyhow!("Expected 2 bytes"));
121        }
122
123        Ok(PackageVersionAnsPayload {
124            package_identifier: b[0],
125            package_version: b[1],
126        })
127    }
128    fn encode(&self) -> Result<Vec<u8>> {
129        Ok(vec![self.package_identifier, self.package_version])
130    }
131}
132
133#[derive(Debug, PartialEq, Clone)]
134pub struct AppTimeReqPayload {
135    pub device_time: u32,
136    pub param: AppTimeReqPayloadParam,
137}
138
139impl PayloadCodec for AppTimeReqPayload {
140    fn decode(b: &[u8]) -> Result<Self> {
141        if b.len() != 5 {
142            return Err(anyhow!("Expected 5 bytes"));
143        }
144
145        Ok(AppTimeReqPayload {
146            device_time: {
147                let mut bytes = [0; 4];
148                bytes.copy_from_slice(&b[0..4]);
149                u32::from_le_bytes(bytes)
150            },
151            param: AppTimeReqPayloadParam {
152                token_req: b[4] & 0x0f,
153                ans_required: b[4] & 0x10 != 0,
154            },
155        })
156    }
157
158    fn encode(&self) -> Result<Vec<u8>> {
159        if self.param.token_req > 15 {
160            return Err(anyhow!("Max token_req value is 15"));
161        }
162
163        let mut b = vec![0; 5];
164        b[0..4].copy_from_slice(&self.device_time.to_le_bytes());
165        b[4] = self.param.token_req;
166
167        if self.param.ans_required {
168            b[4] |= 0x10;
169        }
170
171        Ok(b)
172    }
173}
174
175#[derive(Debug, PartialEq, Clone)]
176pub struct AppTimeReqPayloadParam {
177    pub token_req: u8,
178    pub ans_required: bool,
179}
180
181#[derive(Debug, PartialEq, Clone)]
182pub struct AppTimeAnsPayload {
183    pub time_correction: i32,
184    pub param: AppTimeAnsPayloadParam,
185}
186
187impl PayloadCodec for AppTimeAnsPayload {
188    fn decode(b: &[u8]) -> Result<Self> {
189        if b.len() != 5 {
190            return Err(anyhow!("Expected 5 bytes"));
191        }
192
193        Ok(AppTimeAnsPayload {
194            time_correction: {
195                let mut bytes = [0; 4];
196                bytes.copy_from_slice(&b[0..4]);
197                i32::from_le_bytes(bytes)
198            },
199            param: AppTimeAnsPayloadParam {
200                token_ans: b[4] & 0x0f,
201            },
202        })
203    }
204
205    fn encode(&self) -> Result<Vec<u8>> {
206        if self.param.token_ans > 15 {
207            return Err(anyhow!("Max token_ans value is 15"));
208        }
209
210        let mut b = vec![0; 5];
211        b[0..4].copy_from_slice(&self.time_correction.to_le_bytes());
212        b[4] = self.param.token_ans;
213
214        Ok(b)
215    }
216}
217
218#[derive(Debug, PartialEq, Clone)]
219pub struct AppTimeAnsPayloadParam {
220    pub token_ans: u8,
221}
222
223#[derive(Debug, PartialEq, Clone)]
224pub struct DeviceAppTimePeriodicityReqPayload {
225    pub period: u8,
226}
227
228impl PayloadCodec for DeviceAppTimePeriodicityReqPayload {
229    fn decode(b: &[u8]) -> Result<Self> {
230        if b.len() != 1 {
231            return Err(anyhow!("Expected 1 byte"));
232        }
233
234        Ok(DeviceAppTimePeriodicityReqPayload {
235            period: b[0] & 0x0f,
236        })
237    }
238
239    fn encode(&self) -> Result<Vec<u8>> {
240        if self.period > 15 {
241            return Err(anyhow!("Max period value is 15"));
242        }
243
244        Ok(vec![self.period])
245    }
246}
247
248#[derive(Debug, PartialEq, Clone)]
249pub struct DeviceAppTimePeriodicityAnsPayload {
250    pub status: DeviceAppTimePeriodicityAnsPayloadStatus,
251    pub time: u32,
252}
253
254impl PayloadCodec for DeviceAppTimePeriodicityAnsPayload {
255    fn decode(b: &[u8]) -> Result<Self> {
256        if b.len() != 5 {
257            return Err(anyhow!("Expected 5 bytes"));
258        }
259
260        Ok(DeviceAppTimePeriodicityAnsPayload {
261            status: DeviceAppTimePeriodicityAnsPayloadStatus {
262                not_supported: b[0] & 0x01 != 0,
263            },
264            time: {
265                let mut bytes = [0; 4];
266                bytes.copy_from_slice(&b[1..5]);
267                u32::from_le_bytes(bytes)
268            },
269        })
270    }
271
272    fn encode(&self) -> Result<Vec<u8>> {
273        let mut b = vec![0; 5];
274        if self.status.not_supported {
275            b[0] |= 0x01;
276        }
277
278        b[1..5].copy_from_slice(&self.time.to_le_bytes());
279        Ok(b)
280    }
281}
282
283#[derive(Debug, PartialEq, Clone)]
284pub struct DeviceAppTimePeriodicityAnsPayloadStatus {
285    pub not_supported: bool,
286}
287
288#[derive(Debug, PartialEq, Clone)]
289pub struct ForceDeviceResyncCmdPayload {
290    pub force_conf: ForceDeviceResyncCmdPayloadForceConf,
291}
292
293impl PayloadCodec for ForceDeviceResyncCmdPayload {
294    fn decode(b: &[u8]) -> Result<Self> {
295        if b.len() != 1 {
296            return Err(anyhow!("Expected 1 byte"));
297        }
298
299        Ok(ForceDeviceResyncCmdPayload {
300            force_conf: ForceDeviceResyncCmdPayloadForceConf {
301                nb_transmissions: b[0] & 0x07,
302            },
303        })
304    }
305
306    fn encode(&self) -> Result<Vec<u8>> {
307        if self.force_conf.nb_transmissions > 7 {
308            return Err(anyhow!("Max nb_transmissions is 7"));
309        }
310
311        Ok(vec![self.force_conf.nb_transmissions])
312    }
313}
314
315#[derive(Debug, PartialEq, Clone)]
316pub struct ForceDeviceResyncCmdPayloadForceConf {
317    pub nb_transmissions: u8,
318}
319
320#[cfg(test)]
321mod test {
322    use super::*;
323
324    struct CommandTest {
325        name: String,
326        uplink: bool,
327        command: Payload,
328        bytes: Vec<u8>,
329        expected_error: Option<String>,
330    }
331
332    #[test]
333    fn test_package_version_req() {
334        let encode_tests = [CommandTest {
335            name: "encode PackageVersionReq".into(),
336            uplink: false,
337            command: Payload::PackageVersionReq,
338            bytes: vec![0x00],
339            expected_error: None,
340        }];
341
342        let decode_tests = [CommandTest {
343            name: "decode PackageVersionReq".into(),
344            uplink: false,
345            command: Payload::PackageVersionReq,
346            bytes: vec![0x00],
347            expected_error: None,
348        }];
349
350        run_tests_encode(&encode_tests);
351        run_tests_decode(&decode_tests);
352    }
353
354    #[test]
355    fn test_package_version_ans() {
356        let encode_tests = [CommandTest {
357            name: "encode PackageVersionAns".into(),
358            uplink: true,
359            command: Payload::PackageVersionAns(PackageVersionAnsPayload {
360                package_identifier: 1,
361                package_version: 1,
362            }),
363            bytes: vec![0x00, 0x01, 0x01],
364            expected_error: None,
365        }];
366
367        let decode_tests = [CommandTest {
368            name: "decode PackageVersionAns".into(),
369            uplink: true,
370            command: Payload::PackageVersionAns(PackageVersionAnsPayload {
371                package_identifier: 1,
372                package_version: 1,
373            }),
374            bytes: vec![0x00, 0x01, 0x01],
375            expected_error: None,
376        }];
377
378        run_tests_encode(&encode_tests);
379        run_tests_decode(&decode_tests);
380    }
381
382    #[test]
383    fn test_app_time_req() {
384        let encode_tests = [CommandTest {
385            name: "encode AppTimeReq".into(),
386            uplink: true,
387            command: Payload::AppTimeReq(AppTimeReqPayload {
388                device_time: 1024,
389                param: AppTimeReqPayloadParam {
390                    token_req: 15,
391                    ans_required: true,
392                },
393            }),
394            bytes: vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x1f],
395            expected_error: None,
396        }];
397
398        let decode_tests = [CommandTest {
399            name: "decode AppTimeReq".into(),
400            uplink: true,
401            command: Payload::AppTimeReq(AppTimeReqPayload {
402                device_time: 1024,
403                param: AppTimeReqPayloadParam {
404                    token_req: 15,
405                    ans_required: true,
406                },
407            }),
408            bytes: vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x1f],
409            expected_error: None,
410        }];
411
412        run_tests_encode(&encode_tests);
413        run_tests_decode(&decode_tests);
414    }
415
416    #[test]
417    fn test_app_time_ans() {
418        let encode_tests = [CommandTest {
419            name: "encode AppTimeAns".into(),
420            uplink: false,
421            command: Payload::AppTimeAns(AppTimeAnsPayload {
422                time_correction: 1024,
423                param: AppTimeAnsPayloadParam { token_ans: 15 },
424            }),
425            bytes: vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x0f],
426            expected_error: None,
427        }];
428
429        let decode_tests = [CommandTest {
430            name: "decode AppTimeAns".into(),
431            uplink: false,
432            command: Payload::AppTimeAns(AppTimeAnsPayload {
433                time_correction: 1024,
434                param: AppTimeAnsPayloadParam { token_ans: 15 },
435            }),
436            bytes: vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x0f],
437            expected_error: None,
438        }];
439
440        run_tests_encode(&encode_tests);
441        run_tests_decode(&decode_tests);
442    }
443
444    #[test]
445    fn test_device_app_time_periodicity_req() {
446        let encode_tests = [CommandTest {
447            name: "encode DeviceAppTimePeropdicityReq".into(),
448            uplink: false,
449            command: Payload::DeviceAppTimePeropdicityReq(DeviceAppTimePeriodicityReqPayload {
450                period: 15,
451            }),
452            bytes: vec![0x02, 0x0f],
453            expected_error: None,
454        }];
455
456        let decode_tests = [CommandTest {
457            name: "decode DeviceAppTimePeropdicityReq".into(),
458            uplink: false,
459            command: Payload::DeviceAppTimePeropdicityReq(DeviceAppTimePeriodicityReqPayload {
460                period: 15,
461            }),
462            bytes: vec![0x02, 0x0f],
463            expected_error: None,
464        }];
465
466        run_tests_encode(&encode_tests);
467        run_tests_decode(&decode_tests);
468    }
469
470    #[test]
471    fn test_device_app_time_periodicity_ans() {
472        let encode_tests = [CommandTest {
473            name: "encode DeviceAppTimePeropdicityAns".into(),
474            uplink: true,
475            command: Payload::DeviceAppTimePeropdicityAns(DeviceAppTimePeriodicityAnsPayload {
476                status: DeviceAppTimePeriodicityAnsPayloadStatus {
477                    not_supported: true,
478                },
479                time: 1024,
480            }),
481            bytes: vec![0x02, 0x01, 0x00, 0x04, 0x00, 0x00],
482            expected_error: None,
483        }];
484
485        let decode_tests = [CommandTest {
486            name: "decode DeviceAppTimePeropdicityAns".into(),
487            uplink: true,
488            command: Payload::DeviceAppTimePeropdicityAns(DeviceAppTimePeriodicityAnsPayload {
489                status: DeviceAppTimePeriodicityAnsPayloadStatus {
490                    not_supported: true,
491                },
492                time: 1024,
493            }),
494            bytes: vec![0x02, 0x01, 0x00, 0x04, 0x00, 0x00],
495            expected_error: None,
496        }];
497
498        run_tests_encode(&encode_tests);
499        run_tests_decode(&decode_tests);
500    }
501
502    #[test]
503    fn test_force_device_resync_req() {
504        let encode_tests = [CommandTest {
505            name: "encode ForceDeviceResyncCmd".into(),
506            uplink: false,
507            command: Payload::ForceDeviceResyncCmd(ForceDeviceResyncCmdPayload {
508                force_conf: ForceDeviceResyncCmdPayloadForceConf {
509                    nb_transmissions: 7,
510                },
511            }),
512            bytes: vec![0x03, 0x07],
513            expected_error: None,
514        }];
515
516        let decode_tests = [CommandTest {
517            name: "decode ForceDeviceResyncCmd".into(),
518            uplink: false,
519            command: Payload::ForceDeviceResyncCmd(ForceDeviceResyncCmdPayload {
520                force_conf: ForceDeviceResyncCmdPayloadForceConf {
521                    nb_transmissions: 7,
522                },
523            }),
524            bytes: vec![0x03, 0x07],
525            expected_error: None,
526        }];
527
528        run_tests_encode(&encode_tests);
529        run_tests_decode(&decode_tests);
530    }
531
532    fn run_tests_encode(tests: &[CommandTest]) {
533        for tst in tests {
534            println!("> {}", tst.name);
535            let resp = tst.command.to_vec();
536            if let Some(e) = &tst.expected_error {
537                assert!(resp.is_err());
538                assert_eq!(e, &resp.err().unwrap().to_string());
539            } else {
540                assert_eq!(tst.bytes, resp.unwrap());
541            }
542        }
543    }
544
545    fn run_tests_decode(tests: &[CommandTest]) {
546        for tst in tests {
547            println!("> {}", tst.name);
548            let resp = Payload::from_slice(tst.uplink, &tst.bytes);
549            if let Some(e) = &tst.expected_error {
550                assert!(resp.is_err());
551                assert_eq!(e, &resp.err().unwrap().to_string());
552            } else {
553                assert_eq!(tst.command, resp.unwrap());
554            }
555        }
556    }
557}