coap_zero/message/
token.rs

1// Copyright Open Logistics Foundation
2//
3// Licensed under the Open Logistics Foundation License 1.3.
4// For details on the licensing terms, see the LICENSE file.
5// SPDX-License-Identifier: OLFL-1.3
6
7//! A CoAP Token to match a late response to the original request.
8
9use core::hash::Hash;
10
11use super::Error;
12
13/// Amount of valid bytes in Token.bytes
14#[allow(missing_docs)]
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum TokenLength {
17    Zero = 0,
18    One = 1,
19    Two = 2,
20    Three = 3,
21    Four = 4,
22    Five = 5,
23    Six = 6,
24    Seven = 7,
25    Eight = 8,
26}
27
28impl TryFrom<u8> for TokenLength {
29    type Error = Error;
30
31    fn try_from(value: u8) -> Result<Self, Self::Error> {
32        match value {
33            0 => Ok(TokenLength::Zero),
34            1 => Ok(TokenLength::One),
35            2 => Ok(TokenLength::Two),
36            3 => Ok(TokenLength::Three),
37            4 => Ok(TokenLength::Four),
38            5 => Ok(TokenLength::Five),
39            6 => Ok(TokenLength::Six),
40            7 => Ok(TokenLength::Seven),
41            8 => Ok(TokenLength::Eight),
42            _ => Err(Error::TokenLength),
43        }
44    }
45}
46
47#[allow(clippy::from_over_into)] // We cannot implement From but TryFrom. But we don't need TryInto and can use Into directly.
48impl Into<u8> for TokenLength {
49    fn into(self) -> u8 {
50        self as u8
51    }
52}
53
54impl TryFrom<usize> for TokenLength {
55    type Error = Error;
56
57    fn try_from(value: usize) -> Result<Self, Self::Error> {
58        if value > 255 {
59            Err(Error::TokenLength)
60        } else {
61            Self::try_from(value as u8)
62        }
63    }
64}
65
66#[allow(clippy::from_over_into)] // We cannot implement From but TryFrom. But we don't need TryInto and can use Into directly.
67impl Into<usize> for TokenLength {
68    fn into(self) -> usize {
69        self as usize
70    }
71}
72
73/// Represents a CoAP Token (0-8 Bytes)
74///
75/// Implements [`Ord`], [`Eq`] and [`Hash`] so it may be stored in whatever datastructure seems
76/// appropriate. This might be desirable when the CoAP Observe feature shall be used.
77#[derive(Debug, Clone, Copy)]
78pub struct Token {
79    /// The bytes of the token.
80    /// Supports full length (8 bytes) token.
81    pub(crate) bytes: [u8; 8],
82    /// Actual size of token in bytes (0-8),
83    /// so the actual token is token.bytes[..token.length as usize]
84    pub(crate) length: TokenLength,
85}
86
87impl Default for Token {
88    fn default() -> Self {
89        Self {
90            bytes: [0_u8; 8],
91            length: TokenLength::Zero,
92        }
93    }
94}
95
96impl Token {
97    pub(crate) fn try_new<RNG: embedded_hal::blocking::rng::Read>(
98        length: TokenLength,
99        rng: &mut RNG,
100    ) -> Result<Self, RNG::Error> {
101        let mut token = Token {
102            length,
103            ..Default::default()
104        };
105
106        rng.read(&mut token.bytes[..token.length as usize])?;
107
108        Ok(token)
109    }
110}
111
112impl TryFrom<&[u8]> for Token {
113    type Error = Error;
114
115    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
116        let mut token = Token {
117            bytes: [0_u8; 8],
118            length: (value.len() as u8).try_into()?,
119        };
120
121        token.bytes[..token.length.into()].copy_from_slice(value);
122
123        Ok(token)
124    }
125}
126
127impl PartialEq for Token {
128    fn eq(&self, other: &Self) -> bool {
129        self.bytes[..self.length.into()] == other.bytes[..other.length.into()]
130    }
131}
132
133impl Eq for Token {}
134
135impl PartialOrd for Token {
136    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
137        Some(self.cmp(other))
138    }
139}
140
141impl Ord for Token {
142    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
143        if self.length != other.length {
144            return self.length.cmp(&other.length);
145        }
146        self.bytes[..self.length.into()].cmp(&other.bytes[..self.length.into()])
147    }
148}
149
150impl Hash for Token {
151    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
152        self.bytes[..self.length.into()].hash(state)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn partial_eq() {
162        let a = Token {
163            bytes: [0; 8],
164            length: 8_u8.try_into().unwrap(),
165        };
166        let b = Token {
167            bytes: [1; 8],
168            length: 8_u8.try_into().unwrap(),
169        };
170        let c = Token {
171            bytes: [0; 8],
172            length: 4_u8.try_into().unwrap(),
173        };
174        assert_eq!(a, a); // same
175        assert_ne!(a, b); // different bytes
176        assert_ne!(a, c); // different length
177        assert_ne!(b, c); // all different
178    }
179}