use std::fmt;
pub const HEADER_LEN: usize = 4;
pub const PAYLOAD_MARKER: u8 = 0xFF;
pub const MAX_MESSAGE_LEN: usize = 1152;
pub const MAX_PAYLOAD_LEN: usize = 1024;
pub const MAX_REPRESENTATION_LEN: usize = 64 * 1024;
pub const VERSION: u8 = 1;
pub const MAX_TOKEN_LEN: usize = 8;
pub const MAX_OPTION_LEN: usize = u16::MAX as usize;
pub const OPT_IF_MATCH: u16 = 1;
pub const OPT_URI_HOST: u16 = 3;
pub const OPT_ETAG: u16 = 4;
pub const OPT_IF_NONE_MATCH: u16 = 5;
pub const OPT_OBSERVE: u16 = 6;
pub const OPT_URI_PORT: u16 = 7;
pub const OPT_LOCATION_PATH: u16 = 8;
pub const OPT_URI_PATH: u16 = 11;
pub const OPT_CONTENT_FORMAT: u16 = 12;
pub const OPT_MAX_AGE: u16 = 14;
pub const OPT_URI_QUERY: u16 = 15;
pub const OPT_ACCEPT: u16 = 17;
pub const OPT_LOCATION_QUERY: u16 = 20;
pub const OPT_BLOCK2: u16 = 23;
pub const OPT_BLOCK1: u16 = 27;
pub const OPT_SIZE2: u16 = 28;
pub const OPT_SIZE1: u16 = 60;
pub fn uint_option(value: u32) -> Vec<u8> {
let bytes = value.to_be_bytes();
let first = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len());
bytes[first..].to_vec()
}
pub const CODE_EMPTY: u8 = 0x00;
pub const CODE_GET: u8 = 0x01;
pub const CODE_POST: u8 = 0x02;
pub const CODE_PUT: u8 = 0x03;
pub const CODE_DELETE: u8 = 0x04;
pub const fn code(class: u8, detail: u8) -> u8 {
(class << 5) | (detail & 0x1F)
}
pub 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 {
code >> 5
}
pub const fn code_detail(code: u8) -> u8 {
code & 0x1F
}
pub fn code_to_string(code: u8) -> String {
format!("{}.{:02}", code_class(code), code_detail(code))
}
pub fn parse_code_string(s: &str) -> Option<u8> {
let token = s.split_whitespace().next()?;
let (class, detail) = token.split_once('.')?;
let class: u8 = class.trim().parse().ok()?;
let detail: u8 = detail.trim().parse().ok()?;
if class > 7 || detail > 31 {
return None;
}
Some(code(class, detail))
}
pub fn method_name(code: u8) -> Option<&'static str> {
match code {
CODE_GET => Some("GET"),
CODE_POST => Some("POST"),
CODE_PUT => Some("PUT"),
CODE_DELETE => Some("DELETE"),
_ => None,
}
}
pub fn content_format_name(id: u16) -> Option<&'static str> {
match id {
0 => Some("text/plain"),
40 => Some("application/link-format"),
41 => Some("application/xml"),
42 => Some("application/octet-stream"),
47 => Some("application/exi"),
50 => Some("application/json"),
60 => Some("application/cbor"),
_ => None,
}
}
pub fn content_format_id(name: &str) -> Option<u16> {
let n = name.trim().to_ascii_lowercase();
let n = n.split(';').next().unwrap_or(&n).trim().to_string();
match n.as_str() {
"text/plain" | "text" | "plain" => Some(0),
"application/link-format" | "link-format" => Some(40),
"application/xml" | "xml" => Some(41),
"application/octet-stream" | "octet-stream" | "binary" => Some(42),
"application/exi" | "exi" => Some(47),
"application/json" | "json" => Some(50),
"application/cbor" | "cbor" => Some(60),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageType {
Confirmable,
NonConfirmable,
Acknowledgement,
Reset,
}
impl MessageType {
pub fn from_bits(bits: u8) -> MessageType {
match bits & 0x03 {
0 => MessageType::Confirmable,
1 => MessageType::NonConfirmable,
2 => MessageType::Acknowledgement,
_ => MessageType::Reset,
}
}
pub fn to_bits(self) -> u8 {
match self {
MessageType::Confirmable => 0,
MessageType::NonConfirmable => 1,
MessageType::Acknowledgement => 2,
MessageType::Reset => 3,
}
}
pub fn as_str(self) -> &'static str {
match self {
MessageType::Confirmable => "CON",
MessageType::NonConfirmable => "NON",
MessageType::Acknowledgement => "ACK",
MessageType::Reset => "RST",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CoapMessage {
pub mtype: MessageType,
pub code: u8,
pub message_id: u16,
pub token: Vec<u8>,
pub options: Vec<(u16, Vec<u8>)>,
pub payload: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeError {
TooShort {
len: usize,
},
BadVersion {
version: u8,
},
BadTokenLength {
tkl: u8,
},
Truncated {
what: &'static str,
},
ReservedOptionNibble,
EmptyPayloadAfterMarker,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecodeError::TooShort { len } => {
write!(
f,
"datagram of {len} bytes is shorter than the 4-byte CoAP header"
)
}
DecodeError::BadVersion { version } => {
write!(f, "CoAP version {version} is not supported (expected 1)")
}
DecodeError::BadTokenLength { tkl } => {
write!(f, "token length {tkl} is reserved (must be 0-8)")
}
DecodeError::Truncated { what } => write!(f, "message truncated inside {what}"),
DecodeError::ReservedOptionNibble => {
write!(f, "option delta/length nibble 15 is reserved")
}
DecodeError::EmptyPayloadAfterMarker => {
write!(f, "payload marker present but payload is empty")
}
}
}
}
impl std::error::Error for DecodeError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncodeError {
TokenTooLong { len: usize },
OptionTooLong { number: u16, len: usize },
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EncodeError::TokenTooLong { len } => write!(
f,
"token is {len} octets; RFC 7252 §3 allows 0-{MAX_TOKEN_LEN}, and CoAP matches \
a response to its request by token equality, so a shortened one matches nothing"
),
EncodeError::OptionTooLong { number, len } => write!(
f,
"option {number} carries {len} octets; the option length field tops out at \
{MAX_OPTION_LEN}"
),
}
}
}
impl std::error::Error for EncodeError {}
impl CoapMessage {
pub fn decode(buf: &[u8]) -> Result<CoapMessage, DecodeError> {
if buf.len() < HEADER_LEN {
return Err(DecodeError::TooShort { len: buf.len() });
}
let version = buf[0] >> 6;
if version != VERSION {
return Err(DecodeError::BadVersion { version });
}
let mtype = MessageType::from_bits(buf[0] >> 4);
let tkl = buf[0] & 0x0F;
if tkl > 8 {
return Err(DecodeError::BadTokenLength { tkl });
}
let code = buf[1];
let message_id = u16::from_be_bytes([buf[2], buf[3]]);
let mut pos = HEADER_LEN;
if buf.len() < pos + tkl as usize {
return Err(DecodeError::Truncated { what: "token" });
}
let token = buf[pos..pos + tkl as usize].to_vec();
pos += tkl as usize;
let mut options: Vec<(u16, Vec<u8>)> = Vec::new();
let mut current_number: u16 = 0;
let mut payload = Vec::new();
while pos < buf.len() {
if buf[pos] == PAYLOAD_MARKER {
pos += 1;
if pos >= buf.len() {
return Err(DecodeError::EmptyPayloadAfterMarker);
}
payload = buf[pos..].to_vec();
break;
}
let delta_nibble = buf[pos] >> 4;
let length_nibble = buf[pos] & 0x0F;
pos += 1;
let delta = read_extended(buf, &mut pos, delta_nibble)?;
let length = read_extended(buf, &mut pos, length_nibble)?;
if buf.len() < pos + length as usize {
return Err(DecodeError::Truncated {
what: "option value",
});
}
current_number = current_number.saturating_add(delta);
options.push((current_number, buf[pos..pos + length as usize].to_vec()));
pos += length as usize;
}
Ok(CoapMessage {
mtype,
code,
message_id,
token,
options,
payload,
})
}
pub fn encode(&self) -> Result<Vec<u8>, EncodeError> {
if self.token.len() > MAX_TOKEN_LEN {
return Err(EncodeError::TokenTooLong {
len: self.token.len(),
});
}
for (number, value) in &self.options {
if value.len() > MAX_OPTION_LEN {
return Err(EncodeError::OptionTooLong {
number: *number,
len: value.len(),
});
}
}
let mut out = Vec::with_capacity(HEADER_LEN + self.token.len() + self.payload.len() + 16);
let tkl = self.token.len() as u8;
out.push((VERSION << 6) | (self.mtype.to_bits() << 4) | tkl);
out.push(self.code);
out.extend_from_slice(&self.message_id.to_be_bytes());
out.extend_from_slice(&self.token);
let mut sorted = self.options.clone();
sorted.sort_by_key(|(n, _)| *n);
let mut last: u16 = 0;
for (number, value) in &sorted {
let delta = number.saturating_sub(last);
last = *number;
let (delta_nibble, delta_ext) = split_extended(delta);
let (length_nibble, length_ext) = split_extended(value.len() as u16);
out.push((delta_nibble << 4) | length_nibble);
out.extend_from_slice(&delta_ext);
out.extend_from_slice(&length_ext);
out.extend_from_slice(value);
}
if !self.payload.is_empty() {
out.push(PAYLOAD_MARKER);
out.extend_from_slice(&self.payload);
}
Ok(out)
}
pub fn option_values(&self, number: u16) -> Vec<&[u8]> {
self.options
.iter()
.filter(|(n, _)| *n == number)
.map(|(_, v)| v.as_slice())
.collect()
}
pub fn option_uint(&self, number: u16) -> Option<u32> {
self.option_values(number)
.first()
.map(|v| v.iter().take(4).fold(0u32, |acc, b| (acc << 8) | *b as u32))
}
pub fn uri_path(&self) -> String {
let segments: Vec<String> = self
.option_values(OPT_URI_PATH)
.iter()
.map(|v| String::from_utf8_lossy(v).to_string())
.collect();
if segments.is_empty() {
"/".to_string()
} else {
format!("/{}", segments.join("/"))
}
}
pub fn path_segments(&self) -> Vec<String> {
self.option_values(OPT_URI_PATH)
.iter()
.map(|v| String::from_utf8_lossy(v).to_string())
.collect()
}
pub fn uri_query(&self) -> Option<String> {
let parts: Vec<String> = self
.option_values(OPT_URI_QUERY)
.iter()
.map(|v| String::from_utf8_lossy(v).to_string())
.collect();
if parts.is_empty() {
None
} else {
Some(parts.join("&"))
}
}
pub fn is_empty_message(&self) -> bool {
self.code == CODE_EMPTY
}
pub fn is_request(&self) -> bool {
method_name(self.code).is_some()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockValue {
pub num: u32,
pub more: bool,
pub szx: u8,
}
pub const MAX_BLOCK_NUM: u32 = (1 << 20) - 1;
pub const MAX_SZX: u8 = 6;
impl BlockValue {
pub fn parse(value: &[u8]) -> Option<BlockValue> {
if value.len() > 3 {
return None;
}
let v = value.iter().fold(0u32, |acc, b| (acc << 8) | u32::from(*b));
let szx = (v & 0x07) as u8;
if szx > MAX_SZX {
return None;
}
Some(BlockValue {
num: v >> 4,
more: v & 0x08 != 0,
szx,
})
}
pub fn encode(self) -> Vec<u8> {
let v = ((self.num & MAX_BLOCK_NUM) << 4)
| if self.more { 0x08 } else { 0 }
| u32::from(self.szx.min(MAX_SZX));
let bytes = v.to_be_bytes();
let first = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len());
bytes[first..].to_vec()
}
pub fn size(self) -> usize {
szx_size(self.szx)
}
pub fn offset(self) -> usize {
self.num as usize * self.size()
}
}
pub const fn szx_size(szx: u8) -> usize {
let szx = if szx > MAX_SZX { MAX_SZX } else { szx };
1 << (szx as u32 + 4)
}
impl CoapMessage {
pub fn block1(&self) -> Option<Option<BlockValue>> {
self.option_values(OPT_BLOCK1)
.first()
.map(|v| BlockValue::parse(v))
}
pub fn block2(&self) -> Option<Option<BlockValue>> {
self.option_values(OPT_BLOCK2)
.first()
.map(|v| BlockValue::parse(v))
}
pub fn observe(&self) -> Option<u32> {
self.option_values(OPT_OBSERVE)
.first()
.filter(|v| v.len() <= 3)
.map(|v| v.iter().fold(0u32, |acc, b| (acc << 8) | u32::from(*b)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MessagePrefix {
pub mtype: MessageType,
pub code: u8,
pub message_id: u16,
pub token: Vec<u8>,
}
pub fn message_prefix(buf: &[u8]) -> Option<MessagePrefix> {
if buf.len() < HEADER_LEN || buf[0] >> 6 != VERSION {
return None;
}
let tkl = (buf[0] & 0x0F) as usize;
if tkl > MAX_TOKEN_LEN || buf.len() < HEADER_LEN + tkl {
return None;
}
Some(MessagePrefix {
mtype: MessageType::from_bits(buf[0] >> 4),
code: buf[1],
message_id: u16::from_be_bytes([buf[2], buf[3]]),
token: buf[HEADER_LEN..HEADER_LEN + tkl].to_vec(),
})
}
pub fn response_to(request: &CoapMessage, fresh_message_id: u16, code: u8) -> CoapMessage {
response_shell(
request.mtype,
request.message_id,
request.token.clone(),
fresh_message_id,
code,
)
}
pub fn response_to_prefix(prefix: &MessagePrefix, fresh_message_id: u16, code: u8) -> CoapMessage {
response_shell(
prefix.mtype,
prefix.message_id,
prefix.token.clone(),
fresh_message_id,
code,
)
}
fn response_shell(
request_type: MessageType,
request_message_id: u16,
token: Vec<u8>,
fresh_message_id: u16,
code: u8,
) -> CoapMessage {
let (mtype, message_id) = match request_type {
MessageType::Confirmable => (MessageType::Acknowledgement, request_message_id),
_ => (MessageType::NonConfirmable, fresh_message_id),
};
CoapMessage {
mtype,
code,
message_id,
token,
options: Vec::new(),
payload: Vec::new(),
}
}
pub fn reset_for(message_id: u16) -> CoapMessage {
CoapMessage {
mtype: MessageType::Reset,
code: CODE_EMPTY,
message_id,
token: Vec::new(),
options: Vec::new(),
payload: Vec::new(),
}
}
fn read_extended(buf: &[u8], pos: &mut usize, nibble: u8) -> Result<u16, DecodeError> {
match nibble {
0..=12 => Ok(nibble as u16),
13 => {
let b = *buf.get(*pos).ok_or(DecodeError::Truncated {
what: "option extension byte",
})?;
*pos += 1;
Ok(b as u16 + 13)
}
14 => {
if buf.len() < *pos + 2 {
return Err(DecodeError::Truncated {
what: "option extension bytes",
});
}
let v = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]);
*pos += 2;
Ok(v.saturating_add(269))
}
_ => Err(DecodeError::ReservedOptionNibble),
}
}
fn split_extended(value: u16) -> (u8, Vec<u8>) {
if value < 13 {
(value as u8, Vec::new())
} else if value < 269 {
(13, vec![(value - 13) as u8])
} else {
(14, (value - 269).to_be_bytes().to_vec())
}
}