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};
10#[cfg(feature = "serde")]
11use serde::de::{self, Unexpected, Visitor};
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::fmt::{self, Debug, Display, Formatter};
15use std::io;
16use std::ops::{Add, Deref, RangeInclusive};
17#[cfg(feature = "serde")]
18use std::result;
19use std::str::FromStr;
20use std::string::ToString;
21use std::time::Duration;
22use strum::{Display, EnumString};
23use thiserror::Error;
24
25pub mod cdc;
26pub mod device;
27pub mod message;
28pub mod operand;
29
30#[cfg(feature = "async")]
31mod async_support;
32
33pub(crate) mod ioctls;
34
35pub use linux_cec_sys as sys;
36pub use linux_cec_sys::Timestamp;
37
38#[derive(
41 Clone,
42 Copy,
43 Debug,
44 Default,
45 PartialEq,
46 Hash,
47 IntoPrimitive,
48 TryFromPrimitive,
49 Display,
50 EnumString,
51)]
52#[strum(ascii_case_insensitive)]
53#[repr(u8)]
54pub enum LogicalAddress {
55 #[strum(serialize = "tv", serialize = "0")]
56 Tv = constants::CEC_LOG_ADDR_TV,
57 #[strum(
58 serialize = "recording-device1",
59 serialize = "recording-device-1",
60 serialize = "recording-device",
61 serialize = "1"
62 )]
63 RecordingDevice1 = constants::CEC_LOG_ADDR_RECORD_1,
64 #[strum(
65 serialize = "recording-device2",
66 serialize = "recording-device-2",
67 serialize = "2"
68 )]
69 RecordingDevice2 = constants::CEC_LOG_ADDR_RECORD_2,
70 #[strum(
71 serialize = "tuner1",
72 serialize = "tuner-1",
73 serialize = "tuner",
74 serialize = "3"
75 )]
76 Tuner1 = constants::CEC_LOG_ADDR_TUNER_1,
77 #[strum(
78 serialize = "playback-device1",
79 serialize = "playback-device-1",
80 serialize = "playback-device",
81 serialize = "4"
82 )]
83 PlaybackDevice1 = constants::CEC_LOG_ADDR_PLAYBACK_1,
84 #[strum(serialize = "audio-system", serialize = "5")]
85 AudioSystem = constants::CEC_LOG_ADDR_AUDIOSYSTEM,
86 #[strum(serialize = "tuner2", serialize = "tuner-2", serialize = "6")]
87 Tuner2 = constants::CEC_LOG_ADDR_TUNER_2,
88 #[strum(serialize = "tuner3", serialize = "tuner-3", serialize = "7")]
89 Tuner3 = constants::CEC_LOG_ADDR_TUNER_3,
90 #[strum(
91 serialize = "playback-device2",
92 serialize = "playback-device-2",
93 serialize = "8"
94 )]
95 PlaybackDevice2 = constants::CEC_LOG_ADDR_PLAYBACK_2,
96 #[strum(
97 serialize = "recording-device3",
98 serialize = "recording-device-3",
99 serialize = "9"
100 )]
101 RecordingDevice3 = constants::CEC_LOG_ADDR_RECORD_3,
102 #[strum(
103 serialize = "tuner4",
104 serialize = "tuner-4",
105 serialize = "10",
106 serialize = "a"
107 )]
108 Tuner4 = constants::CEC_LOG_ADDR_TUNER_4,
109 #[strum(
110 serialize = "playback-device3",
111 serialize = "playback-device-3",
112 serialize = "11",
113 serialize = "b"
114 )]
115 PlaybackDevice3 = constants::CEC_LOG_ADDR_PLAYBACK_3,
116 #[strum(
117 serialize = "backup1",
118 serialize = "backup-1",
119 serialize = "12",
120 serialize = "c"
121 )]
122 Backup1 = constants::CEC_LOG_ADDR_BACKUP_1,
123 #[strum(
124 serialize = "backup2",
125 serialize = "backup-2",
126 serialize = "13",
127 serialize = "d"
128 )]
129 Backup2 = constants::CEC_LOG_ADDR_BACKUP_2,
130 #[strum(serialize = "specific", serialize = "14", serialize = "e")]
131 Specific = constants::CEC_LOG_ADDR_SPECIFIC,
132 #[default]
133 #[strum(
134 serialize = "unregistered",
135 serialize = "broadcast",
136 serialize = "15",
137 serialize = "f"
138 )]
139 UnregisteredOrBroadcast = constants::CEC_LOG_ADDR_UNREGISTERED,
140}
141
142impl LogicalAddress {
143 #![allow(non_upper_case_globals)]
144 pub const Unregistered: LogicalAddress = LogicalAddress::UnregisteredOrBroadcast;
146 pub const Broadcast: LogicalAddress = LogicalAddress::UnregisteredOrBroadcast;
148
149 #[must_use]
150 pub fn primary_device_type(self) -> Option<operand::PrimaryDeviceType> {
151 match self {
152 LogicalAddress::Tv => Some(operand::PrimaryDeviceType::Tv),
153 LogicalAddress::RecordingDevice1
154 | LogicalAddress::RecordingDevice2
155 | LogicalAddress::RecordingDevice3 => Some(operand::PrimaryDeviceType::Recording),
156 LogicalAddress::Tuner1
157 | LogicalAddress::Tuner2
158 | LogicalAddress::Tuner3
159 | LogicalAddress::Tuner4 => Some(operand::PrimaryDeviceType::Tuner),
160 LogicalAddress::PlaybackDevice1
161 | LogicalAddress::PlaybackDevice2
162 | LogicalAddress::PlaybackDevice3 => Some(operand::PrimaryDeviceType::Playback),
163 LogicalAddress::AudioSystem => Some(operand::PrimaryDeviceType::Audio),
164 LogicalAddress::Backup1 | LogicalAddress::Backup2 => None,
165 LogicalAddress::Specific => None,
166 LogicalAddress::UnregisteredOrBroadcast => None,
167 }
168 }
169
170 #[must_use]
171 pub fn all_device_types(self) -> operand::AllDeviceTypes {
172 match self {
173 LogicalAddress::Tv => operand::AllDeviceTypes::TV,
174 LogicalAddress::RecordingDevice1
175 | LogicalAddress::RecordingDevice2
176 | LogicalAddress::RecordingDevice3 => operand::AllDeviceTypes::RECORDING,
177 LogicalAddress::Tuner1
178 | LogicalAddress::Tuner2
179 | LogicalAddress::Tuner3
180 | LogicalAddress::Tuner4 => operand::AllDeviceTypes::TUNER,
181 LogicalAddress::PlaybackDevice1
182 | LogicalAddress::PlaybackDevice2
183 | LogicalAddress::PlaybackDevice3 => operand::AllDeviceTypes::PLAYBACK,
184 LogicalAddress::AudioSystem => operand::AllDeviceTypes::AUDIOSYSTEM,
185 LogicalAddress::Backup1 | LogicalAddress::Backup2 => operand::AllDeviceTypes::empty(),
186 LogicalAddress::Specific => operand::AllDeviceTypes::empty(),
187 LogicalAddress::UnregisteredOrBroadcast => operand::AllDeviceTypes::empty(),
188 }
189 }
190
191 #[must_use]
192 pub fn ty(self) -> Option<LogicalAddressType> {
193 match self {
194 LogicalAddress::Tv => Some(LogicalAddressType::Tv),
195 LogicalAddress::RecordingDevice1
196 | LogicalAddress::RecordingDevice2
197 | LogicalAddress::RecordingDevice3 => Some(LogicalAddressType::Record),
198 LogicalAddress::Tuner1
199 | LogicalAddress::Tuner2
200 | LogicalAddress::Tuner3
201 | LogicalAddress::Tuner4 => Some(LogicalAddressType::Tuner),
202 LogicalAddress::PlaybackDevice1
203 | LogicalAddress::PlaybackDevice2
204 | LogicalAddress::PlaybackDevice3 => Some(LogicalAddressType::Playback),
205 LogicalAddress::AudioSystem => Some(LogicalAddressType::AudioSystem),
206 LogicalAddress::Backup1 | LogicalAddress::Backup2 => None,
207 LogicalAddress::Specific => Some(LogicalAddressType::Specific),
208 LogicalAddress::UnregisteredOrBroadcast => Some(LogicalAddressType::Unregistered),
209 }
210 }
211}
212
213#[cfg(feature = "serde")]
214impl Serialize for LogicalAddress {
215 fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
216 where
217 S: Serializer,
218 {
219 serializer.serialize_u8(*self as u8)
220 }
221}
222
223#[cfg(feature = "serde")]
224struct LogicalAddressVisitor;
225
226#[cfg(feature = "serde")]
227impl<'de> Visitor<'de> for LogicalAddressVisitor {
228 type Value = LogicalAddress;
229
230 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
231 formatter.write_str("a logical address")
232 }
233
234 fn visit_u64<E>(self, value: u64) -> result::Result<Self::Value, E>
235 where
236 E: de::Error,
237 {
238 let Ok(value) = u8::try_from(value) else {
239 return Err(de::Error::invalid_value(
240 Unexpected::Unsigned(value),
241 &"a logical address",
242 ));
243 };
244 LogicalAddress::try_from_primitive(value).map_err(|_| {
245 de::Error::invalid_value(Unexpected::Unsigned(value.into()), &"a logical address")
246 })
247 }
248
249 fn visit_str<E>(self, string: &str) -> result::Result<Self::Value, E>
250 where
251 E: de::Error,
252 {
253 LogicalAddress::from_str(string)
254 .map_err(|_| de::Error::invalid_value(Unexpected::Str(string), &"a logical address"))
255 }
256}
257
258#[cfg(feature = "serde")]
259impl<'de> Deserialize<'de> for LogicalAddress {
260 fn deserialize<D>(deserializer: D) -> std::result::Result<LogicalAddress, D::Error>
261 where
262 D: Deserializer<'de>,
263 {
264 deserializer.deserialize_any(LogicalAddressVisitor)
265 }
266}
267
268#[cfg(test)]
269mod test_logical_address {
270 use super::*;
271
272 #[cfg(feature = "serde")]
273 use serde_json::{from_str, to_string};
274
275 #[cfg(feature = "serde")]
276 #[test]
277 fn test_serialize() {
278 assert_eq!(to_string(&LogicalAddress::Tv).unwrap(), "0");
279 }
280
281 #[cfg(feature = "serde")]
282 #[test]
283 fn test_deserialize_int() {
284 assert_eq!(
285 from_str::<LogicalAddress>("1").unwrap(),
286 LogicalAddress::RecordingDevice1
287 );
288 }
289
290 #[cfg(feature = "serde")]
291 #[test]
292 fn test_deserialize_int_invalid() {
293 assert!(from_str::<LogicalAddress>("16").is_err());
294 }
295
296 #[cfg(feature = "serde")]
297 #[test]
298 fn test_deserialize_str() {
299 assert_eq!(
300 from_str::<LogicalAddress>("\"Tv\"").unwrap(),
301 LogicalAddress::Tv
302 );
303 }
304
305 #[cfg(feature = "serde")]
306 #[test]
307 fn test_deserialize_str_invalid() {
308 assert!(from_str::<LogicalAddress>("\"radio\"").is_err());
309 }
310
311 #[cfg(feature = "serde")]
312 #[test]
313 fn test_deserialize_lowercase() {
314 assert_eq!(
315 from_str::<LogicalAddress>("\"tv\"").unwrap(),
316 LogicalAddress::Tv
317 );
318 }
319
320 #[cfg(feature = "serde")]
321 #[test]
322 fn test_deserialize_alt_name() {
323 assert_eq!(
324 from_str::<LogicalAddress>("\"recording-device\"").unwrap(),
325 LogicalAddress::RecordingDevice1
326 );
327 }
328}
329
330#[derive(
333 Clone,
334 Copy,
335 Debug,
336 Default,
337 Eq,
338 PartialEq,
339 Hash,
340 IntoPrimitive,
341 TryFromPrimitive,
342 Display,
343 EnumString,
344)]
345#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
346#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
347#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
348#[repr(u8)]
349pub enum LogicalAddressType {
350 Tv = constants::CEC_LOG_ADDR_TYPE_TV,
351 Record = constants::CEC_LOG_ADDR_TYPE_RECORD,
352 Tuner = constants::CEC_LOG_ADDR_TYPE_TUNER,
353 Playback = constants::CEC_LOG_ADDR_TYPE_PLAYBACK,
354 AudioSystem = constants::CEC_LOG_ADDR_TYPE_AUDIOSYSTEM,
355 Specific = constants::CEC_LOG_ADDR_TYPE_SPECIFIC,
356 #[default]
357 Unregistered = constants::CEC_LOG_ADDR_TYPE_UNREGISTERED,
358}
359
360impl LogicalAddressType {
361 #[must_use]
362 pub fn primary_device_type(self) -> Option<operand::PrimaryDeviceType> {
363 match self {
364 LogicalAddressType::Tv => Some(operand::PrimaryDeviceType::Tv),
365 LogicalAddressType::Record => Some(operand::PrimaryDeviceType::Recording),
366 LogicalAddressType::Tuner => Some(operand::PrimaryDeviceType::Tuner),
367 LogicalAddressType::Playback => Some(operand::PrimaryDeviceType::Playback),
368 LogicalAddressType::AudioSystem => Some(operand::PrimaryDeviceType::Audio),
369 LogicalAddressType::Specific => None,
370 LogicalAddressType::Unregistered => None,
371 }
372 }
373
374 #[must_use]
375 pub fn all_device_types(self) -> operand::AllDeviceTypes {
376 match self {
377 LogicalAddressType::Tv => operand::AllDeviceTypes::TV,
378 LogicalAddressType::Record => operand::AllDeviceTypes::RECORDING,
379 LogicalAddressType::Tuner => operand::AllDeviceTypes::TUNER,
380 LogicalAddressType::Playback => operand::AllDeviceTypes::PLAYBACK,
381 LogicalAddressType::AudioSystem => operand::AllDeviceTypes::AUDIOSYSTEM,
382 LogicalAddressType::Specific | LogicalAddressType::Unregistered => {
383 operand::AllDeviceTypes::empty()
384 }
385 }
386 }
387}
388
389#[cfg(test)]
390mod test_logical_address_type {
391 use super::*;
392
393 #[cfg(feature = "serde")]
394 use serde_json::{from_str, to_string};
395
396 #[cfg(feature = "serde")]
397 #[test]
398 fn test_serialize() {
399 assert_eq!(to_string(&LogicalAddressType::Tv).unwrap(), "\"tv\"");
400 }
401
402 #[cfg(feature = "serde")]
403 #[test]
404 fn test_deserialize_int() {
405 assert!(from_str::<LogicalAddressType>("1").is_err(),);
406 }
407
408 #[cfg(feature = "serde")]
409 #[test]
410 fn test_deserialize_str() {
411 assert_eq!(
412 from_str::<LogicalAddressType>("\"tv\"").unwrap(),
413 LogicalAddressType::Tv
414 );
415 }
416
417 #[cfg(feature = "serde")]
418 #[test]
419 fn test_deserialize_str_invalid() {
420 assert!(from_str::<LogicalAddressType>("\"radio\"").is_err());
421 }
422}
423
424#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
427#[non_exhaustive]
428pub enum InitiatorMode {
429 Disabled,
431 Enabled,
433 Exclusive,
436}
437
438#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
441#[non_exhaustive]
442pub enum FollowerMode {
443 Disabled,
445 Enabled,
447 Exclusive,
450 ExclusivePassthru,
453 MonitorPin,
457 Monitor,
462 MonitorAll,
468}
469
470#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
480pub enum AddressingType {
481 Direct,
484 Broadcast,
486 Either,
488}
489
490#[derive(Clone, Debug, PartialEq, Eq)]
491pub enum Range<T: PartialOrd + Clone + Display + Default + Debug> {
492 AtMost(T),
493 AtLeast(T),
494 Exact(T),
495 Only(Vec<T>),
496 Interval { min: T, max: T },
497}
498
499impl Range<usize> {
500 pub fn check(self, val: impl Into<usize>, quantity: &'static str) -> Result<()> {
501 let val: usize = val.into();
502 match self {
503 Range::AtMost(max) if val <= max => Ok(()),
504 Range::AtLeast(min) if val >= min => Ok(()),
505 Range::Exact(x) if val == x => Ok(()),
506 Range::Only(list) if list.contains(&val) => Ok(()),
507 Range::Interval { min, max } if val >= min && val <= max => Ok(()),
508 _ => Err(Error::OutOfRange {
509 got: val,
510 expected: self,
511 quantity,
512 }),
513 }
514 }
515}
516
517impl<T: PartialOrd + Clone + Display + Default + Debug + Eq> From<RangeInclusive<T>> for Range<T> {
518 fn from(val: RangeInclusive<T>) -> Range<T> {
519 Range::Interval {
520 min: val.start().clone(),
521 max: val.end().clone(),
522 }
523 }
524}
525
526impl<T: PartialOrd + Clone + Display + Default + Debug + Eq> Display for Range<T> {
527 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
528 match self {
529 Range::AtMost(max) => f.write_fmt(format_args!("at most {max}")),
530 Range::AtLeast(min) => f.write_fmt(format_args!("at least {min}")),
531 Range::Exact(x) => f.write_fmt(format_args!("{x}")),
532 Range::Only(list) => {
533 let list = list
534 .iter()
535 .map(ToString::to_string)
536 .fold(String::new(), |a, b| {
537 if a.is_empty() {
538 b
539 } else {
540 format!("{a}, {b}")
541 }
542 });
543 f.write_fmt(format_args!("one of {list}"))
544 }
545 Range::Interval { min, max } => f.write_fmt(format_args!("between {min} and {max}")),
546 }
547 }
548}
549
550impl<T: PartialOrd + Clone + Display + Default + Debug + Eq + Add<Output = T> + Copy> Add<T>
551 for Range<T>
552{
553 type Output = Range<T>;
554
555 fn add(self, rhs: T) -> Self::Output {
556 match self {
557 Range::AtMost(max) => Range::AtMost(max + rhs),
558 Range::AtLeast(min) => Range::AtLeast(min + rhs),
559 Range::Exact(x) => Range::Exact(x + rhs),
560 Range::Only(list) => Range::Only(list.into_iter().map(|val| val + rhs).collect()),
561 Range::Interval { min, max } => Range::Interval {
562 min: min + rhs,
563 max: max + rhs,
564 },
565 }
566 }
567}
568
569#[cfg(test)]
570mod test_range {
571 use super::Range;
572
573 #[test]
574 fn test_display_at_most() {
575 assert_eq!(Range::AtMost(10).to_string(), "at most 10");
576 }
577
578 #[test]
579 fn test_display_at_least() {
580 assert_eq!(Range::AtLeast(10).to_string(), "at least 10");
581 }
582
583 #[test]
584 fn test_display_exact() {
585 assert_eq!(Range::Exact(10).to_string(), "10");
586 }
587
588 #[test]
589 fn test_display_only_1() {
590 assert_eq!(Range::Only(vec![10]).to_string(), "one of 10");
591 }
592
593 #[test]
594 fn test_display_only_2() {
595 assert_eq!(Range::Only(vec![10, 11]).to_string(), "one of 10, 11");
596 }
597
598 #[test]
599 fn test_display_only_3() {
600 assert_eq!(
601 Range::Only(vec![10, 11, 12]).to_string(),
602 "one of 10, 11, 12"
603 );
604 }
605
606 #[test]
607 fn test_display_interval() {
608 assert_eq!(
609 Range::Interval { min: 1, max: 10 }.to_string(),
610 "between 1 and 10"
611 );
612 }
613
614 #[test]
615 fn test_add_at_most() {
616 assert_eq!(Range::AtMost(10) + 1, Range::AtMost(11));
617 }
618
619 #[test]
620 fn test_add_at_least() {
621 assert_eq!(Range::AtLeast(10) + 1, Range::AtLeast(11));
622 }
623
624 #[test]
625 fn test_add_exact() {
626 assert_eq!(Range::Exact(10) + 1, Range::Exact(11));
627 }
628
629 #[test]
630 fn test_add_only() {
631 assert_eq!(
632 Range::Only(vec![10, 11, 12]) + 1,
633 Range::Only(vec![11, 12, 13])
634 );
635 }
636
637 #[test]
638 fn test_add_interval() {
639 assert_eq!(
640 Range::Interval { min: 1, max: 10 } + 1,
641 Range::Interval { min: 2, max: 11 }
642 );
643 }
644}
645
646#[derive(Error, Clone, Debug, PartialEq)]
648#[non_exhaustive]
649pub enum Error {
650 #[error("Expected {expected} {quantity}, got {got} {quantity}")]
652 OutOfRange {
653 expected: Range<usize>,
654 got: usize,
655 quantity: &'static str,
656 },
657 #[error("Invalid value {value} for type {ty}")]
659 InvalidValueForType { ty: &'static str, value: String },
660 #[error("The provided data was not valid")]
662 InvalidData,
663 #[error("A timeout occurred")]
665 Timeout,
666 #[error("The request was aborted")]
668 Abort,
669 #[error("The device does not have a logical address")]
671 NoLogicalAddress,
672 #[error("The device was disconnected")]
674 Disconnected,
675 #[error("This device does not support this operation")]
677 Unsupported,
678 #[error("Got unexpected result from system: {0}")]
680 SystemError(Errno),
681 #[error("{0}")]
684 TxError(#[from] TxError),
685 #[error("{0}")]
688 RxError(#[from] RxError),
689 #[error("Unknown error: {0}")]
691 UnknownError(String),
692}
693
694#[derive(Error, Clone, Debug, PartialEq)]
696#[non_exhaustive]
697#[repr(u8)]
698pub enum TxError {
699 #[error("Arbitration was lost")]
700 ArbLost = constants::CEC_TX_STATUS_ARB_LOST,
701 #[error("No acknowledgement")]
702 Nack = constants::CEC_TX_STATUS_NACK,
703 #[error("Low drive on bus")]
704 LowDrive = constants::CEC_TX_STATUS_LOW_DRIVE,
705 #[error("An unknown error occurred")]
706 UnknownError = constants::CEC_TX_STATUS_ERROR,
707 #[error("Maximum retries hit")]
708 MaxRetries = constants::CEC_TX_STATUS_MAX_RETRIES,
709 #[error("The request was aborted")]
710 Aborted = constants::CEC_TX_STATUS_ABORTED,
711 #[error("The request timed out")]
712 Timeout = constants::CEC_TX_STATUS_TIMEOUT,
713}
714
715#[derive(Error, Clone, Debug, PartialEq)]
717#[non_exhaustive]
718#[repr(u8)]
719pub enum RxError {
720 #[error("The request timed out")]
721 Timeout = constants::CEC_RX_STATUS_TIMEOUT,
722 #[error("The requested feature was not present")]
723 FeatureAbort = constants::CEC_RX_STATUS_FEATURE_ABORT,
724 #[error("The request was aborted")]
725 Aborted = constants::CEC_RX_STATUS_ABORTED,
726}
727
728impl Error {
729 pub(crate) fn add_offset(offset: usize) -> impl FnOnce(Error) -> Error {
730 move |error| match error {
731 Error::OutOfRange {
732 got,
733 expected,
734 quantity,
735 } if quantity == "bytes" => Error::OutOfRange {
736 expected: expected + offset,
737 got: got + offset,
738 quantity,
739 },
740 _ => error,
741 }
742 }
743}
744
745impl From<io::Error> for Error {
746 fn from(val: io::Error) -> Error {
747 if let Some(raw) = val.raw_os_error() {
748 Errno::from_raw(raw).into()
749 } else {
750 Error::UnknownError(format!("{val}"))
751 }
752 }
753}
754
755impl From<Errno> for Error {
756 fn from(val: Errno) -> Error {
757 match val {
758 Errno::EINVAL => Error::InvalidData,
759 Errno::ETIMEDOUT => Error::Timeout,
760 Errno::ENODEV => Error::Disconnected,
761 Errno::ENONET => Error::NoLogicalAddress,
762 Errno::ENOTTY => Error::Unsupported,
763 x => Error::SystemError(x),
764 }
765 }
766}
767
768impl From<CEC_TX_STATUS> for Error {
769 fn from(tx_status: CEC_TX_STATUS) -> Error {
770 if tx_status.contains(CEC_TX_STATUS::TIMEOUT) {
771 return Error::Timeout;
772 }
773 if tx_status.contains(CEC_TX_STATUS::ABORTED) {
774 return Error::Abort;
775 }
776 if tx_status.contains(CEC_TX_STATUS::ARB_LOST) {
777 return TxError::ArbLost.into();
778 }
779 if tx_status.contains(CEC_TX_STATUS::NACK) {
780 return TxError::Nack.into();
781 }
782 if tx_status.contains(CEC_TX_STATUS::LOW_DRIVE) {
783 return TxError::LowDrive.into();
784 }
785 if tx_status.contains(CEC_TX_STATUS::MAX_RETRIES) {
786 return TxError::MaxRetries.into();
787 }
788 Error::UnknownError(format!("{tx_status:?}"))
789 }
790}
791
792impl<T: TryFromPrimitive> From<TryFromPrimitiveError<T>> for Error {
793 fn from(val: TryFromPrimitiveError<T>) -> Error {
794 Error::InvalidValueForType {
795 ty: T::NAME,
796 value: format!("{:?}", val.number),
797 }
798 }
799}
800
801#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
804#[repr(transparent)]
805pub struct PhysicalAddress(pub(crate) u16);
806
807impl PhysicalAddress {
808 pub const ROOT: PhysicalAddress = PhysicalAddress(0x0000);
810
811 pub const INVALID: PhysicalAddress = PhysicalAddress(0xFFFF);
813
814 #[must_use]
816 #[inline]
817 pub fn is_valid(&self) -> bool {
818 self.0 != 0xFFFF
819 }
820
821 #[must_use]
823 #[inline]
824 pub fn is_root(&self) -> bool {
825 self.0 == 0x0000
826 }
827}
828
829impl Display for PhysicalAddress {
830 #[inline]
831 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
832 f.write_fmt(format_args!(
833 "{:x}.{:x}.{:x}.{:x}",
834 self.0 >> 12,
835 (self.0 >> 8) & 0xF,
836 (self.0 >> 4) & 0xF,
837 self.0 & 0xF
838 ))
839 }
840}
841
842impl Default for PhysicalAddress {
843 #[inline]
844 fn default() -> PhysicalAddress {
845 PhysicalAddress(0xFFFF)
846 }
847}
848
849impl From<u16> for PhysicalAddress {
850 #[inline]
851 fn from(val: u16) -> PhysicalAddress {
852 PhysicalAddress(val)
853 }
854}
855
856impl From<PhysicalAddress> for u16 {
857 #[inline]
858 fn from(val: PhysicalAddress) -> u16 {
859 val.0
860 }
861}
862
863#[cfg(feature = "serde")]
864impl Serialize for PhysicalAddress {
865 fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
866 where
867 S: Serializer,
868 {
869 serializer.serialize_str(format!("{self}").as_str())
870 }
871}
872
873#[cfg(feature = "serde")]
874struct PhysicalAddressVisitor;
875
876#[cfg(feature = "serde")]
877impl<'de> Visitor<'de> for PhysicalAddressVisitor {
878 type Value = PhysicalAddress;
879
880 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
881 formatter.write_str("a physical address")
882 }
883
884 fn visit_u64<E>(self, value: u64) -> result::Result<Self::Value, E>
885 where
886 E: de::Error,
887 {
888 let Ok(value) = u16::try_from(value) else {
889 return Err(de::Error::invalid_value(
890 Unexpected::Unsigned(value),
891 &"a physical address",
892 ));
893 };
894 Ok(PhysicalAddress::from(value))
895 }
896
897 fn visit_str<E>(self, string: &str) -> result::Result<Self::Value, E>
898 where
899 E: de::Error,
900 {
901 PhysicalAddress::from_str(string)
902 .map_err(|_| de::Error::invalid_value(Unexpected::Str(string), &"a physical address"))
903 }
904}
905
906#[cfg(feature = "serde")]
907impl<'de> Deserialize<'de> for PhysicalAddress {
908 fn deserialize<D>(deserializer: D) -> std::result::Result<PhysicalAddress, D::Error>
909 where
910 D: Deserializer<'de>,
911 {
912 deserializer.deserialize_any(PhysicalAddressVisitor)
913 }
914}
915
916impl FromStr for PhysicalAddress {
917 type Err = Error;
918
919 fn from_str(val: &str) -> Result<PhysicalAddress> {
920 if let Some(val) = val.strip_prefix("0x") {
921 if val.len() == 4 {
922 if let Ok(addr) = u16::from_str_radix(val, 16) {
923 return Ok(PhysicalAddress(addr));
924 }
925 }
926 }
927 if val.len() == 4 {
928 if let Ok(addr) = u16::from_str_radix(val, 16) {
929 return Ok(PhysicalAddress(addr));
930 }
931 }
932 if val.len() == 7 {
933 let split: Vec<&str> = val.split('.').collect();
934 if split.len() != 4 {
935 return Err(Error::InvalidData);
936 }
937 if !split.iter().all(|place| place.len() == 1) {
938 return Err(Error::InvalidData);
939 }
940 let Ok(a) = u16::from_str_radix(split[0], 16) else {
941 return Err(Error::InvalidData);
942 };
943 let Ok(b) = u16::from_str_radix(split[1], 16) else {
944 return Err(Error::InvalidData);
945 };
946 let Ok(c) = u16::from_str_radix(split[2], 16) else {
947 return Err(Error::InvalidData);
948 };
949 let Ok(d) = u16::from_str_radix(split[3], 16) else {
950 return Err(Error::InvalidData);
951 };
952 return Ok(PhysicalAddress((a << 12) | (b << 8) | (c << 4) | d));
953 }
954 Err(Error::InvalidData)
955 }
956}
957
958#[cfg(test)]
959mod test_physical_address {
960 use super::*;
961
962 #[cfg(feature = "serde")]
963 use serde_json::{from_str, to_string};
964
965 #[test]
966 fn test_fmt() {
967 assert_eq!(format!("{}", PhysicalAddress::from(0x12AB)), "1.2.a.b");
968 }
969
970 #[test]
971 fn test_from() {
972 assert_eq!(PhysicalAddress::from(0x12AB), PhysicalAddress(0x12AB));
973 }
974
975 #[test]
976 fn test_into() {
977 assert_eq!(<_ as Into<u16>>::into(PhysicalAddress(0x12AB)), 0x12AB);
978 }
979
980 #[test]
981 fn test_from_str_hex_lower() {
982 assert_eq!(
983 PhysicalAddress::from_str("12ab").unwrap(),
984 PhysicalAddress(0x12AB)
985 );
986 }
987
988 #[test]
989 fn test_from_str_hex_upper() {
990 assert_eq!(
991 PhysicalAddress::from_str("12AB").unwrap(),
992 PhysicalAddress(0x12AB)
993 );
994 }
995
996 #[test]
997 fn test_from_str_hex_prefix() {
998 assert_eq!(
999 PhysicalAddress::from_str("0x12AB").unwrap(),
1000 PhysicalAddress(0x12AB)
1001 );
1002 }
1003
1004 #[test]
1005 fn test_from_str_dotted_lower() {
1006 assert_eq!(
1007 PhysicalAddress::from_str("1.2.a.b").unwrap(),
1008 PhysicalAddress(0x12AB)
1009 );
1010 }
1011
1012 #[test]
1013 fn test_from_str_dotted_upper() {
1014 assert_eq!(
1015 PhysicalAddress::from_str("1.2.A.B").unwrap(),
1016 PhysicalAddress(0x12AB)
1017 );
1018 }
1019
1020 #[test]
1021 fn test_from_str_too_short() {
1022 assert!(PhysicalAddress::from_str("12a").is_err(),);
1023 }
1024
1025 #[test]
1026 fn test_from_str_too_long() {
1027 assert!(PhysicalAddress::from_str("12abcd").is_err(),);
1028 }
1029
1030 #[test]
1031 fn test_from_str_dotted_too_short() {
1032 assert!(PhysicalAddress::from_str("1.2.a").is_err(),);
1033 }
1034
1035 #[test]
1036 fn test_from_str_dotted_too_long() {
1037 assert!(PhysicalAddress::from_str("1.2.a.b.c").is_err(),);
1038 }
1039
1040 #[test]
1041 fn test_from_str_dotted_missing() {
1042 assert!(PhysicalAddress::from_str("1.2.ab").is_err(),);
1043 }
1044
1045 #[test]
1046 fn test_from_str_dotted_too_long_group() {
1047 assert!(PhysicalAddress::from_str("1.2.a.bc").is_err(),);
1048 }
1049
1050 #[test]
1051 fn test_from_str_dotted_too_short_group() {
1052 assert!(PhysicalAddress::from_str("1.2.a.").is_err(),);
1053 }
1054
1055 #[test]
1056 fn test_from_str_dotted_extra_group_before() {
1057 assert!(PhysicalAddress::from_str(".1.2.a.b").is_err(),);
1058 }
1059
1060 #[test]
1061 fn test_from_str_dotted_extra_group_mid() {
1062 assert!(PhysicalAddress::from_str("1..2.a.b").is_err(),);
1063 }
1064
1065 #[test]
1066 fn test_from_str_dotted_extra_group_after() {
1067 assert!(PhysicalAddress::from_str("1.2.a.b.").is_err(),);
1068 }
1069
1070 #[cfg(feature = "serde")]
1071 #[test]
1072 fn test_serialize() {
1073 assert_eq!(to_string(&PhysicalAddress(0x123f)).unwrap(), "\"1.2.3.f\"");
1074 }
1075
1076 #[cfg(feature = "serde")]
1077 #[test]
1078 fn test_deserialize_dotted() {
1079 assert_eq!(
1080 from_str::<PhysicalAddress>("\"1.2.3.f\"").unwrap(),
1081 PhysicalAddress(0x123f)
1082 );
1083 }
1084
1085 #[cfg(feature = "serde")]
1086 #[test]
1087 fn test_deserialize_hex_unprefixed() {
1088 assert_eq!(
1089 from_str::<PhysicalAddress>("\"1234\"").unwrap(),
1090 PhysicalAddress(0x1234)
1091 );
1092 }
1093
1094 #[cfg(feature = "serde")]
1095 #[test]
1096 fn test_deserialize_hex_prefixed() {
1097 assert_eq!(
1098 from_str::<PhysicalAddress>("\"0x123f\"").unwrap(),
1099 PhysicalAddress(0x123f)
1100 );
1101 }
1102
1103 #[cfg(feature = "serde")]
1104 #[test]
1105 fn test_deserialize_invalid() {
1106 assert!(from_str::<PhysicalAddress>("\"abcdg\"").is_err());
1107 }
1108
1109 #[cfg(feature = "serde")]
1110 #[test]
1111 fn test_deserialize_numeric() {
1112 assert_eq!(
1113 from_str::<PhysicalAddress>("16").unwrap(),
1114 PhysicalAddress(0x10)
1115 );
1116 }
1117}
1118
1119#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Operand)]
1127pub struct VendorId(pub [u8; 3]);
1128
1129impl Deref for VendorId {
1130 type Target = [u8; 3];
1131
1132 fn deref(&self) -> &[u8; 3] {
1133 &self.0
1134 }
1135}
1136
1137impl From<VendorId> for SysVendorId {
1138 #[inline]
1139 fn from(val: VendorId) -> SysVendorId {
1140 SysVendorId::try_from(u32::from(val)).unwrap()
1141 }
1142}
1143
1144impl From<VendorId> for i32 {
1145 #[inline]
1146 fn from(val: VendorId) -> i32 {
1147 (i32::from(val.0[0]) << 16) | (i32::from(val.0[1]) << 8) | i32::from(val.0[2])
1148 }
1149}
1150
1151impl From<VendorId> for u32 {
1152 #[inline]
1153 fn from(val: VendorId) -> u32 {
1154 (u32::from(val.0[0]) << 16) | (u32::from(val.0[1]) << 8) | u32::from(val.0[2])
1155 }
1156}
1157
1158impl FromStr for VendorId {
1159 type Err = Error;
1160
1161 fn from_str(val: &str) -> Result<VendorId> {
1162 let parts: Vec<&str> = val.split('-').collect();
1163 if parts.len() != 3 {
1164 return Err(Error::InvalidData);
1165 }
1166
1167 let mut id = [0; 3];
1168 for (idx, part) in parts.into_iter().enumerate() {
1169 if part.len() != 2 {
1170 return Err(Error::InvalidData);
1171 }
1172 id[idx] = u8::from_str_radix(part, 16).map_err(|_| Error::InvalidData)?;
1173 }
1174 Ok(VendorId(id))
1175 }
1176}
1177
1178impl Display for VendorId {
1179 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1180 write!(f, "{:02x}-{:02x}-{:02x}", self.0[0], self.0[1], self.0[2])
1181 }
1182}
1183
1184impl VendorId {
1185 pub fn try_from_sys(vendor_id: SysVendorId) -> Result<Option<VendorId>> {
1190 match vendor_id {
1191 x if x.is_none() => Ok(None),
1192 x if x.is_valid() => {
1193 let val: u32 = x.into();
1194 Ok(Some(VendorId([
1195 ((val >> 16) & 0xFF).try_into().unwrap(),
1196 ((val >> 8) & 0xFF).try_into().unwrap(),
1197 (val & 0xFF).try_into().unwrap(),
1198 ])))
1199 }
1200 _ => Err(Error::InvalidData),
1201 }
1202 }
1203}
1204
1205#[cfg(feature = "serde")]
1206impl Serialize for VendorId {
1207 fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
1208 where
1209 S: Serializer,
1210 {
1211 serializer.serialize_str(format!("{self}").as_str())
1212 }
1213}
1214
1215#[cfg(feature = "serde")]
1216impl<'de> Deserialize<'de> for VendorId {
1217 fn deserialize<D>(deserializer: D) -> result::Result<VendorId, D::Error>
1218 where
1219 D: Deserializer<'de>,
1220 {
1221 let string = String::deserialize(deserializer)?;
1222
1223 VendorId::from_str(string.as_str()).map_err(|_| {
1224 de::Error::invalid_value(Unexpected::Str(&string), &"3 hyphen-separated octets")
1225 })
1226 }
1227}
1228
1229#[cfg(test)]
1230mod test_vendor_id {
1231 use super::*;
1232
1233 #[cfg(feature = "serde")]
1234 use serde_json::{from_str, to_string};
1235
1236 #[test]
1237 fn test_parsing() {
1238 assert_eq!(
1239 VendorId::from_str("01-ab-2c"),
1240 Ok(VendorId([0x01, 0xAB, 0x2C]))
1241 );
1242 assert_eq!(
1243 VendorId::from_str("01-23-45"),
1244 Ok(VendorId([0x01, 0x23, 0x45]))
1245 );
1246 }
1247
1248 #[test]
1249 fn test_invalid_parsing() {
1250 assert_eq!(VendorId::from_str("01-ab-2g"), Err(Error::InvalidData));
1251 assert_eq!(VendorId::from_str("01-ab"), Err(Error::InvalidData));
1252 assert_eq!(VendorId::from_str("01ab2c"), Err(Error::InvalidData));
1253 assert_eq!(VendorId::from_str("01:ab:2c"), Err(Error::InvalidData));
1254 }
1255
1256 #[test]
1257 fn test_fmt() {
1258 assert_eq!(format!("{}", VendorId([0x01, 0xAB, 0x2C])), "01-ab-2c");
1259 }
1260
1261 #[test]
1262 fn test_from_sys() {
1263 assert_eq!(
1264 VendorId::try_from_sys(SysVendorId::try_from(0x01ab2c).unwrap()),
1265 Ok(Some(VendorId([0x01, 0xAB, 0x2C])))
1266 );
1267
1268 assert_eq!(
1269 VendorId::try_from_sys(SysVendorId::try_from(0xFFFFFFFF).unwrap()),
1270 Ok(None)
1271 );
1272 }
1273
1274 #[test]
1275 fn test_into_i32() {
1276 assert_eq!(
1277 <_ as Into<i32>>::into(VendorId([0x01, 0xAB, 0x2C])),
1278 0x01ab2ci32
1279 );
1280 }
1281
1282 #[test]
1283 fn test_into_u32() {
1284 assert_eq!(
1285 <_ as Into<u32>>::into(VendorId([0x01, 0xAB, 0x2C])),
1286 0x01ab2cu32
1287 );
1288 }
1289
1290 #[test]
1291 fn test_into_sys() {
1292 assert_eq!(
1293 <_ as Into<SysVendorId>>::into(VendorId([0x01, 0xAB, 0x2C])),
1294 SysVendorId::try_from(0x01ab2c).unwrap()
1295 );
1296 }
1297
1298 #[cfg(feature = "serde")]
1299 #[test]
1300 fn test_serialize() {
1301 assert_eq!(
1302 to_string(&VendorId([0x01, 0xAB, 0x2C])).unwrap(),
1303 "\"01-ab-2c\""
1304 );
1305 }
1306
1307 #[cfg(feature = "serde")]
1308 #[test]
1309 fn test_deserialize() {
1310 assert_eq!(
1311 from_str::<VendorId>("\"01-ab-2c\"").unwrap(),
1312 VendorId([0x01, 0xAB, 0x2C])
1313 );
1314 }
1315
1316 #[cfg(feature = "serde")]
1317 #[test]
1318 fn test_deserialize_invalid() {
1319 assert!(from_str::<VendorId>("\"01-ab-2g\"").is_err());
1320 }
1321}
1322
1323#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1326#[repr(transparent)]
1327pub struct Timeout(u32);
1328
1329impl Timeout {
1330 pub const NONE: Timeout = Timeout(0);
1332
1333 pub const MAX: Timeout = Timeout(1000);
1335
1336 #[must_use]
1338 #[inline]
1339 pub fn as_ms(&self) -> u32 {
1340 self.0
1341 }
1342
1343 #[must_use]
1345 #[inline]
1346 pub fn from_ms(millis: u32) -> Timeout {
1347 Timeout(millis)
1348 }
1349}
1350
1351impl TryFrom<&Duration> for Timeout {
1352 type Error = Error;
1353
1354 #[inline]
1355 fn try_from(duration: &Duration) -> Result<Timeout> {
1356 let millis = duration.as_millis();
1357 if let Ok(millis) = u32::try_from(millis) {
1358 Ok(Timeout(millis))
1359 } else {
1360 Err(Error::OutOfRange {
1361 expected: Range::AtMost(0xFFFFFFFF),
1362 got: if let Ok(millis) = usize::try_from(millis) {
1363 millis
1364 } else {
1365 usize::MAX
1366 },
1367 quantity: "milliseconds",
1368 })
1369 }
1370 }
1371}
1372
1373impl From<Timeout> for Duration {
1374 fn from(val: Timeout) -> Duration {
1375 Duration::from_millis(val.as_ms().into())
1376 }
1377}
1378
1379#[cfg(test)]
1380mod test_timeout {
1381 use super::*;
1382
1383 #[test]
1384 fn test_from_duration() {
1385 assert_eq!(
1386 Timeout::try_from(&Duration::from_secs(2)),
1387 Ok(Timeout::from_ms(2000))
1388 );
1389 assert_eq!(
1390 Timeout::try_from(&Duration::from_millis(0x1_0000_0000)),
1391 Err(Error::OutOfRange {
1392 expected: Range::AtMost(0xFFFFFFFF),
1393 got: 0x1_0000_0000,
1394 quantity: "milliseconds",
1395 })
1396 );
1397 }
1398}
1399
1400pub type Result<T> = std::result::Result<T, Error>;