Skip to main content

linux_cec/
lib.rs

1/*
2 * Copyright © 2024 Valve Software
3 * SPDX-License-Identifier: LGPL-2.1-or-later
4 */
5
6use 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/// A CEC logical address, used for identifying devices
33/// attached to the CEC bus.
34#[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    /** When used as initiator address */
138    pub const Unregistered: LogicalAddress = LogicalAddress::UnregisteredOrBroadcast;
139    /** When used as destination address */
140    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/// The type of a CEC logical address, used for determining what type
207/// type of device is at the given address and for requesting an address.
208#[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/// An mode specifying how a given [`Device`](device::Device) should act as
264/// an initiator; that is, if the device should be able to send messages.
265#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
266#[non_exhaustive]
267pub enum InitiatorMode {
268    /// Do not act as an initiator.
269    Disabled,
270    /// Act as an initiator.
271    Enabled,
272    /// Act as an initiator and disallow other processes
273    /// acting as an initiator while the device is open.
274    Exclusive,
275}
276
277/// A mode specifying how a given [`Device`](device::Device) should act as
278/// a follower; that is, how receiving messages should be handled.
279#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
280#[non_exhaustive]
281pub enum FollowerMode {
282    /// Do not act as a follower.
283    Disabled,
284    /// Act as a follower.
285    Enabled,
286    /// Act as a follower and disallow other processes
287    /// acting as an follower while the device is open.
288    Exclusive,
289    /// Act as a follower and pass through all messages, bypassing
290    /// any in-kernel processing that would normally be done.
291    ExclusivePassthru,
292    /// Enable monitoring of applicable [`Pin`](device::Pin)s. This mode requires
293    /// [`Capabilities::MONITOR_PIN`](device::Capabilities::MONITOR_PIN) to be
294    /// present on the device.
295    MonitorPin,
296    /// Enable monitoring of all messages on the CEC bus, not just messages
297    /// addressed to this device and broadcast messages. This requires
298    /// [`Capabilities::MONITOR_ALL`](device::Capabilities::MONITOR_ALL) to be
299    /// present on the device.
300    Monitor,
301    /// Enable monitoring of applicable [`Pin`](device::Pin)s and all messages on the
302    /// CEC bus, not just messages addressed to this device and broadcast messages.
303    /// This requires [`Capabilities::MONITOR_PIN`](device::Capabilities::MONITOR_PIN)
304    /// and [`Capabilities::MONITOR_ALL`](device::Capabilities::MONITOR_ALL) to be
305    /// present on the device.
306    MonitorAll,
307}
308
309/// This enum encodes the diferent ways in which a given message may be
310/// allowed to be addressed.
311///
312/// Hypothetically all messages can be addressed either directly or broadcast;
313/// however, the specification states that each message type only has specific
314/// ways it can be addressed. This enum encodes the various ways a message may
315/// be allowed to be addressed, per the specification, and can be queried on
316/// either a [`Message`](crate::message::Message) or an
317/// [`Opcode`](crate::message::Opcode).
318#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
319pub enum AddressingType {
320    /// The given message can only be addressed
321    /// directly to a specific logical address.
322    Direct,
323    /// The given message can only be broadcast to all logical addresses.
324    Broadcast,
325    /// The given message can be either directly addressed or broadcast.
326    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/// A set of common errors.
486#[derive(Error, Clone, Debug, PartialEq)]
487#[non_exhaustive]
488pub enum Error {
489    /// A value of a given `quantity` was outside of the `expected` range.
490    #[error("Expected {expected} {quantity}, got {got} {quantity}")]
491    OutOfRange {
492        expected: Range<usize>,
493        got: usize,
494        quantity: &'static str,
495    },
496    /// Got a `value` for a given type that was invalid for that `ty`.
497    #[error("Invalid value {value} for type {ty}")]
498    InvalidValueForType { ty: &'static str, value: String },
499    /// Got generic invalid data.
500    #[error("The provided data was not valid")]
501    InvalidData,
502    /// A timeout occurred.
503    #[error("A timeout occurred")]
504    Timeout,
505    /// A request was aborted.
506    #[error("The request was aborted")]
507    Abort,
508    /// The request failed because the device doesn't have a logical address.
509    #[error("The device does not have a logical address")]
510    NoLogicalAddress,
511    /// The device was disconnected from the system.
512    #[error("The device was disconnected")]
513    Disconnected,
514    /// An unsupported operation was attempted on this device.
515    #[error("This device does not support this operation")]
516    Unsupported,
517    /// A generic system error occurred.
518    #[error("Got unexpected result from system: {0}")]
519    SystemError(Errno),
520    /// Got an error while transmitting a [`Message`](crate::message::Message)
521    /// that did not correspond to one of the other error types.
522    #[error("{0}")]
523    TxError(#[from] TxError),
524    /// Got an error while receiving a [`Message`](crate::message::Message)
525    /// that did not correspond to one of the other error types.
526    #[error("{0}")]
527    RxError(#[from] RxError),
528    /// Got an error that does not map to any of the other error types.
529    #[error("Unknown error: {0}")]
530    UnknownError(String),
531}
532
533/// A set of error codes that correspond to [`CEC_TX_STATUS`].
534#[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/// A set of error codes that correspond to [`CEC_RX_STATUS`](sys::CEC_RX_STATUS).
555#[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/// A unique 16-bit value that refers to a single
641/// device in the topology of the CDC network.
642#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
643#[repr(transparent)]
644pub struct PhysicalAddress(pub(crate) u16);
645
646impl PhysicalAddress {
647    /// The physical address reserved for the root device of the CEC hierarchy
648    pub const ROOT: PhysicalAddress = PhysicalAddress(0x0000);
649
650    /// The physical address reserved to denote an invalid address
651    pub const INVALID: PhysicalAddress = PhysicalAddress(0xFFFF);
652
653    /// Check whether or not the physical address is valid, i.e. not `f.f.f.f`.
654    #[must_use]
655    #[inline]
656    pub fn is_valid(&self) -> bool {
657        self.0 != 0xFFFF
658    }
659
660    /// Check whether or not the physical address is the root device, i.e. `0.0.0.0`.
661    #[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/// A 24-bit [MA-L/OUI](https://en.wikipedia.org/wiki/Organizationally_unique_identifier)
855/// identifying a device's vendor or manufacturer.
856///
857/// These IDs are obtained from the IEEE, and a current list of OUIs can be queried from
858/// [their website](https://regauth.standards.ieee.org/standards-ra-web/pub/view.html#registries).
859/// A full list is also available as [plain text](https://standards-oui.ieee.org/oui/oui.txt) or
860/// [CSV](https://standards-oui.ieee.org/oui/oui.csv).
861#[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    /// Convert a [`linux_cec_sys::VendorId`] into a `VendorId`. Since `linux_cec_sys::VendorId` is just
921    /// a convenience type around `u32`, not all values are valid, so this conversion can fail: the
922    /// reserved value 0xFFFFFFFF is treated as `Ok(None)`, and all over values outside of the valid range
923    /// return [`Error::InvalidData`].
924    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/// A convenience type for an unsigned 32-bit millisecond-granularity
1008/// timeout, as used in the kernel interface.
1009#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1010#[repr(transparent)]
1011pub struct Timeout(u32);
1012
1013impl Timeout {
1014    /// A timeout that never expires.
1015    pub const NONE: Timeout = Timeout(0);
1016
1017    /// The maximum timeout allowed by CEC.
1018    pub const MAX: Timeout = Timeout(1000);
1019
1020    /// Return the number of milliseconds for this timeout.
1021    #[must_use]
1022    #[inline]
1023    pub fn as_ms(&self) -> u32 {
1024        self.0
1025    }
1026
1027    /// Create a timeout for a number of milliseconds.
1028    #[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>;