1use std::fmt;
15
16pub const HEADER_LEN: usize = 4;
18
19pub const PAYLOAD_MARKER: u8 = 0xFF;
21
22pub const MAX_MESSAGE_LEN: usize = 1152;
42
43pub const MAX_PAYLOAD_LEN: usize = 1024;
51
52pub const MAX_REPRESENTATION_LEN: usize = 64 * 1024;
65
66pub const VERSION: u8 = 1;
68
69pub const MAX_TOKEN_LEN: usize = 8;
78
79pub const MAX_OPTION_LEN: usize = u16::MAX as usize;
86
87pub const OPT_IF_MATCH: u16 = 1;
92pub const OPT_URI_HOST: u16 = 3;
93pub const OPT_ETAG: u16 = 4;
94pub const OPT_IF_NONE_MATCH: u16 = 5;
95pub const OPT_OBSERVE: u16 = 6;
98pub const OPT_URI_PORT: u16 = 7;
99pub const OPT_LOCATION_PATH: u16 = 8;
100pub const OPT_URI_PATH: u16 = 11;
101pub const OPT_CONTENT_FORMAT: u16 = 12;
102pub const OPT_MAX_AGE: u16 = 14;
103pub const OPT_URI_QUERY: u16 = 15;
104pub const OPT_ACCEPT: u16 = 17;
105pub const OPT_LOCATION_QUERY: u16 = 20;
106pub const OPT_BLOCK2: u16 = 23;
108pub const OPT_BLOCK1: u16 = 27;
110pub const OPT_SIZE2: u16 = 28;
112pub const OPT_SIZE1: u16 = 60;
115
116pub fn uint_option(value: u32) -> Vec<u8> {
119 let bytes = value.to_be_bytes();
120 let first = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len());
121 bytes[first..].to_vec()
122}
123
124pub const CODE_EMPTY: u8 = 0x00;
130pub const CODE_GET: u8 = 0x01;
131pub const CODE_POST: u8 = 0x02;
132pub const CODE_PUT: u8 = 0x03;
133pub const CODE_DELETE: u8 = 0x04;
134
135pub const fn code(class: u8, detail: u8) -> u8 {
137 (class << 5) | (detail & 0x1F)
138}
139
140pub const CODE_CONTENT: u8 = code(2, 5); pub const CODE_CONTINUE: u8 = code(2, 31); pub const CODE_BAD_REQUEST: u8 = code(4, 0); pub const CODE_BAD_OPTION: u8 = code(4, 2); pub const CODE_NOT_FOUND: u8 = code(4, 4); pub const CODE_METHOD_NOT_ALLOWED: u8 = code(4, 5); pub const CODE_REQUEST_ENTITY_INCOMPLETE: u8 = code(4, 8); pub const CODE_REQUEST_ENTITY_TOO_LARGE: u8 = code(4, 13); pub const CODE_INTERNAL_SERVER_ERROR: u8 = code(5, 0); pub const CODE_SERVICE_UNAVAILABLE: u8 = code(5, 3); pub const fn code_class(code: u8) -> u8 {
153 code >> 5
154}
155
156pub const fn code_detail(code: u8) -> u8 {
158 code & 0x1F
159}
160
161pub fn code_to_string(code: u8) -> String {
163 format!("{}.{:02}", code_class(code), code_detail(code))
164}
165
166pub fn parse_code_string(s: &str) -> Option<u8> {
168 let token = s.split_whitespace().next()?;
169 let (class, detail) = token.split_once('.')?;
170 let class: u8 = class.trim().parse().ok()?;
171 let detail: u8 = detail.trim().parse().ok()?;
172 if class > 7 || detail > 31 {
173 return None;
174 }
175 Some(code(class, detail))
176}
177
178pub fn method_name(code: u8) -> Option<&'static str> {
180 match code {
181 CODE_GET => Some("GET"),
182 CODE_POST => Some("POST"),
183 CODE_PUT => Some("PUT"),
184 CODE_DELETE => Some("DELETE"),
185 _ => None,
186 }
187}
188
189pub fn content_format_name(id: u16) -> Option<&'static str> {
195 match id {
196 0 => Some("text/plain"),
197 40 => Some("application/link-format"),
198 41 => Some("application/xml"),
199 42 => Some("application/octet-stream"),
200 47 => Some("application/exi"),
201 50 => Some("application/json"),
202 60 => Some("application/cbor"),
203 _ => None,
204 }
205}
206
207pub fn content_format_id(name: &str) -> Option<u16> {
209 let n = name.trim().to_ascii_lowercase();
210 let n = n.split(';').next().unwrap_or(&n).trim().to_string();
212 match n.as_str() {
213 "text/plain" | "text" | "plain" => Some(0),
214 "application/link-format" | "link-format" => Some(40),
215 "application/xml" | "xml" => Some(41),
216 "application/octet-stream" | "octet-stream" | "binary" => Some(42),
217 "application/exi" | "exi" => Some(47),
218 "application/json" | "json" => Some(50),
219 "application/cbor" | "cbor" => Some(60),
220 _ => None,
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum MessageType {
231 Confirmable,
232 NonConfirmable,
233 Acknowledgement,
234 Reset,
235}
236
237impl MessageType {
238 pub fn from_bits(bits: u8) -> MessageType {
239 match bits & 0x03 {
240 0 => MessageType::Confirmable,
241 1 => MessageType::NonConfirmable,
242 2 => MessageType::Acknowledgement,
243 _ => MessageType::Reset,
244 }
245 }
246
247 pub fn to_bits(self) -> u8 {
248 match self {
249 MessageType::Confirmable => 0,
250 MessageType::NonConfirmable => 1,
251 MessageType::Acknowledgement => 2,
252 MessageType::Reset => 3,
253 }
254 }
255
256 pub fn as_str(self) -> &'static str {
258 match self {
259 MessageType::Confirmable => "CON",
260 MessageType::NonConfirmable => "NON",
261 MessageType::Acknowledgement => "ACK",
262 MessageType::Reset => "RST",
263 }
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq)]
269pub struct CoapMessage {
270 pub mtype: MessageType,
271 pub code: u8,
272 pub message_id: u16,
273 pub token: Vec<u8>,
274 pub options: Vec<(u16, Vec<u8>)>,
276 pub payload: Vec<u8>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
281pub enum DecodeError {
282 TooShort {
283 len: usize,
284 },
285 BadVersion {
286 version: u8,
287 },
288 BadTokenLength {
289 tkl: u8,
290 },
291 Truncated {
292 what: &'static str,
293 },
294 ReservedOptionNibble,
296 EmptyPayloadAfterMarker,
298}
299
300impl fmt::Display for DecodeError {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 match self {
303 DecodeError::TooShort { len } => {
304 write!(
305 f,
306 "datagram of {len} bytes is shorter than the 4-byte CoAP header"
307 )
308 }
309 DecodeError::BadVersion { version } => {
310 write!(f, "CoAP version {version} is not supported (expected 1)")
311 }
312 DecodeError::BadTokenLength { tkl } => {
313 write!(f, "token length {tkl} is reserved (must be 0-8)")
314 }
315 DecodeError::Truncated { what } => write!(f, "message truncated inside {what}"),
316 DecodeError::ReservedOptionNibble => {
317 write!(f, "option delta/length nibble 15 is reserved")
318 }
319 DecodeError::EmptyPayloadAfterMarker => {
320 write!(f, "payload marker present but payload is empty")
321 }
322 }
323 }
324}
325
326impl std::error::Error for DecodeError {}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
334pub enum EncodeError {
335 TokenTooLong { len: usize },
337 OptionTooLong { number: u16, len: usize },
339}
340
341impl fmt::Display for EncodeError {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 match self {
344 EncodeError::TokenTooLong { len } => write!(
345 f,
346 "token is {len} octets; RFC 7252 §3 allows 0-{MAX_TOKEN_LEN}, and CoAP matches \
347 a response to its request by token equality, so a shortened one matches nothing"
348 ),
349 EncodeError::OptionTooLong { number, len } => write!(
350 f,
351 "option {number} carries {len} octets; the option length field tops out at \
352 {MAX_OPTION_LEN}"
353 ),
354 }
355 }
356}
357
358impl std::error::Error for EncodeError {}
359
360impl CoapMessage {
361 pub fn decode(buf: &[u8]) -> Result<CoapMessage, DecodeError> {
363 if buf.len() < HEADER_LEN {
364 return Err(DecodeError::TooShort { len: buf.len() });
365 }
366
367 let version = buf[0] >> 6;
368 if version != VERSION {
369 return Err(DecodeError::BadVersion { version });
370 }
371 let mtype = MessageType::from_bits(buf[0] >> 4);
372 let tkl = buf[0] & 0x0F;
373 if tkl > 8 {
374 return Err(DecodeError::BadTokenLength { tkl });
375 }
376 let code = buf[1];
377 let message_id = u16::from_be_bytes([buf[2], buf[3]]);
378
379 let mut pos = HEADER_LEN;
380 if buf.len() < pos + tkl as usize {
381 return Err(DecodeError::Truncated { what: "token" });
382 }
383 let token = buf[pos..pos + tkl as usize].to_vec();
384 pos += tkl as usize;
385
386 let mut options: Vec<(u16, Vec<u8>)> = Vec::new();
387 let mut current_number: u16 = 0;
388 let mut payload = Vec::new();
389
390 while pos < buf.len() {
391 if buf[pos] == PAYLOAD_MARKER {
392 pos += 1;
393 if pos >= buf.len() {
394 return Err(DecodeError::EmptyPayloadAfterMarker);
395 }
396 payload = buf[pos..].to_vec();
397 break;
398 }
399
400 let delta_nibble = buf[pos] >> 4;
401 let length_nibble = buf[pos] & 0x0F;
402 pos += 1;
403
404 let delta = read_extended(buf, &mut pos, delta_nibble)?;
405 let length = read_extended(buf, &mut pos, length_nibble)?;
406
407 if buf.len() < pos + length as usize {
408 return Err(DecodeError::Truncated {
409 what: "option value",
410 });
411 }
412 current_number = current_number.saturating_add(delta);
413 options.push((current_number, buf[pos..pos + length as usize].to_vec()));
414 pos += length as usize;
415 }
416
417 Ok(CoapMessage {
418 mtype,
419 code,
420 message_id,
421 token,
422 options,
423 payload,
424 })
425 }
426
427 pub fn encode(&self) -> Result<Vec<u8>, EncodeError> {
432 if self.token.len() > MAX_TOKEN_LEN {
433 return Err(EncodeError::TokenTooLong {
434 len: self.token.len(),
435 });
436 }
437 for (number, value) in &self.options {
438 if value.len() > MAX_OPTION_LEN {
439 return Err(EncodeError::OptionTooLong {
440 number: *number,
441 len: value.len(),
442 });
443 }
444 }
445
446 let mut out = Vec::with_capacity(HEADER_LEN + self.token.len() + self.payload.len() + 16);
447 let tkl = self.token.len() as u8;
448 out.push((VERSION << 6) | (self.mtype.to_bits() << 4) | tkl);
449 out.push(self.code);
450 out.extend_from_slice(&self.message_id.to_be_bytes());
451 out.extend_from_slice(&self.token);
452
453 let mut sorted = self.options.clone();
454 sorted.sort_by_key(|(n, _)| *n);
457
458 let mut last: u16 = 0;
459 for (number, value) in &sorted {
460 let delta = number.saturating_sub(last);
461 last = *number;
462
463 let (delta_nibble, delta_ext) = split_extended(delta);
465 let (length_nibble, length_ext) = split_extended(value.len() as u16);
466
467 out.push((delta_nibble << 4) | length_nibble);
468 out.extend_from_slice(&delta_ext);
469 out.extend_from_slice(&length_ext);
470 out.extend_from_slice(value);
471 }
472
473 if !self.payload.is_empty() {
474 out.push(PAYLOAD_MARKER);
475 out.extend_from_slice(&self.payload);
476 }
477
478 Ok(out)
479 }
480
481 pub fn option_values(&self, number: u16) -> Vec<&[u8]> {
483 self.options
484 .iter()
485 .filter(|(n, _)| *n == number)
486 .map(|(_, v)| v.as_slice())
487 .collect()
488 }
489
490 pub fn option_uint(&self, number: u16) -> Option<u32> {
492 self.option_values(number)
493 .first()
494 .map(|v| v.iter().take(4).fold(0u32, |acc, b| (acc << 8) | *b as u32))
495 }
496
497 pub fn uri_path(&self) -> String {
499 let segments: Vec<String> = self
500 .option_values(OPT_URI_PATH)
501 .iter()
502 .map(|v| String::from_utf8_lossy(v).to_string())
503 .collect();
504 if segments.is_empty() {
505 "/".to_string()
506 } else {
507 format!("/{}", segments.join("/"))
508 }
509 }
510
511 pub fn path_segments(&self) -> Vec<String> {
513 self.option_values(OPT_URI_PATH)
514 .iter()
515 .map(|v| String::from_utf8_lossy(v).to_string())
516 .collect()
517 }
518
519 pub fn uri_query(&self) -> Option<String> {
521 let parts: Vec<String> = self
522 .option_values(OPT_URI_QUERY)
523 .iter()
524 .map(|v| String::from_utf8_lossy(v).to_string())
525 .collect();
526 if parts.is_empty() {
527 None
528 } else {
529 Some(parts.join("&"))
530 }
531 }
532
533 pub fn is_empty_message(&self) -> bool {
535 self.code == CODE_EMPTY
536 }
537
538 pub fn is_request(&self) -> bool {
540 method_name(self.code).is_some()
541 }
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq)]
546pub struct BlockValue {
547 pub num: u32,
549 pub more: bool,
551 pub szx: u8,
553}
554
555pub const MAX_BLOCK_NUM: u32 = (1 << 20) - 1;
557
558pub const MAX_SZX: u8 = 6;
560
561impl BlockValue {
562 pub fn parse(value: &[u8]) -> Option<BlockValue> {
564 if value.len() > 3 {
565 return None;
566 }
567 let v = value.iter().fold(0u32, |acc, b| (acc << 8) | u32::from(*b));
568 let szx = (v & 0x07) as u8;
569 if szx > MAX_SZX {
570 return None;
571 }
572 Some(BlockValue {
573 num: v >> 4,
574 more: v & 0x08 != 0,
575 szx,
576 })
577 }
578
579 pub fn encode(self) -> Vec<u8> {
581 let v = ((self.num & MAX_BLOCK_NUM) << 4)
582 | if self.more { 0x08 } else { 0 }
583 | u32::from(self.szx.min(MAX_SZX));
584 let bytes = v.to_be_bytes();
585 let first = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len());
586 bytes[first..].to_vec()
587 }
588
589 pub fn size(self) -> usize {
591 szx_size(self.szx)
592 }
593
594 pub fn offset(self) -> usize {
596 self.num as usize * self.size()
597 }
598}
599
600pub const fn szx_size(szx: u8) -> usize {
602 let szx = if szx > MAX_SZX { MAX_SZX } else { szx };
603 1 << (szx as u32 + 4)
604}
605
606impl CoapMessage {
607 pub fn block1(&self) -> Option<Option<BlockValue>> {
609 self.option_values(OPT_BLOCK1)
610 .first()
611 .map(|v| BlockValue::parse(v))
612 }
613
614 pub fn block2(&self) -> Option<Option<BlockValue>> {
616 self.option_values(OPT_BLOCK2)
617 .first()
618 .map(|v| BlockValue::parse(v))
619 }
620
621 pub fn observe(&self) -> Option<u32> {
623 self.option_values(OPT_OBSERVE)
624 .first()
625 .filter(|v| v.len() <= 3)
626 .map(|v| v.iter().fold(0u32, |acc, b| (acc << 8) | u32::from(*b)))
627 }
628}
629
630#[derive(Debug, Clone, PartialEq, Eq)]
637pub struct MessagePrefix {
638 pub mtype: MessageType,
639 pub code: u8,
640 pub message_id: u16,
641 pub token: Vec<u8>,
642}
643
644pub fn message_prefix(buf: &[u8]) -> Option<MessagePrefix> {
650 if buf.len() < HEADER_LEN || buf[0] >> 6 != VERSION {
651 return None;
652 }
653 let tkl = (buf[0] & 0x0F) as usize;
654 if tkl > MAX_TOKEN_LEN || buf.len() < HEADER_LEN + tkl {
655 return None;
656 }
657 Some(MessagePrefix {
658 mtype: MessageType::from_bits(buf[0] >> 4),
659 code: buf[1],
660 message_id: u16::from_be_bytes([buf[2], buf[3]]),
661 token: buf[HEADER_LEN..HEADER_LEN + tkl].to_vec(),
662 })
663}
664
665pub fn response_to(request: &CoapMessage, fresh_message_id: u16, code: u8) -> CoapMessage {
673 response_shell(
674 request.mtype,
675 request.message_id,
676 request.token.clone(),
677 fresh_message_id,
678 code,
679 )
680}
681
682pub fn response_to_prefix(prefix: &MessagePrefix, fresh_message_id: u16, code: u8) -> CoapMessage {
685 response_shell(
686 prefix.mtype,
687 prefix.message_id,
688 prefix.token.clone(),
689 fresh_message_id,
690 code,
691 )
692}
693
694fn response_shell(
695 request_type: MessageType,
696 request_message_id: u16,
697 token: Vec<u8>,
698 fresh_message_id: u16,
699 code: u8,
700) -> CoapMessage {
701 let (mtype, message_id) = match request_type {
702 MessageType::Confirmable => (MessageType::Acknowledgement, request_message_id),
703 _ => (MessageType::NonConfirmable, fresh_message_id),
704 };
705 CoapMessage {
706 mtype,
707 code,
708 message_id,
709 token,
710 options: Vec::new(),
711 payload: Vec::new(),
712 }
713}
714
715pub fn reset_for(message_id: u16) -> CoapMessage {
717 CoapMessage {
718 mtype: MessageType::Reset,
719 code: CODE_EMPTY,
720 message_id,
721 token: Vec::new(),
722 options: Vec::new(),
723 payload: Vec::new(),
724 }
725}
726
727fn read_extended(buf: &[u8], pos: &mut usize, nibble: u8) -> Result<u16, DecodeError> {
733 match nibble {
734 0..=12 => Ok(nibble as u16),
735 13 => {
736 let b = *buf.get(*pos).ok_or(DecodeError::Truncated {
737 what: "option extension byte",
738 })?;
739 *pos += 1;
740 Ok(b as u16 + 13)
741 }
742 14 => {
743 if buf.len() < *pos + 2 {
744 return Err(DecodeError::Truncated {
745 what: "option extension bytes",
746 });
747 }
748 let v = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]);
749 *pos += 2;
750 Ok(v.saturating_add(269))
751 }
752 _ => Err(DecodeError::ReservedOptionNibble),
753 }
754}
755
756fn split_extended(value: u16) -> (u8, Vec<u8>) {
758 if value < 13 {
759 (value as u8, Vec::new())
760 } else if value < 269 {
761 (13, vec![(value - 13) as u8])
762 } else {
763 (14, (value - 269).to_be_bytes().to_vec())
764 }
765}