use alloc::vec::Vec;
use core::fmt;
use crate::option::CoapOption;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MessageType {
Confirmable,
NonConfirmable,
Acknowledgement,
Reset,
}
impl MessageType {
#[must_use]
pub const fn from_bits(v: u8) -> Option<Self> {
match v {
0 => Some(Self::Confirmable),
1 => Some(Self::NonConfirmable),
2 => Some(Self::Acknowledgement),
3 => Some(Self::Reset),
_ => None,
}
}
#[must_use]
pub const fn to_bits(self) -> u8 {
match self {
Self::Confirmable => 0,
Self::NonConfirmable => 1,
Self::Acknowledgement => 2,
Self::Reset => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CoapCode {
pub class: u8,
pub detail: u8,
}
impl CoapCode {
#[must_use]
pub const fn new(class: u8, detail: u8) -> Self {
Self {
class: class & 0b111,
detail: detail & 0b1_1111,
}
}
#[must_use]
pub const fn from_byte(b: u8) -> Self {
Self {
class: (b >> 5) & 0b111,
detail: b & 0b1_1111,
}
}
#[must_use]
pub const fn to_byte(self) -> u8 {
(self.class << 5) | (self.detail & 0b1_1111)
}
pub const EMPTY: Self = Self::new(0, 0);
pub const GET: Self = Self::new(0, 1);
pub const POST: Self = Self::new(0, 2);
pub const PUT: Self = Self::new(0, 3);
pub const DELETE: Self = Self::new(0, 4);
pub const CREATED: Self = Self::new(2, 1);
pub const DELETED: Self = Self::new(2, 2);
pub const VALID: Self = Self::new(2, 3);
pub const CHANGED: Self = Self::new(2, 4);
pub const CONTENT: Self = Self::new(2, 5);
pub const BAD_REQUEST: Self = Self::new(4, 0);
pub const NOT_FOUND: Self = Self::new(4, 4);
pub const INTERNAL_SERVER_ERROR: Self = Self::new(5, 0);
#[must_use]
pub const fn is_request(self) -> bool {
self.class == 0 && self.detail > 0
}
#[must_use]
pub const fn is_success(self) -> bool {
self.class == 2
}
#[must_use]
pub const fn is_client_error(self) -> bool {
self.class == 4
}
#[must_use]
pub const fn is_server_error(self) -> bool {
self.class == 5
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.class == 0 && self.detail == 0
}
}
impl fmt::Display for CoapCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{:02}", self.class, self.detail)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoapMessage {
pub version: u8,
pub message_type: MessageType,
pub code: CoapCode,
pub message_id: u16,
pub token: Vec<u8>,
pub options: Vec<CoapOption>,
pub payload: Vec<u8>,
}
impl CoapMessage {
#[must_use]
pub const fn new(message_type: MessageType, code: CoapCode, message_id: u16) -> Self {
Self {
version: 1,
message_type,
code,
message_id,
token: Vec::new(),
options: Vec::new(),
payload: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn message_type_round_trips_via_bits() {
for t in [
MessageType::Confirmable,
MessageType::NonConfirmable,
MessageType::Acknowledgement,
MessageType::Reset,
] {
assert_eq!(MessageType::from_bits(t.to_bits()), Some(t));
}
}
#[test]
fn message_type_rejects_out_of_range() {
for v in 4..=7 {
assert_eq!(MessageType::from_bits(v), None);
}
}
#[test]
fn code_byte_round_trip() {
for class in 0..8 {
for detail in 0..32 {
let c = CoapCode::new(class, detail);
assert_eq!(CoapCode::from_byte(c.to_byte()), c);
}
}
}
#[test]
fn well_known_codes_match_spec_values() {
assert_eq!(CoapCode::GET.to_byte(), 0b000_00001);
assert_eq!(CoapCode::POST.to_byte(), 0b000_00010);
assert_eq!(CoapCode::PUT.to_byte(), 0b000_00011);
assert_eq!(CoapCode::DELETE.to_byte(), 0b000_00100);
assert_eq!(CoapCode::CONTENT.to_byte(), 0x45);
assert_eq!(CoapCode::NOT_FOUND.to_byte(), 0x84);
assert_eq!(CoapCode::INTERNAL_SERVER_ERROR.to_byte(), 0xA0);
}
#[test]
fn code_display_uses_dotted_format() {
assert_eq!(alloc::format!("{}", CoapCode::GET), "0.01");
assert_eq!(alloc::format!("{}", CoapCode::CONTENT), "2.05");
assert_eq!(alloc::format!("{}", CoapCode::NOT_FOUND), "4.04");
assert_eq!(
alloc::format!("{}", CoapCode::INTERNAL_SERVER_ERROR),
"5.00"
);
}
#[test]
fn code_classification_predicates() {
assert!(CoapCode::GET.is_request());
assert!(!CoapCode::GET.is_success());
assert!(CoapCode::CONTENT.is_success());
assert!(CoapCode::NOT_FOUND.is_client_error());
assert!(CoapCode::INTERNAL_SERVER_ERROR.is_server_error());
assert!(CoapCode::EMPTY.is_empty());
assert!(!CoapCode::GET.is_empty());
}
#[test]
fn new_message_defaults_version_to_1() {
let m = CoapMessage::new(MessageType::Confirmable, CoapCode::GET, 42);
assert_eq!(m.version, 1);
assert_eq!(m.message_id, 42);
assert!(m.token.is_empty());
assert!(m.options.is_empty());
assert!(m.payload.is_empty());
}
}