resol_vbus/
packet.rs

1use std::{
2    fmt,
3    hash::{Hash, Hasher},
4};
5
6use crate::{error::Result, header::Header, id_hash::IdHash};
7
8/// A tuple of identification information about a `Packet` value.
9///
10/// It consists of the following parts:
11///
12/// - the channel
13/// - the destination address
14/// - the source address
15/// - the command
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct PacketId(pub u8, pub u16, pub u16, pub u16);
18
19impl PacketId {
20    /// Create an ID string for the given `PacketId` value.
21    ///
22    /// # Examples
23    ///
24    /// ```rust
25    /// use resol_vbus::PacketId;
26    ///
27    /// assert_eq!("11_1213_1415_10_1718", PacketId(0x11, 0x1213, 0x1415, 0x1718).packet_id_string());
28    /// ```
29    pub fn packet_id_string(&self) -> String {
30        format!(
31            "{:02X}_{:04X}_{:04X}_10_{:04X}",
32            self.0, self.1, self.2, self.3
33        )
34    }
35}
36
37/// A trait to get a `PacketId` for a given value.
38pub trait ToPacketId {
39    /// Get the `PacketId` for a given value.
40    fn to_packet_id(&self) -> Result<PacketId>;
41}
42
43impl ToPacketId for PacketId {
44    fn to_packet_id(&self) -> Result<PacketId> {
45        Ok(*self)
46    }
47}
48
49impl ToPacketId for str {
50    /// Parse the string into a packet ID tuple.
51    ///
52    /// ## Examples
53    ///
54    /// ```rust
55    /// use resol_vbus::{PacketId, ToPacketId};
56    ///
57    /// assert_eq!(PacketId(0x11, 0x1213, 0x1415, 0x1718), "11_1213_1415_10_1718".to_packet_id().unwrap());
58    /// ```
59    fn to_packet_id(&self) -> Result<PacketId> {
60        let is_not_hex_char = |c| !matches!(c, '0'..='9' | 'A'..='F' | 'a'..='f');
61
62        if self.len() < 20 {
63            return Err(format!("Invalid length of input {:?}", self).into());
64        }
65
66        let mut parts = self.split('_');
67
68        let channel_str = parts.next().unwrap();
69        if channel_str.len() != 2 {
70            return Err(format!("Invalid length of channel {:?}", channel_str).into());
71        }
72        if channel_str.chars().any(&is_not_hex_char) {
73            return Err(format!("Invalid characters in channel {:?}", channel_str).into());
74        }
75        let channel = u8::from_str_radix(channel_str, 16).unwrap();
76
77        let destination_address_str = parts.next().unwrap();
78        if destination_address_str.len() != 4 {
79            return Err(format!(
80                "Invalid length of destination address {:?}",
81                destination_address_str
82            )
83            .into());
84        }
85        if destination_address_str.chars().any(&is_not_hex_char) {
86            return Err(format!(
87                "Invalid characters in destination address {:?}",
88                destination_address_str
89            )
90            .into());
91        }
92        let destination_address = u16::from_str_radix(destination_address_str, 16).unwrap();
93
94        let source_address_str = parts.next().unwrap();
95        if source_address_str.len() != 4 {
96            return Err(
97                format!("Invalid length of source address {:?}", source_address_str).into(),
98            );
99        }
100        if source_address_str.chars().any(&is_not_hex_char) {
101            return Err(format!(
102                "Invalid characters in source address {:?}",
103                source_address_str
104            )
105            .into());
106        }
107        let source_address = u16::from_str_radix(source_address_str, 16).unwrap();
108
109        let protocol_version_str = parts.next().unwrap();
110        if protocol_version_str.len() != 2 {
111            return Err(format!(
112                "Invalid length of protocol version {:?}",
113                protocol_version_str
114            )
115            .into());
116        }
117        if protocol_version_str.chars().any(&is_not_hex_char) {
118            return Err(format!(
119                "Invalid characters in protocol version {:?}",
120                protocol_version_str
121            )
122            .into());
123        }
124        let protocol_version = u8::from_str_radix(protocol_version_str, 16).unwrap();
125        if (protocol_version & 0xF0) != 0x10 {
126            return Err(format!("Unsupported protocol version 0x{:02X}", protocol_version).into());
127        }
128
129        let command_str = parts.next().unwrap();
130        if command_str.len() != 4 {
131            return Err(format!("Invalid length of command {:?}", command_str).into());
132        }
133        if command_str.chars().any(&is_not_hex_char) {
134            return Err(format!("Invalid characters in command {:?}", command_str).into());
135        }
136        let command = u16::from_str_radix(command_str, 16).unwrap();
137
138        Ok(PacketId(
139            channel,
140            destination_address,
141            source_address,
142            command,
143        ))
144    }
145}
146
147/// A tuple of identification information about a field in a `Packet` value.
148///
149/// It consists of the following parts:
150///
151/// - the packet ID tuple (channel, destination address, source address and command)
152/// - the field ID
153#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
154pub struct PacketFieldId<'a>(pub PacketId, pub &'a str);
155
156impl<'a> PacketFieldId<'a> {
157    /// Get the packet ID string for a given `PacketFieldId` value.
158    ///
159    /// ## Examples
160    ///
161    /// ```rust
162    /// use resol_vbus::{PacketId, PacketFieldId};
163    ///
164    /// let packet_field_id = PacketFieldId(PacketId(0x11, 0x1213, 0x1415, 0x1718), "012_4_0");
165    /// assert_eq!("11_1213_1415_10_1718", packet_field_id.packet_id_string());
166    /// ```
167    pub fn packet_id_string(&self) -> String {
168        self.0.packet_id_string()
169    }
170
171    /// Get the packet field ID string for a given `PacketFieldId` value.
172    ///
173    /// ## Examples
174    ///
175    /// ```rust
176    /// use resol_vbus::{PacketId, PacketFieldId};
177    ///
178    /// let packet_field_id = PacketFieldId(PacketId(0x11, 0x1213, 0x1415, 0x1718), "012_4_0");
179    /// assert_eq!("11_1213_1415_10_1718_012_4_0", packet_field_id.packet_field_id_string());
180    /// ```
181    pub fn packet_field_id_string(&self) -> String {
182        format!("{}_{}", self.packet_id_string(), self.1)
183    }
184}
185
186/// A trait to get a `PacketFieldId` for a given value.
187pub trait ToPacketFieldId {
188    /// Get the `PacketFieldId` for a given value.
189    fn to_packet_field_id(&self) -> Result<PacketFieldId<'_>>;
190}
191
192impl<'a> ToPacketFieldId for PacketFieldId<'a> {
193    fn to_packet_field_id(&self) -> Result<PacketFieldId<'_>> {
194        Ok(*self)
195    }
196}
197
198impl ToPacketFieldId for str {
199    /// Parse the string into a packet field ID tuple.
200    ///
201    /// ## Examples
202    ///
203    /// ```rust
204    /// use resol_vbus::{PacketId, PacketFieldId, ToPacketFieldId};
205    ///
206    /// assert_eq!(PacketFieldId(PacketId(0x11, 0x1213, 0x1415, 0x1718), "012_4_0"), "11_1213_1415_10_1718_012_4_0".to_packet_field_id().unwrap());
207    /// ```
208    fn to_packet_field_id(&self) -> Result<PacketFieldId<'_>> {
209        if self.len() < 21 {
210            return Err(format!("Invalid length of input {:?}", self).into());
211        }
212
213        let packet_id = self.to_packet_id()?;
214
215        let field_id = &self[21..];
216
217        Ok(PacketFieldId(packet_id, field_id))
218    }
219}
220
221/// The `Packet` type stores information according to the VBus protocol version 1.x.
222///
223/// Packets are used to transmit larger amount of information (up to 508 bytes of payload) relying
224/// on the fact that both sides of the communication know how that payload is structured and how to
225/// extract the information out of it.
226///
227/// ## The "identity" of `Packet` values
228///
229/// As described in [the corresponding section of the `Header` struct][1] VBus data types use
230/// some of their fields as part of their "identity". In addition to the fields used by the
231/// `Header` type the `Packet` type also respects the `command` field. That means that two `Packet`
232/// with differing `timestamp`, `frame_count` and `frame_data` fields are still considered
233/// "identical", if the other fields match.
234///
235/// [1]: struct.Header.html#the-identity-of-header-values
236///
237/// ## The payload of `Packet` values
238///
239/// The VBus Protocol Specification describes that all the fields used for the `Packet`'s
240/// "identity" can also be used to determine the structure of the payload contained in the
241/// `frame_data` field. The [`Specification`][2] type can be used to decode the payload
242/// information.
243///
244/// [2]: struct.Specification.html
245pub struct Packet {
246    /// The shared `Header` of all VBus protocol types.
247    pub header: Header,
248
249    /// The command of this `Packet`.
250    pub command: u16,
251
252    /// The number of 4-byte frames attached to this `Packet`.
253    pub frame_count: u8,
254
255    /// The actual data from the frames attached to this `Packet`.
256    pub frame_data: [u8; 508],
257}
258
259impl Packet {
260    /// Return the length of the valid area of the `frame_data`.
261    ///
262    /// # Examples
263    ///
264    /// ```rust
265    /// use resol_vbus::{Header, Packet};
266    /// use resol_vbus::utils::utc_timestamp;
267    ///
268    /// let packet = Packet {
269    ///     header: Header {
270    ///         timestamp: utc_timestamp(1485688933),
271    ///         channel: 0x11,
272    ///         destination_address: 0x1213,
273    ///         source_address: 0x1415,
274    ///         protocol_version: 0x16,
275    ///     },
276    ///     command: 0x1718,
277    ///     frame_count: 0x19,
278    ///     frame_data: [0u8; 508],
279    /// };
280    ///
281    /// assert_eq!(100, packet.valid_frame_data_len());
282    /// ```
283    pub fn valid_frame_data_len(&self) -> usize {
284        self.frame_count as usize * 4
285    }
286
287    /// Return the valid area of the `frame_data` immutably.
288    ///
289    /// # Examples
290    ///
291    /// ```rust
292    /// use resol_vbus::{Header, Packet};
293    /// use resol_vbus::utils::utc_timestamp;
294    ///
295    /// let packet = Packet {
296    ///     header: Header {
297    ///         timestamp: utc_timestamp(1485688933),
298    ///         channel: 0x11,
299    ///         destination_address: 0x1213,
300    ///         source_address: 0x1415,
301    ///         protocol_version: 0x16,
302    ///     },
303    ///     command: 0x1718,
304    ///     frame_count: 0x19,
305    ///     frame_data: [0u8; 508],
306    /// };
307    ///
308    /// assert_eq!(508, packet.frame_data.len());
309    /// assert_eq!(100, packet.valid_frame_data().len());
310    /// ```
311    pub fn valid_frame_data(&self) -> &[u8] {
312        let end = self.valid_frame_data_len();
313        &self.frame_data[0..end]
314    }
315
316    /// Return the valid area of the `frame_data` mutably.
317    ///
318    /// # Examples
319    ///
320    /// ```rust
321    /// use resol_vbus::{Header, Packet};
322    /// use resol_vbus::utils::utc_timestamp;
323    ///
324    /// let mut packet = Packet {
325    ///     header: Header {
326    ///         timestamp: utc_timestamp(1485688933),
327    ///         channel: 0x11,
328    ///         destination_address: 0x1213,
329    ///         source_address: 0x1415,
330    ///         protocol_version: 0x16,
331    ///     },
332    ///     command: 0x1718,
333    ///     frame_count: 0x19,
334    ///     frame_data: [0u8; 508],
335    /// };
336    ///
337    /// assert_eq!(508, packet.frame_data.len());
338    /// assert_eq!(100, packet.valid_frame_data_mut().len());
339    /// ```
340    pub fn valid_frame_data_mut(&mut self) -> &mut [u8] {
341        let end = self.valid_frame_data_len();
342        &mut self.frame_data[0..end]
343    }
344
345    /// Returns identification information about this `Packet`.
346    ///
347    /// The result contains all fields that count towards the "identity" of the `Packet` with the
348    /// exception of the `protocol_version` (since it must be 1.x to be a `Packet` anyway):
349    ///
350    /// - `channel`
351    /// - `destination_address`
352    /// - `source_address`
353    /// - `command`
354    ///
355    /// # Examples
356    ///
357    /// ```rust
358    /// use resol_vbus::{Header, Packet, PacketId};
359    /// use resol_vbus::utils::utc_timestamp;
360    ///
361    /// let packet = Packet {
362    ///     header: Header {
363    ///         timestamp: utc_timestamp(1485688933),
364    ///         channel: 0x11,
365    ///         destination_address: 0x1213,
366    ///         source_address: 0x1415,
367    ///         protocol_version: 0x16,
368    ///     },
369    ///     command: 0x1718,
370    ///     frame_count: 0x19,
371    ///     frame_data: [0u8; 508],
372    /// };
373    ///
374    /// assert_eq!(PacketId(0x11, 0x1213, 0x1415, 0x1718), packet.packet_id());
375    /// ```
376    pub fn packet_id(&self) -> PacketId {
377        PacketId(
378            self.header.channel,
379            self.header.destination_address,
380            self.header.source_address,
381            self.command,
382        )
383    }
384
385    /// Creates an identification string for this `Packet`.
386    ///
387    /// The string contains all fields that count towards the "identity" of the `Packet`:
388    ///
389    /// - `channel`
390    /// - `destination_address`
391    /// - `source_address`
392    /// - `protocol_version`
393    /// - `command`
394    ///
395    /// # Examples
396    ///
397    /// ```rust
398    /// use resol_vbus::{Header, Packet};
399    /// use resol_vbus::utils::utc_timestamp;
400    ///
401    /// let packet = Packet {
402    ///     header: Header {
403    ///         timestamp: utc_timestamp(1485688933),
404    ///         channel: 0x11,
405    ///         destination_address: 0x1213,
406    ///         source_address: 0x1415,
407    ///         protocol_version: 0x16,
408    ///     },
409    ///     command: 0x1718,
410    ///     frame_count: 0x19,
411    ///     frame_data: [0u8; 508],
412    /// };
413    ///
414    /// assert_eq!("11_1213_1415_16_1718", packet.id_string());
415    /// ```
416    pub fn id_string(&self) -> String {
417        format!("{}_{:04X}", self.header.id_string(), self.command)
418    }
419}
420
421impl IdHash for Packet {
422    /// Returns an identification hash for this `Packet`.
423    ///
424    /// The hash contains all fields that count towards the "identity" of the `Packet`:
425    ///
426    /// - `channel`
427    /// - `destination_address`
428    /// - `source_address`
429    /// - `protocol_version`
430    /// - `command`
431    ///
432    /// # Examples
433    ///
434    /// ```rust
435    /// use resol_vbus::{Header, Packet, id_hash};
436    /// use resol_vbus::utils::utc_timestamp;
437    ///
438    /// let packet = Packet {
439    ///     header: Header {
440    ///         timestamp: utc_timestamp(1485688933),
441    ///         channel: 0x11,
442    ///         destination_address: 0x1213,
443    ///         source_address: 0x1415,
444    ///         protocol_version: 0x16,
445    ///     },
446    ///     command: 0x1718,
447    ///     frame_count: 0x19,
448    ///     frame_data: [0u8; 508],
449    /// };
450    ///
451    /// assert_eq!(2215810099849021132, id_hash(&packet));
452    /// ```
453    fn id_hash<H: Hasher>(&self, h: &mut H) {
454        self.header.id_hash(h);
455        self.command.hash(h);
456    }
457}
458
459impl ToPacketId for Packet {
460    fn to_packet_id(&self) -> Result<PacketId> {
461        Ok(self.packet_id())
462    }
463}
464
465impl fmt::Debug for Packet {
466    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467        f.debug_struct("Packet")
468            .field("header", &self.header)
469            .field("command", &format_args!("0x{:04X}", self.command))
470            .field("frame_count", &format_args!("0x{:02X}", self.frame_count))
471            .field("frame_data", &format_args!("..."))
472            .finish()
473    }
474}
475
476impl Clone for Packet {
477    fn clone(&self) -> Self {
478        let mut frame_data = [0u8; 508];
479        frame_data.copy_from_slice(&self.frame_data);
480
481        Packet {
482            header: self.header.clone(),
483            command: self.command,
484            frame_count: self.frame_count,
485            frame_data,
486        }
487    }
488}
489
490impl AsRef<Header> for Packet {
491    fn as_ref(&self) -> &Header {
492        &self.header
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499
500    use crate::{error::Error, utils::utc_timestamp};
501
502    #[test]
503    fn test_packet_id_string() {
504        assert_eq!(
505            "11_1213_1415_10_1718",
506            PacketId(0x11, 0x1213, 0x1415, 0x1718).packet_id_string()
507        );
508    }
509
510    #[test]
511    fn test_packet_id_to_packet_id() {
512        let packet_id = PacketId(0x11, 0x1213, 0x1415, 0x1718);
513
514        let result = packet_id.to_packet_id().expect("Must not fail");
515
516        assert_eq!(packet_id, result);
517    }
518
519    #[test]
520    fn test_str_to_packet_id() {
521        assert_eq!(
522            PacketId(0x11, 0x1213, 0x1415, 0x1718),
523            "11_1213_1415_10_1718".to_packet_id().unwrap()
524        );
525        assert_eq!(
526            PacketId(0x11, 0x1213, 0x1415, 0x1718),
527            "11_1213_1415_10_1718_XXX_X_X".to_packet_id().unwrap()
528        );
529        assert_eq!(
530            Error::new("Invalid length of input \"11_1213_1415_10_171\""),
531            "11_1213_1415_10_171".to_packet_id().unwrap_err()
532        );
533        assert_eq!(
534            Error::new("Invalid length of channel \"111\""),
535            "111_1213_1415_10_1718".to_packet_id().unwrap_err()
536        );
537        assert_eq!(
538            Error::new("Invalid characters in channel \"1G\""),
539            "1G_1213_1415_10_1718".to_packet_id().unwrap_err()
540        );
541        assert_eq!(
542            Error::new("Invalid length of destination address \"12131\""),
543            "11_12131_1415_10_1718".to_packet_id().unwrap_err()
544        );
545        assert_eq!(
546            Error::new("Invalid characters in destination address \"121G\""),
547            "11_121G_1415_10_1718".to_packet_id().unwrap_err()
548        );
549        assert_eq!(
550            Error::new("Invalid length of source address \"14151\""),
551            "11_1213_14151_10_1718".to_packet_id().unwrap_err()
552        );
553        assert_eq!(
554            Error::new("Invalid characters in source address \"141G\""),
555            "11_1213_141G_10_1718".to_packet_id().unwrap_err()
556        );
557        assert_eq!(
558            Error::new("Invalid length of protocol version \"101\""),
559            "11_1213_1415_101_1718".to_packet_id().unwrap_err()
560        );
561        assert_eq!(
562            Error::new("Invalid characters in protocol version \"1G\""),
563            "11_1213_1415_1G_1718".to_packet_id().unwrap_err()
564        );
565        assert_eq!(
566            Error::new("Unsupported protocol version 0x20"),
567            "11_1213_1415_20_1718".to_packet_id().unwrap_err()
568        );
569        assert_eq!(
570            Error::new("Invalid length of command \"17181\""),
571            "11_1213_1415_10_17181".to_packet_id().unwrap_err()
572        );
573        assert_eq!(
574            Error::new("Invalid characters in command \"171G\""),
575            "11_1213_1415_10_171G".to_packet_id().unwrap_err()
576        );
577    }
578
579    #[test]
580    fn test_packet_field_id_packet_id_string() {
581        let packet_field_id = PacketFieldId(PacketId(0x11, 0x1213, 0x1415, 0x1718), "019_2_0");
582
583        let result = packet_field_id.packet_id_string();
584
585        assert_eq!("11_1213_1415_10_1718", result);
586    }
587
588    #[test]
589    fn test_packet_field_id_packet_field_id_string() {
590        let packet_field_id = PacketFieldId(PacketId(0x11, 0x1213, 0x1415, 0x1718), "019_2_0");
591
592        let result = packet_field_id.packet_field_id_string();
593
594        assert_eq!("11_1213_1415_10_1718_019_2_0", result);
595    }
596
597    #[test]
598    fn test_packet_field_id_to_packet_field_id() {
599        let packet_field_id = PacketFieldId(PacketId(0x11, 0x1213, 0x1415, 0x1718), "019_2_0");
600
601        let result = packet_field_id.to_packet_field_id().expect("Must not fail");
602
603        assert_eq!(packet_field_id, result);
604    }
605
606    #[test]
607    fn test_str_to_packet_field_id() {
608        let packet_field_id_string = "11_1213_1415_10_1718_019_2_0";
609
610        let result = packet_field_id_string
611            .to_packet_field_id()
612            .expect("Must not fail");
613
614        assert_eq!(packet_field_id_string, result.packet_field_id_string());
615
616        let result = "11_1213_1415_10_1718".to_packet_field_id().unwrap_err();
617
618        assert_eq!(
619            Error::new("Invalid length of input \"11_1213_1415_10_1718\""),
620            result
621        );
622    }
623
624    #[test]
625    fn test_debug_fmt() {
626        let packet = Packet {
627            header: Header {
628                timestamp: utc_timestamp(1485688933),
629                channel: 0x11,
630                destination_address: 0x1213,
631                source_address: 0x1415,
632                protocol_version: 0x16,
633            },
634            command: 0x1718,
635            frame_count: 0x19,
636            frame_data: [0u8; 508],
637        };
638
639        let result = format!("{:?}", packet);
640
641        assert_eq!("Packet { header: Header { timestamp: 2017-01-29T11:22:13Z, channel: 0x11, destination_address: 0x1213, source_address: 0x1415, protocol_version: 0x16 }, command: 0x1718, frame_count: 0x19, frame_data: ... }", result);
642    }
643}