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};
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/// A CEC logical address, used for identifying devices
39/// attached to the CEC bus.
40#[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    /** When used as initiator address */
145    pub const Unregistered: LogicalAddress = LogicalAddress::UnregisteredOrBroadcast;
146    /** When used as destination address */
147    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/// The type of a CEC logical address, used for determining what type
331/// type of device is at the given address and for requesting an address.
332#[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/// An mode specifying how a given [`Device`](device::Device) should act as
425/// an initiator; that is, if the device should be able to send messages.
426#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
427#[non_exhaustive]
428pub enum InitiatorMode {
429    /// Do not act as an initiator.
430    Disabled,
431    /// Act as an initiator.
432    Enabled,
433    /// Act as an initiator and disallow other processes
434    /// acting as an initiator while the device is open.
435    Exclusive,
436}
437
438/// A mode specifying how a given [`Device`](device::Device) should act as
439/// a follower; that is, how receiving messages should be handled.
440#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
441#[non_exhaustive]
442pub enum FollowerMode {
443    /// Do not act as a follower.
444    Disabled,
445    /// Act as a follower.
446    Enabled,
447    /// Act as a follower and disallow other processes
448    /// acting as an follower while the device is open.
449    Exclusive,
450    /// Act as a follower and pass through all messages, bypassing
451    /// any in-kernel processing that would normally be done.
452    ExclusivePassthru,
453    /// Enable monitoring of applicable [`Pin`](device::Pin)s. This mode requires
454    /// [`Capabilities::MONITOR_PIN`](device::Capabilities::MONITOR_PIN) to be
455    /// present on the device.
456    MonitorPin,
457    /// Enable monitoring of all messages on the CEC bus, not just messages
458    /// addressed to this device and broadcast messages. This requires
459    /// [`Capabilities::MONITOR_ALL`](device::Capabilities::MONITOR_ALL) to be
460    /// present on the device.
461    Monitor,
462    /// Enable monitoring of applicable [`Pin`](device::Pin)s and all messages on the
463    /// CEC bus, not just messages addressed to this device and broadcast messages.
464    /// This requires [`Capabilities::MONITOR_PIN`](device::Capabilities::MONITOR_PIN)
465    /// and [`Capabilities::MONITOR_ALL`](device::Capabilities::MONITOR_ALL) to be
466    /// present on the device.
467    MonitorAll,
468}
469
470/// This enum encodes the diferent ways in which a given message may be
471/// allowed to be addressed.
472///
473/// Hypothetically all messages can be addressed either directly or broadcast;
474/// however, the specification states that each message type only has specific
475/// ways it can be addressed. This enum encodes the various ways a message may
476/// be allowed to be addressed, per the specification, and can be queried on
477/// either a [`Message`](crate::message::Message) or an
478/// [`Opcode`](crate::message::Opcode).
479#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
480pub enum AddressingType {
481    /// The given message can only be addressed
482    /// directly to a specific logical address.
483    Direct,
484    /// The given message can only be broadcast to all logical addresses.
485    Broadcast,
486    /// The given message can be either directly addressed or broadcast.
487    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/// A set of common errors.
647#[derive(Error, Clone, Debug, PartialEq)]
648#[non_exhaustive]
649pub enum Error {
650    /// A value of a given `quantity` was outside of the `expected` range.
651    #[error("Expected {expected} {quantity}, got {got} {quantity}")]
652    OutOfRange {
653        expected: Range<usize>,
654        got: usize,
655        quantity: &'static str,
656    },
657    /// Got a `value` for a given type that was invalid for that `ty`.
658    #[error("Invalid value {value} for type {ty}")]
659    InvalidValueForType { ty: &'static str, value: String },
660    /// Got generic invalid data.
661    #[error("The provided data was not valid")]
662    InvalidData,
663    /// A timeout occurred.
664    #[error("A timeout occurred")]
665    Timeout,
666    /// A request was aborted.
667    #[error("The request was aborted")]
668    Abort,
669    /// The request failed because the device doesn't have a logical address.
670    #[error("The device does not have a logical address")]
671    NoLogicalAddress,
672    /// The device was disconnected from the system.
673    #[error("The device was disconnected")]
674    Disconnected,
675    /// An unsupported operation was attempted on this device.
676    #[error("This device does not support this operation")]
677    Unsupported,
678    /// A generic system error occurred.
679    #[error("Got unexpected result from system: {0}")]
680    SystemError(Errno),
681    /// Got an error while transmitting a [`Message`](crate::message::Message)
682    /// that did not correspond to one of the other error types.
683    #[error("{0}")]
684    TxError(#[from] TxError),
685    /// Got an error while receiving a [`Message`](crate::message::Message)
686    /// that did not correspond to one of the other error types.
687    #[error("{0}")]
688    RxError(#[from] RxError),
689    /// Got an error that does not map to any of the other error types.
690    #[error("Unknown error: {0}")]
691    UnknownError(String),
692}
693
694/// A set of error codes that correspond to [`CEC_TX_STATUS`].
695#[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/// A set of error codes that correspond to [`CEC_RX_STATUS`](sys::CEC_RX_STATUS).
716#[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/// A unique 16-bit value that refers to a single
802/// device in the topology of the CDC network.
803#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
804#[repr(transparent)]
805pub struct PhysicalAddress(pub(crate) u16);
806
807impl PhysicalAddress {
808    /// The physical address reserved for the root device of the CEC hierarchy
809    pub const ROOT: PhysicalAddress = PhysicalAddress(0x0000);
810
811    /// The physical address reserved to denote an invalid address
812    pub const INVALID: PhysicalAddress = PhysicalAddress(0xFFFF);
813
814    /// Check whether or not the physical address is valid, i.e. not `f.f.f.f`.
815    #[must_use]
816    #[inline]
817    pub fn is_valid(&self) -> bool {
818        self.0 != 0xFFFF
819    }
820
821    /// Check whether or not the physical address is the root device, i.e. `0.0.0.0`.
822    #[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/// A 24-bit [MA-L/OUI](https://en.wikipedia.org/wiki/Organizationally_unique_identifier)
1120/// identifying a device's vendor or manufacturer.
1121///
1122/// These IDs are obtained from the IEEE, and a current list of OUIs can be queried from
1123/// [their website](https://regauth.standards.ieee.org/standards-ra-web/pub/view.html#registries).
1124/// A full list is also available as [plain text](https://standards-oui.ieee.org/oui/oui.txt) or
1125/// [CSV](https://standards-oui.ieee.org/oui/oui.csv).
1126#[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    /// Convert a [`linux_cec_sys::VendorId`] into a `VendorId`. Since `linux_cec_sys::VendorId` is just
1186    /// a convenience type around `u32`, not all values are valid, so this conversion can fail: the
1187    /// reserved value 0xFFFFFFFF is treated as `Ok(None)`, and all over values outside of the valid range
1188    /// return [`Error::InvalidData`].
1189    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/// A convenience type for an unsigned 32-bit millisecond-granularity
1324/// timeout, as used in the kernel interface.
1325#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1326#[repr(transparent)]
1327pub struct Timeout(u32);
1328
1329impl Timeout {
1330    /// A timeout that never expires.
1331    pub const NONE: Timeout = Timeout(0);
1332
1333    /// The maximum timeout allowed by CEC.
1334    pub const MAX: Timeout = Timeout(1000);
1335
1336    /// Return the number of milliseconds for this timeout.
1337    #[must_use]
1338    #[inline]
1339    pub fn as_ms(&self) -> u32 {
1340        self.0
1341    }
1342
1343    /// Create a timeout for a number of milliseconds.
1344    #[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>;