coap-zero 0.3.0

CoAP protocol implementation for no_std without alloc
Documentation
// Copyright Open Logistics Foundation
//
// Licensed under the Open Logistics Foundation License 1.3.
// For details on the licensing terms, see the LICENSE file.
// SPDX-License-Identifier: OLFL-1.3

//! A CoAP Token to match a late response to the original request.

use core::hash::Hash;

use super::Error;

/// Amount of valid bytes in Token.bytes
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TokenLength {
    Zero = 0,
    One = 1,
    Two = 2,
    Three = 3,
    Four = 4,
    Five = 5,
    Six = 6,
    Seven = 7,
    Eight = 8,
}

impl TryFrom<u8> for TokenLength {
    type Error = Error;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(TokenLength::Zero),
            1 => Ok(TokenLength::One),
            2 => Ok(TokenLength::Two),
            3 => Ok(TokenLength::Three),
            4 => Ok(TokenLength::Four),
            5 => Ok(TokenLength::Five),
            6 => Ok(TokenLength::Six),
            7 => Ok(TokenLength::Seven),
            8 => Ok(TokenLength::Eight),
            _ => Err(Error::TokenLength),
        }
    }
}

#[allow(clippy::from_over_into)] // We cannot implement From but TryFrom. But we don't need TryInto and can use Into directly.
impl Into<u8> for TokenLength {
    fn into(self) -> u8 {
        self as u8
    }
}

impl TryFrom<usize> for TokenLength {
    type Error = Error;

    fn try_from(value: usize) -> Result<Self, Self::Error> {
        if value > 255 {
            Err(Error::TokenLength)
        } else {
            Self::try_from(value as u8)
        }
    }
}

#[allow(clippy::from_over_into)] // We cannot implement From but TryFrom. But we don't need TryInto and can use Into directly.
impl Into<usize> for TokenLength {
    fn into(self) -> usize {
        self as usize
    }
}

/// Represents a CoAP Token (0-8 Bytes)
///
/// Implements [`Ord`], [`Eq`] and [`Hash`] so it may be stored in whatever datastructure seems
/// appropriate. This might be desirable when the CoAP Observe feature shall be used.
#[derive(Debug, Clone, Copy)]
pub struct Token {
    /// The bytes of the token.
    /// Supports full length (8 bytes) token.
    pub(crate) bytes: [u8; 8],
    /// Actual size of token in bytes (0-8),
    /// so the actual token is token.bytes[..token.length as usize]
    pub(crate) length: TokenLength,
}

impl Default for Token {
    fn default() -> Self {
        Self {
            bytes: [0_u8; 8],
            length: TokenLength::Zero,
        }
    }
}

impl Token {
    pub(crate) fn try_new<RNG: embedded_hal::blocking::rng::Read>(
        length: TokenLength,
        rng: &mut RNG,
    ) -> Result<Self, RNG::Error> {
        let mut token = Token {
            length,
            ..Default::default()
        };

        rng.read(&mut token.bytes[..token.length as usize])?;

        Ok(token)
    }
}

impl TryFrom<&[u8]> for Token {
    type Error = Error;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let mut token = Token {
            bytes: [0_u8; 8],
            length: (value.len() as u8).try_into()?,
        };

        token.bytes[..token.length.into()].copy_from_slice(value);

        Ok(token)
    }
}

impl PartialEq for Token {
    fn eq(&self, other: &Self) -> bool {
        self.bytes[..self.length.into()] == other.bytes[..other.length.into()]
    }
}

impl Eq for Token {}

impl PartialOrd for Token {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Token {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        if self.length != other.length {
            return self.length.cmp(&other.length);
        }
        self.bytes[..self.length.into()].cmp(&other.bytes[..self.length.into()])
    }
}

impl Hash for Token {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.bytes[..self.length.into()].hash(state)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn partial_eq() {
        let a = Token {
            bytes: [0; 8],
            length: 8_u8.try_into().unwrap(),
        };
        let b = Token {
            bytes: [1; 8],
            length: 8_u8.try_into().unwrap(),
        };
        let c = Token {
            bytes: [0; 8],
            length: 4_u8.try_into().unwrap(),
        };
        assert_eq!(a, a); // same
        assert_ne!(a, b); // different bytes
        assert_ne!(a, c); // different length
        assert_ne!(b, c); // all different
    }
}