1use linux_cec_macros::Operand;
7use linux_cec_sys::{constants, VendorId as SysVendorId, CEC_TX_STATUS};
8use nix::errno::Errno;
9use num_enum::{IntoPrimitive, TryFromPrimitive, TryFromPrimitiveError};
10use std::fmt::{self, Debug, Display, Formatter};
11use std::io;
12use std::ops::{Add, Deref, RangeInclusive};
13use std::str::FromStr;
14use std::string::ToString;
15use std::time::Duration;
16use strum::{Display, EnumString};
17use thiserror::Error;
18
19pub mod cdc;
20pub mod device;
21pub mod message;
22pub mod operand;
23
24#[cfg(feature = "async")]
25mod async_support;
26
27pub(crate) mod ioctls;
28
29pub use linux_cec_sys as sys;
30pub use linux_cec_sys::Timestamp;
31
32#[derive(
35 Clone,
36 Copy,
37 Debug,
38 Default,
39 PartialEq,
40 Hash,
41 IntoPrimitive,
42 TryFromPrimitive,
43 Display,
44 EnumString,
45)]
46#[repr(u8)]
47pub enum LogicalAddress {
48 #[strum(serialize = "tv", serialize = "0")]
49 Tv = constants::CEC_LOG_ADDR_TV,
50 #[strum(
51 serialize = "recording-device1",
52 serialize = "recording-device-1",
53 serialize = "recording-device",
54 serialize = "1"
55 )]
56 RecordingDevice1 = constants::CEC_LOG_ADDR_RECORD_1,
57 #[strum(
58 serialize = "recording-device2",
59 serialize = "recording-device-2",
60 serialize = "2"
61 )]
62 RecordingDevice2 = constants::CEC_LOG_ADDR_RECORD_2,
63 #[strum(
64 serialize = "tuner1",
65 serialize = "tuner-1",
66 serialize = "tuner",
67 serialize = "3"
68 )]
69 Tuner1 = constants::CEC_LOG_ADDR_TUNER_1,
70 #[strum(
71 serialize = "playback-device1",
72 serialize = "playback-device-1",
73 serialize = "playback-device",
74 serialize = "4"
75 )]
76 PlaybackDevice1 = constants::CEC_LOG_ADDR_PLAYBACK_1,
77 #[strum(serialize = "audio-system", serialize = "5")]
78 AudioSystem = constants::CEC_LOG_ADDR_AUDIOSYSTEM,
79 #[strum(serialize = "tuner2", serialize = "tuner-2", serialize = "6")]
80 Tuner2 = constants::CEC_LOG_ADDR_TUNER_2,
81 #[strum(serialize = "tuner3", serialize = "tuner-3", serialize = "7")]
82 Tuner3 = constants::CEC_LOG_ADDR_TUNER_3,
83 #[strum(
84 serialize = "playback-device2",
85 serialize = "playback-device-2",
86 serialize = "8"
87 )]
88 PlaybackDevice2 = constants::CEC_LOG_ADDR_PLAYBACK_2,
89 #[strum(
90 serialize = "recording-device3",
91 serialize = "recording-device-3",
92 serialize = "9"
93 )]
94 RecordingDevice3 = constants::CEC_LOG_ADDR_RECORD_3,
95 #[strum(
96 serialize = "tuner4",
97 serialize = "tuner-4",
98 serialize = "10",
99 serialize = "a"
100 )]
101 Tuner4 = constants::CEC_LOG_ADDR_TUNER_4,
102 #[strum(
103 serialize = "playback-device3",
104 serialize = "playback-device-3",
105 serialize = "11",
106 serialize = "b"
107 )]
108 PlaybackDevice3 = constants::CEC_LOG_ADDR_PLAYBACK_3,
109 #[strum(
110 serialize = "backup1",
111 serialize = "backup-1",
112 serialize = "12",
113 serialize = "c"
114 )]
115 Backup1 = constants::CEC_LOG_ADDR_BACKUP_1,
116 #[strum(
117 serialize = "backup2",
118 serialize = "backup-2",
119 serialize = "13",
120 serialize = "d"
121 )]
122 Backup2 = constants::CEC_LOG_ADDR_BACKUP_2,
123 #[strum(serialize = "specific", serialize = "14", serialize = "e")]
124 Specific = constants::CEC_LOG_ADDR_SPECIFIC,
125 #[default]
126 #[strum(
127 serialize = "unregistered",
128 serialize = "broadcast",
129 serialize = "15",
130 serialize = "f"
131 )]
132 UnregisteredOrBroadcast = constants::CEC_LOG_ADDR_UNREGISTERED,
133}
134
135impl LogicalAddress {
136 #![allow(non_upper_case_globals)]
137 pub const Unregistered: LogicalAddress = LogicalAddress::UnregisteredOrBroadcast;
139 pub const Broadcast: LogicalAddress = LogicalAddress::UnregisteredOrBroadcast;
141
142 #[must_use]
143 pub fn primary_device_type(self) -> Option<operand::PrimaryDeviceType> {
144 match self {
145 LogicalAddress::Tv => Some(operand::PrimaryDeviceType::Tv),
146 LogicalAddress::RecordingDevice1
147 | LogicalAddress::RecordingDevice2
148 | LogicalAddress::RecordingDevice3 => Some(operand::PrimaryDeviceType::Recording),
149 LogicalAddress::Tuner1
150 | LogicalAddress::Tuner2
151 | LogicalAddress::Tuner3
152 | LogicalAddress::Tuner4 => Some(operand::PrimaryDeviceType::Tuner),
153 LogicalAddress::PlaybackDevice1
154 | LogicalAddress::PlaybackDevice2
155 | LogicalAddress::PlaybackDevice3 => Some(operand::PrimaryDeviceType::Playback),
156 LogicalAddress::AudioSystem => Some(operand::PrimaryDeviceType::Audio),
157 LogicalAddress::Backup1 | LogicalAddress::Backup2 => None,
158 LogicalAddress::Specific => None,
159 LogicalAddress::UnregisteredOrBroadcast => None,
160 }
161 }
162
163 #[must_use]
164 pub fn all_device_types(self) -> operand::AllDeviceTypes {
165 match self {
166 LogicalAddress::Tv => operand::AllDeviceTypes::TV,
167 LogicalAddress::RecordingDevice1
168 | LogicalAddress::RecordingDevice2
169 | LogicalAddress::RecordingDevice3 => operand::AllDeviceTypes::RECORDING,
170 LogicalAddress::Tuner1
171 | LogicalAddress::Tuner2
172 | LogicalAddress::Tuner3
173 | LogicalAddress::Tuner4 => operand::AllDeviceTypes::TUNER,
174 LogicalAddress::PlaybackDevice1
175 | LogicalAddress::PlaybackDevice2
176 | LogicalAddress::PlaybackDevice3 => operand::AllDeviceTypes::PLAYBACK,
177 LogicalAddress::AudioSystem => operand::AllDeviceTypes::AUDIOSYSTEM,
178 LogicalAddress::Backup1 | LogicalAddress::Backup2 => operand::AllDeviceTypes::empty(),
179 LogicalAddress::Specific => operand::AllDeviceTypes::empty(),
180 LogicalAddress::UnregisteredOrBroadcast => operand::AllDeviceTypes::empty(),
181 }
182 }
183
184 #[must_use]
185 pub fn ty(self) -> Option<LogicalAddressType> {
186 match self {
187 LogicalAddress::Tv => Some(LogicalAddressType::Tv),
188 LogicalAddress::RecordingDevice1
189 | LogicalAddress::RecordingDevice2
190 | LogicalAddress::RecordingDevice3 => Some(LogicalAddressType::Record),
191 LogicalAddress::Tuner1
192 | LogicalAddress::Tuner2
193 | LogicalAddress::Tuner3
194 | LogicalAddress::Tuner4 => Some(LogicalAddressType::Tuner),
195 LogicalAddress::PlaybackDevice1
196 | LogicalAddress::PlaybackDevice2
197 | LogicalAddress::PlaybackDevice3 => Some(LogicalAddressType::Playback),
198 LogicalAddress::AudioSystem => Some(LogicalAddressType::AudioSystem),
199 LogicalAddress::Backup1 | LogicalAddress::Backup2 => None,
200 LogicalAddress::Specific => Some(LogicalAddressType::Specific),
201 LogicalAddress::UnregisteredOrBroadcast => Some(LogicalAddressType::Unregistered),
202 }
203 }
204}
205
206#[derive(
209 Clone,
210 Copy,
211 Debug,
212 Default,
213 Eq,
214 PartialEq,
215 Hash,
216 IntoPrimitive,
217 TryFromPrimitive,
218 Display,
219 EnumString,
220)]
221#[strum(serialize_all = "kebab-case")]
222#[repr(u8)]
223pub enum LogicalAddressType {
224 Tv = constants::CEC_LOG_ADDR_TYPE_TV,
225 Record = constants::CEC_LOG_ADDR_TYPE_RECORD,
226 Tuner = constants::CEC_LOG_ADDR_TYPE_TUNER,
227 Playback = constants::CEC_LOG_ADDR_TYPE_PLAYBACK,
228 AudioSystem = constants::CEC_LOG_ADDR_TYPE_AUDIOSYSTEM,
229 Specific = constants::CEC_LOG_ADDR_TYPE_SPECIFIC,
230 #[default]
231 Unregistered = constants::CEC_LOG_ADDR_TYPE_UNREGISTERED,
232}
233
234impl LogicalAddressType {
235 #[must_use]
236 pub fn primary_device_type(self) -> Option<operand::PrimaryDeviceType> {
237 match self {
238 LogicalAddressType::Tv => Some(operand::PrimaryDeviceType::Tv),
239 LogicalAddressType::Record => Some(operand::PrimaryDeviceType::Recording),
240 LogicalAddressType::Tuner => Some(operand::PrimaryDeviceType::Tuner),
241 LogicalAddressType::Playback => Some(operand::PrimaryDeviceType::Playback),
242 LogicalAddressType::AudioSystem => Some(operand::PrimaryDeviceType::Audio),
243 LogicalAddressType::Specific => None,
244 LogicalAddressType::Unregistered => None,
245 }
246 }
247
248 #[must_use]
249 pub fn all_device_types(self) -> operand::AllDeviceTypes {
250 match self {
251 LogicalAddressType::Tv => operand::AllDeviceTypes::TV,
252 LogicalAddressType::Record => operand::AllDeviceTypes::RECORDING,
253 LogicalAddressType::Tuner => operand::AllDeviceTypes::TUNER,
254 LogicalAddressType::Playback => operand::AllDeviceTypes::PLAYBACK,
255 LogicalAddressType::AudioSystem => operand::AllDeviceTypes::AUDIOSYSTEM,
256 LogicalAddressType::Specific | LogicalAddressType::Unregistered => {
257 operand::AllDeviceTypes::empty()
258 }
259 }
260 }
261}
262
263#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
266#[non_exhaustive]
267pub enum InitiatorMode {
268 Disabled,
270 Enabled,
272 Exclusive,
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
280#[non_exhaustive]
281pub enum FollowerMode {
282 Disabled,
284 Enabled,
286 Exclusive,
289 ExclusivePassthru,
292 MonitorPin,
296 Monitor,
301 MonitorAll,
307}
308
309#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
319pub enum AddressingType {
320 Direct,
323 Broadcast,
325 Either,
327}
328
329#[derive(Clone, Debug, PartialEq, Eq)]
330pub enum Range<T: PartialOrd + Clone + Display + Default + Debug> {
331 AtMost(T),
332 AtLeast(T),
333 Exact(T),
334 Only(Vec<T>),
335 Interval { min: T, max: T },
336}
337
338impl Range<usize> {
339 pub fn check(self, val: impl Into<usize>, quantity: &'static str) -> Result<()> {
340 let val: usize = val.into();
341 match self {
342 Range::AtMost(max) if val <= max => Ok(()),
343 Range::AtLeast(min) if val >= min => Ok(()),
344 Range::Exact(x) if val == x => Ok(()),
345 Range::Only(list) if list.contains(&val) => Ok(()),
346 Range::Interval { min, max } if val >= min && val <= max => Ok(()),
347 _ => Err(Error::OutOfRange {
348 got: val,
349 expected: self,
350 quantity,
351 }),
352 }
353 }
354}
355
356impl<T: PartialOrd + Clone + Display + Default + Debug + Eq> From<RangeInclusive<T>> for Range<T> {
357 fn from(val: RangeInclusive<T>) -> Range<T> {
358 Range::Interval {
359 min: val.start().clone(),
360 max: val.end().clone(),
361 }
362 }
363}
364
365impl<T: PartialOrd + Clone + Display + Default + Debug + Eq> Display for Range<T> {
366 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
367 match self {
368 Range::AtMost(max) => f.write_fmt(format_args!("at most {max}")),
369 Range::AtLeast(min) => f.write_fmt(format_args!("at least {min}")),
370 Range::Exact(x) => f.write_fmt(format_args!("{x}")),
371 Range::Only(list) => {
372 let list = list
373 .iter()
374 .map(ToString::to_string)
375 .fold(String::new(), |a, b| {
376 if a.is_empty() {
377 b
378 } else {
379 format!("{a}, {b}")
380 }
381 });
382 f.write_fmt(format_args!("one of {list}"))
383 }
384 Range::Interval { min, max } => f.write_fmt(format_args!("between {min} and {max}")),
385 }
386 }
387}
388
389impl<T: PartialOrd + Clone + Display + Default + Debug + Eq + Add<Output = T> + Copy> Add<T>
390 for Range<T>
391{
392 type Output = Range<T>;
393
394 fn add(self, rhs: T) -> Self::Output {
395 match self {
396 Range::AtMost(max) => Range::AtMost(max + rhs),
397 Range::AtLeast(min) => Range::AtLeast(min + rhs),
398 Range::Exact(x) => Range::Exact(x + rhs),
399 Range::Only(list) => Range::Only(list.into_iter().map(|val| val + rhs).collect()),
400 Range::Interval { min, max } => Range::Interval {
401 min: min + rhs,
402 max: max + rhs,
403 },
404 }
405 }
406}
407
408#[cfg(test)]
409mod test_range {
410 use super::Range;
411
412 #[test]
413 fn test_display_at_most() {
414 assert_eq!(Range::AtMost(10).to_string(), "at most 10");
415 }
416
417 #[test]
418 fn test_display_at_least() {
419 assert_eq!(Range::AtLeast(10).to_string(), "at least 10");
420 }
421
422 #[test]
423 fn test_display_exact() {
424 assert_eq!(Range::Exact(10).to_string(), "10");
425 }
426
427 #[test]
428 fn test_display_only_1() {
429 assert_eq!(Range::Only(vec![10]).to_string(), "one of 10");
430 }
431
432 #[test]
433 fn test_display_only_2() {
434 assert_eq!(Range::Only(vec![10, 11]).to_string(), "one of 10, 11");
435 }
436
437 #[test]
438 fn test_display_only_3() {
439 assert_eq!(
440 Range::Only(vec![10, 11, 12]).to_string(),
441 "one of 10, 11, 12"
442 );
443 }
444
445 #[test]
446 fn test_display_interval() {
447 assert_eq!(
448 Range::Interval { min: 1, max: 10 }.to_string(),
449 "between 1 and 10"
450 );
451 }
452
453 #[test]
454 fn test_add_at_most() {
455 assert_eq!(Range::AtMost(10) + 1, Range::AtMost(11));
456 }
457
458 #[test]
459 fn test_add_at_least() {
460 assert_eq!(Range::AtLeast(10) + 1, Range::AtLeast(11));
461 }
462
463 #[test]
464 fn test_add_exact() {
465 assert_eq!(Range::Exact(10) + 1, Range::Exact(11));
466 }
467
468 #[test]
469 fn test_add_only() {
470 assert_eq!(
471 Range::Only(vec![10, 11, 12]) + 1,
472 Range::Only(vec![11, 12, 13])
473 );
474 }
475
476 #[test]
477 fn test_add_interval() {
478 assert_eq!(
479 Range::Interval { min: 1, max: 10 } + 1,
480 Range::Interval { min: 2, max: 11 }
481 );
482 }
483}
484
485#[derive(Error, Clone, Debug, PartialEq)]
487#[non_exhaustive]
488pub enum Error {
489 #[error("Expected {expected} {quantity}, got {got} {quantity}")]
491 OutOfRange {
492 expected: Range<usize>,
493 got: usize,
494 quantity: &'static str,
495 },
496 #[error("Invalid value {value} for type {ty}")]
498 InvalidValueForType { ty: &'static str, value: String },
499 #[error("The provided data was not valid")]
501 InvalidData,
502 #[error("A timeout occurred")]
504 Timeout,
505 #[error("The request was aborted")]
507 Abort,
508 #[error("The device does not have a logical address")]
510 NoLogicalAddress,
511 #[error("The device was disconnected")]
513 Disconnected,
514 #[error("This device does not support this operation")]
516 Unsupported,
517 #[error("Got unexpected result from system: {0}")]
519 SystemError(Errno),
520 #[error("{0}")]
523 TxError(#[from] TxError),
524 #[error("{0}")]
527 RxError(#[from] RxError),
528 #[error("Unknown error: {0}")]
530 UnknownError(String),
531}
532
533#[derive(Error, Clone, Debug, PartialEq)]
535#[non_exhaustive]
536#[repr(u8)]
537pub enum TxError {
538 #[error("Arbitration was lost")]
539 ArbLost = constants::CEC_TX_STATUS_ARB_LOST,
540 #[error("No acknowledgement")]
541 Nack = constants::CEC_TX_STATUS_NACK,
542 #[error("Low drive on bus")]
543 LowDrive = constants::CEC_TX_STATUS_LOW_DRIVE,
544 #[error("An unknown error occurred")]
545 UnknownError = constants::CEC_TX_STATUS_ERROR,
546 #[error("Maximum retries hit")]
547 MaxRetries = constants::CEC_TX_STATUS_MAX_RETRIES,
548 #[error("The request was aborted")]
549 Aborted = constants::CEC_TX_STATUS_ABORTED,
550 #[error("The request timed out")]
551 Timeout = constants::CEC_TX_STATUS_TIMEOUT,
552}
553
554#[derive(Error, Clone, Debug, PartialEq)]
556#[non_exhaustive]
557#[repr(u8)]
558pub enum RxError {
559 #[error("The request timed out")]
560 Timeout = constants::CEC_RX_STATUS_TIMEOUT,
561 #[error("The requested feature was not present")]
562 FeatureAbort = constants::CEC_RX_STATUS_FEATURE_ABORT,
563 #[error("The request was aborted")]
564 Aborted = constants::CEC_RX_STATUS_ABORTED,
565}
566
567impl Error {
568 pub(crate) fn add_offset(offset: usize) -> impl FnOnce(Error) -> Error {
569 move |error| match error {
570 Error::OutOfRange {
571 got,
572 expected,
573 quantity,
574 } if quantity == "bytes" => Error::OutOfRange {
575 expected: expected + offset,
576 got: got + offset,
577 quantity,
578 },
579 _ => error,
580 }
581 }
582}
583
584impl From<io::Error> for Error {
585 fn from(val: io::Error) -> Error {
586 if let Some(raw) = val.raw_os_error() {
587 Errno::from_raw(raw).into()
588 } else {
589 Error::UnknownError(format!("{val}"))
590 }
591 }
592}
593
594impl From<Errno> for Error {
595 fn from(val: Errno) -> Error {
596 match val {
597 Errno::EINVAL => Error::InvalidData,
598 Errno::ETIMEDOUT => Error::Timeout,
599 Errno::ENODEV => Error::Disconnected,
600 Errno::ENONET => Error::NoLogicalAddress,
601 Errno::ENOTTY => Error::Unsupported,
602 x => Error::SystemError(x),
603 }
604 }
605}
606
607impl From<CEC_TX_STATUS> for Error {
608 fn from(tx_status: CEC_TX_STATUS) -> Error {
609 if tx_status.contains(CEC_TX_STATUS::TIMEOUT) {
610 return Error::Timeout;
611 }
612 if tx_status.contains(CEC_TX_STATUS::ABORTED) {
613 return Error::Abort;
614 }
615 if tx_status.contains(CEC_TX_STATUS::ARB_LOST) {
616 return TxError::ArbLost.into();
617 }
618 if tx_status.contains(CEC_TX_STATUS::NACK) {
619 return TxError::Nack.into();
620 }
621 if tx_status.contains(CEC_TX_STATUS::LOW_DRIVE) {
622 return TxError::LowDrive.into();
623 }
624 if tx_status.contains(CEC_TX_STATUS::MAX_RETRIES) {
625 return TxError::MaxRetries.into();
626 }
627 Error::UnknownError(format!("{tx_status:?}"))
628 }
629}
630
631impl<T: TryFromPrimitive> From<TryFromPrimitiveError<T>> for Error {
632 fn from(val: TryFromPrimitiveError<T>) -> Error {
633 Error::InvalidValueForType {
634 ty: T::NAME,
635 value: format!("{:?}", val.number),
636 }
637 }
638}
639
640#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
643#[repr(transparent)]
644pub struct PhysicalAddress(pub(crate) u16);
645
646impl PhysicalAddress {
647 pub const ROOT: PhysicalAddress = PhysicalAddress(0x0000);
649
650 pub const INVALID: PhysicalAddress = PhysicalAddress(0xFFFF);
652
653 #[must_use]
655 #[inline]
656 pub fn is_valid(&self) -> bool {
657 self.0 != 0xFFFF
658 }
659
660 #[must_use]
662 #[inline]
663 pub fn is_root(&self) -> bool {
664 self.0 == 0x0000
665 }
666}
667
668impl Display for PhysicalAddress {
669 #[inline]
670 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
671 f.write_fmt(format_args!(
672 "{:x}.{:x}.{:x}.{:x}",
673 self.0 >> 12,
674 (self.0 >> 8) & 0xF,
675 (self.0 >> 4) & 0xF,
676 self.0 & 0xF
677 ))
678 }
679}
680
681impl Default for PhysicalAddress {
682 #[inline]
683 fn default() -> PhysicalAddress {
684 PhysicalAddress(0xFFFF)
685 }
686}
687
688impl From<u16> for PhysicalAddress {
689 #[inline]
690 fn from(val: u16) -> PhysicalAddress {
691 PhysicalAddress(val)
692 }
693}
694
695impl From<PhysicalAddress> for u16 {
696 #[inline]
697 fn from(val: PhysicalAddress) -> u16 {
698 val.0
699 }
700}
701
702impl FromStr for PhysicalAddress {
703 type Err = Error;
704
705 fn from_str(val: &str) -> Result<PhysicalAddress> {
706 if let Some(val) = val.strip_prefix("0x") {
707 if val.len() == 4 {
708 if let Ok(addr) = u16::from_str_radix(val, 16) {
709 return Ok(PhysicalAddress(addr));
710 }
711 }
712 }
713 if val.len() == 4 {
714 if let Ok(addr) = u16::from_str_radix(val, 16) {
715 return Ok(PhysicalAddress(addr));
716 }
717 }
718 if val.len() == 7 {
719 let split: Vec<&str> = val.split('.').collect();
720 if split.len() != 4 {
721 return Err(Error::InvalidData);
722 }
723 if !split.iter().all(|place| place.len() == 1) {
724 return Err(Error::InvalidData);
725 }
726 let Ok(a) = u16::from_str_radix(split[0], 16) else {
727 return Err(Error::InvalidData);
728 };
729 let Ok(b) = u16::from_str_radix(split[1], 16) else {
730 return Err(Error::InvalidData);
731 };
732 let Ok(c) = u16::from_str_radix(split[2], 16) else {
733 return Err(Error::InvalidData);
734 };
735 let Ok(d) = u16::from_str_radix(split[3], 16) else {
736 return Err(Error::InvalidData);
737 };
738 return Ok(PhysicalAddress((a << 12) | (b << 8) | (c << 4) | d));
739 }
740 Err(Error::InvalidData)
741 }
742}
743
744#[cfg(test)]
745mod test_physical_address {
746 use super::*;
747
748 #[test]
749 fn test_fmt() {
750 assert_eq!(format!("{}", PhysicalAddress::from(0x12AB)), "1.2.a.b");
751 }
752
753 #[test]
754 fn test_from() {
755 assert_eq!(PhysicalAddress::from(0x12AB), PhysicalAddress(0x12AB));
756 }
757
758 #[test]
759 fn test_into() {
760 assert_eq!(<_ as Into<u16>>::into(PhysicalAddress(0x12AB)), 0x12AB);
761 }
762
763 #[test]
764 fn test_from_str_hex_lower() {
765 assert_eq!(
766 PhysicalAddress::from_str("12ab").unwrap(),
767 PhysicalAddress(0x12AB)
768 );
769 }
770
771 #[test]
772 fn test_from_str_hex_upper() {
773 assert_eq!(
774 PhysicalAddress::from_str("12AB").unwrap(),
775 PhysicalAddress(0x12AB)
776 );
777 }
778
779 #[test]
780 fn test_from_str_hex_prefix() {
781 assert_eq!(
782 PhysicalAddress::from_str("0x12AB").unwrap(),
783 PhysicalAddress(0x12AB)
784 );
785 }
786
787 #[test]
788 fn test_from_str_dotted_lower() {
789 assert_eq!(
790 PhysicalAddress::from_str("1.2.a.b").unwrap(),
791 PhysicalAddress(0x12AB)
792 );
793 }
794
795 #[test]
796 fn test_from_str_dotted_upper() {
797 assert_eq!(
798 PhysicalAddress::from_str("1.2.A.B").unwrap(),
799 PhysicalAddress(0x12AB)
800 );
801 }
802
803 #[test]
804 fn test_from_str_too_short() {
805 assert!(PhysicalAddress::from_str("12a").is_err(),);
806 }
807
808 #[test]
809 fn test_from_str_too_long() {
810 assert!(PhysicalAddress::from_str("12abcd").is_err(),);
811 }
812
813 #[test]
814 fn test_from_str_dotted_too_short() {
815 assert!(PhysicalAddress::from_str("1.2.a").is_err(),);
816 }
817
818 #[test]
819 fn test_from_str_dotted_too_long() {
820 assert!(PhysicalAddress::from_str("1.2.a.b.c").is_err(),);
821 }
822
823 #[test]
824 fn test_from_str_dotted_missing() {
825 assert!(PhysicalAddress::from_str("1.2.ab").is_err(),);
826 }
827
828 #[test]
829 fn test_from_str_dotted_too_long_group() {
830 assert!(PhysicalAddress::from_str("1.2.a.bc").is_err(),);
831 }
832
833 #[test]
834 fn test_from_str_dotted_too_short_group() {
835 assert!(PhysicalAddress::from_str("1.2.a.").is_err(),);
836 }
837
838 #[test]
839 fn test_from_str_dotted_extra_group_before() {
840 assert!(PhysicalAddress::from_str(".1.2.a.b").is_err(),);
841 }
842
843 #[test]
844 fn test_from_str_dotted_extra_group_mid() {
845 assert!(PhysicalAddress::from_str("1..2.a.b").is_err(),);
846 }
847
848 #[test]
849 fn test_from_str_dotted_extra_group_after() {
850 assert!(PhysicalAddress::from_str("1.2.a.b.").is_err(),);
851 }
852}
853
854#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Operand)]
862pub struct VendorId(pub [u8; 3]);
863
864impl Deref for VendorId {
865 type Target = [u8; 3];
866
867 fn deref(&self) -> &[u8; 3] {
868 &self.0
869 }
870}
871
872impl From<VendorId> for SysVendorId {
873 #[inline]
874 fn from(val: VendorId) -> SysVendorId {
875 SysVendorId::try_from(u32::from(val)).unwrap()
876 }
877}
878
879impl From<VendorId> for i32 {
880 #[inline]
881 fn from(val: VendorId) -> i32 {
882 (i32::from(val.0[0]) << 16) | (i32::from(val.0[1]) << 8) | i32::from(val.0[2])
883 }
884}
885
886impl From<VendorId> for u32 {
887 #[inline]
888 fn from(val: VendorId) -> u32 {
889 (u32::from(val.0[0]) << 16) | (u32::from(val.0[1]) << 8) | u32::from(val.0[2])
890 }
891}
892
893impl FromStr for VendorId {
894 type Err = Error;
895
896 fn from_str(val: &str) -> Result<VendorId> {
897 let parts: Vec<&str> = val.split('-').collect();
898 if parts.len() != 3 {
899 return Err(Error::InvalidData);
900 }
901
902 let mut id = [0; 3];
903 for (idx, part) in parts.into_iter().enumerate() {
904 if part.len() != 2 {
905 return Err(Error::InvalidData);
906 }
907 id[idx] = u8::from_str_radix(part, 16).map_err(|_| Error::InvalidData)?;
908 }
909 Ok(VendorId(id))
910 }
911}
912
913impl Display for VendorId {
914 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
915 write!(f, "{:02x}-{:02x}-{:02x}", self.0[0], self.0[1], self.0[2])
916 }
917}
918
919impl VendorId {
920 pub fn try_from_sys(vendor_id: SysVendorId) -> Result<Option<VendorId>> {
925 match vendor_id {
926 x if x.is_none() => Ok(None),
927 x if x.is_valid() => {
928 let val: u32 = x.into();
929 Ok(Some(VendorId([
930 ((val >> 16) & 0xFF).try_into().unwrap(),
931 ((val >> 8) & 0xFF).try_into().unwrap(),
932 (val & 0xFF).try_into().unwrap(),
933 ])))
934 }
935 _ => Err(Error::InvalidData),
936 }
937 }
938}
939
940#[cfg(test)]
941mod test_vendor_id {
942 use super::*;
943
944 #[test]
945 fn test_parsing() {
946 assert_eq!(
947 VendorId::from_str("01-ab-2c"),
948 Ok(VendorId([0x01, 0xAB, 0x2C]))
949 );
950 assert_eq!(
951 VendorId::from_str("01-23-45"),
952 Ok(VendorId([0x01, 0x23, 0x45]))
953 );
954 }
955
956 #[test]
957 fn test_invalid_parsing() {
958 assert_eq!(VendorId::from_str("01-ab-2g"), Err(Error::InvalidData));
959 assert_eq!(VendorId::from_str("01-ab"), Err(Error::InvalidData));
960 assert_eq!(VendorId::from_str("01ab2c"), Err(Error::InvalidData));
961 assert_eq!(VendorId::from_str("01:ab:2c"), Err(Error::InvalidData));
962 }
963
964 #[test]
965 fn test_fmt() {
966 assert_eq!(format!("{}", VendorId([0x01, 0xAB, 0x2C])), "01-ab-2c");
967 }
968
969 #[test]
970 fn test_from_sys() {
971 assert_eq!(
972 VendorId::try_from_sys(SysVendorId::try_from(0x01ab2c).unwrap()),
973 Ok(Some(VendorId([0x01, 0xAB, 0x2C])))
974 );
975
976 assert_eq!(
977 VendorId::try_from_sys(SysVendorId::try_from(0xFFFFFFFF).unwrap()),
978 Ok(None)
979 );
980 }
981
982 #[test]
983 fn test_into_i32() {
984 assert_eq!(
985 <_ as Into<i32>>::into(VendorId([0x01, 0xAB, 0x2C])),
986 0x01ab2ci32
987 );
988 }
989
990 #[test]
991 fn test_into_u32() {
992 assert_eq!(
993 <_ as Into<u32>>::into(VendorId([0x01, 0xAB, 0x2C])),
994 0x01ab2cu32
995 );
996 }
997
998 #[test]
999 fn test_into_sys() {
1000 assert_eq!(
1001 <_ as Into<SysVendorId>>::into(VendorId([0x01, 0xAB, 0x2C])),
1002 SysVendorId::try_from(0x01ab2c).unwrap()
1003 );
1004 }
1005}
1006
1007#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1010#[repr(transparent)]
1011pub struct Timeout(u32);
1012
1013impl Timeout {
1014 pub const NONE: Timeout = Timeout(0);
1016
1017 pub const MAX: Timeout = Timeout(1000);
1019
1020 #[must_use]
1022 #[inline]
1023 pub fn as_ms(&self) -> u32 {
1024 self.0
1025 }
1026
1027 #[must_use]
1029 #[inline]
1030 pub fn from_ms(millis: u32) -> Timeout {
1031 Timeout(millis)
1032 }
1033}
1034
1035impl TryFrom<&Duration> for Timeout {
1036 type Error = Error;
1037
1038 #[inline]
1039 fn try_from(duration: &Duration) -> Result<Timeout> {
1040 let millis = duration.as_millis();
1041 if let Ok(millis) = u32::try_from(millis) {
1042 Ok(Timeout(millis))
1043 } else {
1044 Err(Error::OutOfRange {
1045 expected: Range::AtMost(0xFFFFFFFF),
1046 got: if let Ok(millis) = usize::try_from(millis) {
1047 millis
1048 } else {
1049 usize::MAX
1050 },
1051 quantity: "milliseconds",
1052 })
1053 }
1054 }
1055}
1056
1057impl From<Timeout> for Duration {
1058 fn from(val: Timeout) -> Duration {
1059 Duration::from_millis(val.as_ms().into())
1060 }
1061}
1062
1063#[cfg(test)]
1064mod test_timeout {
1065 use super::*;
1066
1067 #[test]
1068 fn test_from_duration() {
1069 assert_eq!(
1070 Timeout::try_from(&Duration::from_secs(2)),
1071 Ok(Timeout::from_ms(2000))
1072 );
1073 assert_eq!(
1074 Timeout::try_from(&Duration::from_millis(0x1_0000_0000)),
1075 Err(Error::OutOfRange {
1076 expected: Range::AtMost(0xFFFFFFFF),
1077 got: 0x1_0000_0000,
1078 quantity: "milliseconds",
1079 })
1080 );
1081 }
1082}
1083
1084pub type Result<T> = std::result::Result<T, Error>;