1use alloc::vec::Vec;
7use core::fmt;
8
9use crate::option::CoapOption;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum MessageType {
16 Confirmable,
18 NonConfirmable,
20 Acknowledgement,
22 Reset,
24}
25
26impl MessageType {
27 #[must_use]
32 pub const fn from_bits(v: u8) -> Option<Self> {
33 match v {
34 0 => Some(Self::Confirmable),
35 1 => Some(Self::NonConfirmable),
36 2 => Some(Self::Acknowledgement),
37 3 => Some(Self::Reset),
38 _ => None,
39 }
40 }
41
42 #[must_use]
44 pub const fn to_bits(self) -> u8 {
45 match self {
46 Self::Confirmable => 0,
47 Self::NonConfirmable => 1,
48 Self::Acknowledgement => 2,
49 Self::Reset => 3,
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub struct CoapCode {
65 pub class: u8,
67 pub detail: u8,
69}
70
71impl CoapCode {
72 #[must_use]
75 pub const fn new(class: u8, detail: u8) -> Self {
76 Self {
77 class: class & 0b111,
78 detail: detail & 0b1_1111,
79 }
80 }
81
82 #[must_use]
84 pub const fn from_byte(b: u8) -> Self {
85 Self {
86 class: (b >> 5) & 0b111,
87 detail: b & 0b1_1111,
88 }
89 }
90
91 #[must_use]
93 pub const fn to_byte(self) -> u8 {
94 (self.class << 5) | (self.detail & 0b1_1111)
95 }
96
97 pub const EMPTY: Self = Self::new(0, 0);
99 pub const GET: Self = Self::new(0, 1);
101 pub const POST: Self = Self::new(0, 2);
103 pub const PUT: Self = Self::new(0, 3);
105 pub const DELETE: Self = Self::new(0, 4);
107 pub const CREATED: Self = Self::new(2, 1);
109 pub const DELETED: Self = Self::new(2, 2);
111 pub const VALID: Self = Self::new(2, 3);
113 pub const CHANGED: Self = Self::new(2, 4);
115 pub const CONTENT: Self = Self::new(2, 5);
117 pub const BAD_REQUEST: Self = Self::new(4, 0);
119 pub const NOT_FOUND: Self = Self::new(4, 4);
121 pub const INTERNAL_SERVER_ERROR: Self = Self::new(5, 0);
123
124 #[must_use]
127 pub const fn is_request(self) -> bool {
128 self.class == 0 && self.detail > 0
129 }
130
131 #[must_use]
133 pub const fn is_success(self) -> bool {
134 self.class == 2
135 }
136
137 #[must_use]
139 pub const fn is_client_error(self) -> bool {
140 self.class == 4
141 }
142
143 #[must_use]
145 pub const fn is_server_error(self) -> bool {
146 self.class == 5
147 }
148
149 #[must_use]
151 pub const fn is_empty(self) -> bool {
152 self.class == 0 && self.detail == 0
153 }
154}
155
156impl fmt::Display for CoapCode {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 write!(f, "{}.{:02}", self.class, self.detail)
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct CoapMessage {
165 pub version: u8,
167 pub message_type: MessageType,
169 pub code: CoapCode,
171 pub message_id: u16,
173 pub token: Vec<u8>,
175 pub options: Vec<CoapOption>,
178 pub payload: Vec<u8>,
180}
181
182impl CoapMessage {
183 #[must_use]
186 pub const fn new(message_type: MessageType, code: CoapCode, message_id: u16) -> Self {
187 Self {
188 version: 1,
189 message_type,
190 code,
191 message_id,
192 token: Vec::new(),
193 options: Vec::new(),
194 payload: Vec::new(),
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn message_type_round_trips_via_bits() {
205 for t in [
207 MessageType::Confirmable,
208 MessageType::NonConfirmable,
209 MessageType::Acknowledgement,
210 MessageType::Reset,
211 ] {
212 assert_eq!(MessageType::from_bits(t.to_bits()), Some(t));
213 }
214 }
215
216 #[test]
217 fn message_type_rejects_out_of_range() {
218 for v in 4..=7 {
219 assert_eq!(MessageType::from_bits(v), None);
220 }
221 }
222
223 #[test]
224 fn code_byte_round_trip() {
225 for class in 0..8 {
227 for detail in 0..32 {
228 let c = CoapCode::new(class, detail);
229 assert_eq!(CoapCode::from_byte(c.to_byte()), c);
230 }
231 }
232 }
233
234 #[test]
235 fn well_known_codes_match_spec_values() {
236 assert_eq!(CoapCode::GET.to_byte(), 0b000_00001);
238 assert_eq!(CoapCode::POST.to_byte(), 0b000_00010);
239 assert_eq!(CoapCode::PUT.to_byte(), 0b000_00011);
240 assert_eq!(CoapCode::DELETE.to_byte(), 0b000_00100);
241 assert_eq!(CoapCode::CONTENT.to_byte(), 0x45);
243 assert_eq!(CoapCode::NOT_FOUND.to_byte(), 0x84);
245 assert_eq!(CoapCode::INTERNAL_SERVER_ERROR.to_byte(), 0xA0);
247 }
248
249 #[test]
250 fn code_display_uses_dotted_format() {
251 assert_eq!(alloc::format!("{}", CoapCode::GET), "0.01");
253 assert_eq!(alloc::format!("{}", CoapCode::CONTENT), "2.05");
254 assert_eq!(alloc::format!("{}", CoapCode::NOT_FOUND), "4.04");
255 assert_eq!(
256 alloc::format!("{}", CoapCode::INTERNAL_SERVER_ERROR),
257 "5.00"
258 );
259 }
260
261 #[test]
262 fn code_classification_predicates() {
263 assert!(CoapCode::GET.is_request());
264 assert!(!CoapCode::GET.is_success());
265 assert!(CoapCode::CONTENT.is_success());
266 assert!(CoapCode::NOT_FOUND.is_client_error());
267 assert!(CoapCode::INTERNAL_SERVER_ERROR.is_server_error());
268 assert!(CoapCode::EMPTY.is_empty());
269 assert!(!CoapCode::GET.is_empty());
270 }
271
272 #[test]
273 fn new_message_defaults_version_to_1() {
274 let m = CoapMessage::new(MessageType::Confirmable, CoapCode::GET, 42);
276 assert_eq!(m.version, 1);
277 assert_eq!(m.message_id, 42);
278 assert!(m.token.is_empty());
279 assert!(m.options.is_empty());
280 assert!(m.payload.is_empty());
281 }
282}