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}