Skip to main content

mx_remote/wire/
uid.rs

1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! Device and bay identifiers.
5
6use core::fmt;
7
8/// Width of a device UID on the wire.
9pub(crate) const UID_LEN: usize = 16;
10
11/// The 16-byte unique identifier of an MX Remote device on the network.
12///
13/// The default value is the empty (all-zero) UID.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct DeviceUid([u8; UID_LEN]);
16
17/// A UID could not be read from the given text or bytes.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct UidParseError {
20    input: String,
21}
22
23impl fmt::Display for UidParseError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "invalid uid {:?}", self.input)
26    }
27}
28
29impl std::error::Error for UidParseError {}
30
31impl DeviceUid {
32    /// The all-zero UID.
33    pub const ZERO: Self = Self([0; UID_LEN]);
34
35    /// Builds a UID from its raw 16 bytes.
36    pub const fn from_array(raw: [u8; UID_LEN]) -> Self {
37        Self(raw)
38    }
39
40    /// Builds a UID from raw bytes.
41    ///
42    /// An empty slice yields [`DeviceUid::ZERO`]; any other length shorter than
43    /// 16 is an error. Trailing bytes past the first 16 are ignored.
44    pub fn from_bytes(b: &[u8]) -> Result<Self, UidParseError> {
45        if b.is_empty() {
46            return Ok(Self::ZERO);
47        }
48        match b
49            .get(..UID_LEN)
50            .and_then(|s| <[u8; UID_LEN]>::try_from(s).ok())
51        {
52            Some(raw) => Ok(Self(raw)),
53            None => Err(UidParseError {
54                input: format!("{b:02x?}"),
55            }),
56        }
57    }
58
59    /// Reports whether the UID is all zero.
60    pub fn is_zero(&self) -> bool {
61        self.0 == [0; UID_LEN]
62    }
63
64    /// Returns the raw 16-byte value.
65    pub const fn as_bytes(&self) -> &[u8; UID_LEN] {
66        &self.0
67    }
68}
69
70impl fmt::Display for DeviceUid {
71    /// Writes the dotted-hex form: four little-endian 32-bit words, each
72    /// printed big-endian, separated by dots.
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        for (i, word) in self.0.chunks_exact(4).enumerate() {
75            if i > 0 {
76                f.write_str(".")?;
77            }
78            for b in word.iter().rev() {
79                write!(f, "{b:02x}")?;
80            }
81        }
82        Ok(())
83    }
84}
85
86impl std::str::FromStr for DeviceUid {
87    type Err = UidParseError;
88
89    /// Parses the dotted-hex form written by [`fmt::Display`]. Fields past the
90    /// fourth are ignored.
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        let err = || UidParseError {
93            input: s.to_owned(),
94        };
95        let mut raw = [0u8; UID_LEN];
96        let mut parts = s.split('.');
97        for word in raw.chunks_exact_mut(4) {
98            let part = parts.next().ok_or_else(err)?;
99            let v = u32::from_str_radix(part, 16).map_err(|_| err())?;
100            word.copy_from_slice(&v.to_le_bytes());
101        }
102        Ok(Self(raw))
103    }
104}
105
106/// Identifies a single bay (port) by its owning device and port number.
107#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
108pub struct BayUid {
109    /// The device the bay belongs to.
110    pub device: DeviceUid,
111    /// The bay's port number on that device.
112    pub port: u16,
113}
114
115impl BayUid {
116    /// Builds a bay identifier from a device and a port number.
117    pub const fn new(device: DeviceUid, port: u16) -> Self {
118        Self { device, port }
119    }
120}
121
122impl fmt::Display for BayUid {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(f, "{}:{}", self.device, self.port)
125    }
126}