1use std::{ffi::c_void, mem::MaybeUninit, slice::Iter};
19
20use num_traits::FromPrimitive;
21
22use libcoap_sys::{
23 coap_add_data, coap_add_data_large_request, coap_add_optlist_pdu, coap_add_token, coap_delete_optlist,
24 coap_delete_pdu, coap_get_data, coap_insert_optlist, coap_new_optlist, coap_opt_length, coap_opt_t, coap_opt_value,
25 coap_option_iterator_init, coap_option_next, coap_option_num_t, coap_optlist_t, coap_pdu_get_code,
26 coap_pdu_get_mid, coap_pdu_get_token, coap_pdu_get_type, coap_pdu_init, coap_pdu_set_code, coap_pdu_set_type,
27 coap_pdu_t, coap_session_t,
28};
29pub use request::CoapRequest;
30pub use response::CoapResponse;
31
32use crate::types::{decode_var_len_u16, decode_var_len_u32, encode_var_len_u16, encode_var_len_u32, encode_var_len_u8};
33use crate::{
34 error::{MessageConversionError, OptionValueError},
35 protocol::{
36 Block, CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionNum, CoapOptionType, ContentFormat, ETag,
37 HopLimit, MaxAge, NoResponse, Observe, ProxyScheme, ProxyUri, Size, UriHost, UriPath, UriPort, UriQuery,
38 },
39 session::CoapSessionCommon,
40 types::CoapMessageId,
41};
42
43pub mod request;
44pub mod response;
45
46#[derive(Debug, Hash, Eq, PartialEq, Clone)]
51pub enum CoapOption {
52 IfMatch(CoapMatch),
53 IfNoneMatch,
54 UriHost(UriHost),
55 UriPort(UriPort),
56 UriPath(UriPath),
57 UriQuery(UriQuery),
58 LocationPath(UriPath),
59 LocationQuery(UriQuery),
60 ProxyUri(ProxyUri),
61 ProxyScheme(ProxyScheme),
62 ContentFormat(ContentFormat),
63 Accept(ContentFormat),
64 Size1(Size),
65 Size2(Size),
66 Block1(Block),
67 Block2(Block),
68 HopLimit(HopLimit),
71 NoResponse(NoResponse),
72 ETag(ETag),
73 MaxAge(MaxAge),
74 Observe(Observe),
75 Other(CoapOptionNum, Box<[u8]>),
76}
77
78impl CoapOption {
79 pub(crate) unsafe fn from_raw_opt(
85 number: coap_option_num_t,
86 opt: *const coap_opt_t,
87 ) -> Result<CoapOption, OptionValueError> {
88 let value = Vec::from(std::slice::from_raw_parts(
89 coap_opt_value(opt),
90 coap_opt_length(opt) as usize,
91 ));
92 match CoapOptionType::try_from(number) {
93 Ok(opt_type) => {
94 if opt_type.min_len() > value.len() {
95 return Err(OptionValueError::TooShort);
96 } else if opt_type.max_len() < value.len() {
97 return Err(OptionValueError::TooLong);
98 }
99 match opt_type {
100 CoapOptionType::IfMatch => Ok(CoapOption::IfMatch(if value.is_empty() {
101 CoapMatch::Empty
102 } else {
103 CoapMatch::ETag(value.into_boxed_slice())
104 })),
105 CoapOptionType::UriHost => Ok(CoapOption::UriHost(String::from_utf8(value)?)),
106 CoapOptionType::ETag => Ok(CoapOption::ETag(value.into_boxed_slice())),
107 CoapOptionType::IfNoneMatch => Ok(CoapOption::IfNoneMatch),
108 CoapOptionType::UriPort => Ok(CoapOption::UriPort(decode_var_len_u16(value.as_slice()))),
109 CoapOptionType::LocationPath => Ok(CoapOption::LocationPath(String::from_utf8(value)?)),
110 CoapOptionType::UriPath => Ok(CoapOption::UriPath(String::from_utf8(value)?)),
111 CoapOptionType::ContentFormat => {
112 Ok(CoapOption::ContentFormat(decode_var_len_u16(value.as_slice())))
113 },
114 CoapOptionType::MaxAge => Ok(CoapOption::MaxAge(decode_var_len_u32(value.as_slice()))),
115 CoapOptionType::UriQuery => Ok(CoapOption::UriQuery(String::from_utf8(value)?)),
116 CoapOptionType::Accept => Ok(CoapOption::Accept(decode_var_len_u16(value.as_slice()))),
117 CoapOptionType::LocationQuery => Ok(CoapOption::LocationQuery(String::from_utf8(value)?)),
118 CoapOptionType::ProxyUri => Ok(CoapOption::ProxyUri(String::from_utf8(value)?)),
119 CoapOptionType::ProxyScheme => Ok(CoapOption::ProxyScheme(String::from_utf8(value)?)),
120 CoapOptionType::Size1 => Ok(CoapOption::Size1(decode_var_len_u32(value.as_slice()))),
121 CoapOptionType::Size2 => Ok(CoapOption::Size2(decode_var_len_u32(value.as_slice()))),
122 CoapOptionType::Block1 => Ok(CoapOption::Block1(decode_var_len_u32(value.as_slice()))),
123 CoapOptionType::Block2 => Ok(CoapOption::Block2(decode_var_len_u32(value.as_slice()))),
124 CoapOptionType::HopLimit => Ok(CoapOption::HopLimit(decode_var_len_u16(value.as_slice()))),
125 CoapOptionType::NoResponse => Ok(CoapOption::Size2(decode_var_len_u32(value.as_slice()))),
126 CoapOptionType::Observe => Ok(CoapOption::Observe(decode_var_len_u32(value.as_slice()))),
127 }
128 },
129 _ => Ok(CoapOption::Other(number, value.into_boxed_slice())),
130 }
131 }
132
133 pub fn number(&self) -> CoapOptionNum {
135 match self {
136 CoapOption::IfMatch(_) => CoapOptionType::IfMatch as u16,
137 CoapOption::IfNoneMatch => CoapOptionType::IfNoneMatch as u16,
138 CoapOption::UriHost(_) => CoapOptionType::UriHost as u16,
139 CoapOption::UriPort(_) => CoapOptionType::UriPort as u16,
140 CoapOption::UriPath(_) => CoapOptionType::UriPath as u16,
141 CoapOption::UriQuery(_) => CoapOptionType::UriQuery as u16,
142 CoapOption::LocationPath(_) => CoapOptionType::LocationPath as u16,
143 CoapOption::LocationQuery(_) => CoapOptionType::LocationQuery as u16,
144 CoapOption::ProxyUri(_) => CoapOptionType::ProxyUri as u16,
145 CoapOption::ProxyScheme(_) => CoapOptionType::ProxyScheme as u16,
146 CoapOption::ContentFormat(_) => CoapOptionType::ContentFormat as u16,
147 CoapOption::Accept(_) => CoapOptionType::Accept as u16,
148 CoapOption::Size1(_) => CoapOptionType::Size1 as u16,
149 CoapOption::Size2(_) => CoapOptionType::Size2 as u16,
150 CoapOption::Block1(_) => CoapOptionType::Block1 as u16,
151 CoapOption::Block2(_) => CoapOptionType::Block2 as u16,
152 CoapOption::HopLimit(_) => CoapOptionType::HopLimit as u16,
153 CoapOption::NoResponse(_) => CoapOptionType::NoResponse as u16,
154 CoapOption::ETag(_) => CoapOptionType::ETag as u16,
155 CoapOption::MaxAge(_) => CoapOptionType::MaxAge as u16,
156 CoapOption::Observe(_) => CoapOptionType::Observe as u16,
157 CoapOption::Other(num, _) => *num,
158 }
159 }
160
161 pub fn into_value_bytes(self) -> Result<Box<[u8]>, OptionValueError> {
163 let num = self.number();
164 let bytes = match self {
165 CoapOption::IfMatch(val) => match val {
166 CoapMatch::ETag(tag) => tag,
167 CoapMatch::Empty => Box::new([]),
168 },
169 CoapOption::IfNoneMatch => Box::new([]),
170 CoapOption::UriHost(value) => value.into_boxed_str().into_boxed_bytes(),
171 CoapOption::UriPort(value) => encode_var_len_u16(value),
172 CoapOption::UriPath(value) => value.into_boxed_str().into_boxed_bytes(),
173 CoapOption::UriQuery(value) => value.into_boxed_str().into_boxed_bytes(),
174 CoapOption::LocationPath(value) => value.into_boxed_str().into_boxed_bytes(),
175 CoapOption::LocationQuery(value) => value.into_boxed_str().into_boxed_bytes(),
176 CoapOption::ProxyUri(value) => value.into_boxed_str().into_boxed_bytes(),
177 CoapOption::ProxyScheme(value) => value.into_boxed_str().into_boxed_bytes(),
178 CoapOption::ContentFormat(value) => encode_var_len_u16(value),
179 CoapOption::Accept(value) => encode_var_len_u16(value),
180 CoapOption::Size1(value) => encode_var_len_u32(value),
181 CoapOption::Size2(value) => encode_var_len_u32(value),
182 CoapOption::Block1(value) => encode_var_len_u32(value),
183 CoapOption::Block2(value) => encode_var_len_u32(value),
184 CoapOption::HopLimit(value) => encode_var_len_u16(value),
185 CoapOption::NoResponse(value) => encode_var_len_u8(value),
186 CoapOption::ETag(value) => value,
187 CoapOption::MaxAge(value) => encode_var_len_u32(value),
188 CoapOption::Observe(value) => encode_var_len_u32(value),
189 CoapOption::Other(_num, data) => data,
190 };
191 if let Some(opt_type) = <CoapOptionType as FromPrimitive>::from_u16(num) {
192 if bytes.len() < opt_type.min_len() {
193 return Err(OptionValueError::TooShort);
194 } else if bytes.len() > opt_type.max_len() {
195 return Err(OptionValueError::TooLong);
196 }
197 }
198 Ok(bytes)
199 }
200
201 pub(crate) fn into_optlist_entry(self) -> Result<*mut coap_optlist_t, OptionValueError> {
204 let num = self.number();
205 let value = self.into_value_bytes()?;
206 Ok(unsafe { coap_new_optlist(num, value.len(), value.as_ptr()) })
207 }
208}
209
210pub trait CoapMessageCommon {
212 fn add_option(&mut self, option: CoapOption) {
214 self.as_message_mut().options.push(option);
215 }
216
217 fn clear_options(&mut self) {
219 self.as_message_mut().options.clear();
220 }
221
222 fn options_iter(&self) -> Iter<CoapOption> {
224 self.as_message().options.iter()
225 }
226
227 fn type_(&self) -> CoapMessageType {
229 self.as_message().type_
230 }
231
232 fn set_type_(&mut self, type_: CoapMessageType) {
234 self.as_message_mut().type_ = type_;
235 }
236
237 fn code(&self) -> CoapMessageCode {
241 self.as_message().code
242 }
243
244 fn set_code<C: Into<CoapMessageCode>>(&mut self, code: C) {
246 self.as_message_mut().code = code.into();
247 }
248
249 fn mid(&self) -> Option<CoapMessageId> {
251 self.as_message().mid
252 }
253
254 fn set_mid(&mut self, mid: Option<CoapMessageId>) {
256 self.as_message_mut().mid = mid;
257 }
258
259 fn data(&self) -> Option<&[u8]> {
261 self.as_message().data.as_ref().map(|v| v.as_ref())
262 }
263
264 fn set_data<D: Into<Box<[u8]>>>(&mut self, data: Option<D>) {
266 self.as_message_mut().data = data.map(Into::into);
267 }
268
269 fn token(&self) -> Option<&[u8]> {
271 self.as_message().token.as_ref().map(|v| v.as_ref())
272 }
273
274 fn set_token<D: Into<Box<[u8]>>>(&mut self, token: Option<D>) {
279 self.as_message_mut().token = token.map(Into::into);
280 }
281
282 fn as_message(&self) -> &CoapMessage;
284 fn as_message_mut(&mut self) -> &mut CoapMessage;
286}
287
288#[derive(Debug, Clone, PartialEq, Eq, Hash)]
290pub struct CoapMessage {
291 type_: CoapMessageType,
293 code: CoapMessageCode,
295 mid: Option<CoapMessageId>,
297 options: Vec<CoapOption>,
299 token: Option<Box<[u8]>>,
301 data: Option<Box<[u8]>>,
303}
304
305impl CoapMessage {
306 pub fn new(type_: CoapMessageType, code: CoapMessageCode) -> CoapMessage {
308 CoapMessage {
309 type_,
310 code,
311 mid: None,
312 options: Vec::new(),
313 token: None,
314 data: None,
315 }
316 }
317
318 pub unsafe fn from_raw_pdu(raw_pdu: *const coap_pdu_t) -> Result<CoapMessage, MessageConversionError> {
323 let mut option_iter = MaybeUninit::zeroed();
324 coap_option_iterator_init(raw_pdu, option_iter.as_mut_ptr(), std::ptr::null());
325 let mut option_iter = option_iter.assume_init();
326 let mut options = Vec::new();
327 while let Some(read_option) = coap_option_next(&mut option_iter).as_ref() {
328 options.push(CoapOption::from_raw_opt(option_iter.number, read_option).map_err(|e| {
329 MessageConversionError::InvalidOptionValue(CoapOptionType::try_from(option_iter.number).ok(), e)
330 })?);
331 }
332 let mut len: usize = 0;
333 let mut data = std::ptr::null();
334 coap_get_data(raw_pdu, &mut len, &mut data);
335 let data = match len {
336 0 => None,
337 len => Some(Vec::from(std::slice::from_raw_parts(data, len)).into_boxed_slice()),
338 };
339 let raw_token = coap_pdu_get_token(raw_pdu);
340 let token = Vec::from(std::slice::from_raw_parts(raw_token.s, raw_token.length));
341 Ok(CoapMessage {
342 type_: coap_pdu_get_type(raw_pdu).into(),
343 code: coap_pdu_get_code(raw_pdu).try_into().unwrap(),
344 mid: Some(coap_pdu_get_mid(raw_pdu)),
345 options,
346 token: Some(token.into_boxed_slice()),
347 data,
348 })
349 }
350
351 pub fn into_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>(
357 mut self,
358 session: &S,
359 ) -> Result<*mut coap_pdu_t, MessageConversionError> {
360 let message = self.as_message_mut();
361
362 let pdu = unsafe {
364 coap_pdu_init(
365 message.type_.to_raw_pdu_type(),
366 message.code.to_raw_pdu_code(),
367 message.mid.ok_or(MessageConversionError::MissingMessageId)?,
368 session.max_pdu_size(),
369 )
370 };
371 if pdu.is_null() {
372 return Err(MessageConversionError::Unknown);
373 }
374 unsafe {
376 let result = self.apply_to_raw_pdu(pdu, session);
377 if result.is_err() {
378 coap_delete_pdu(pdu);
379 }
380 result
381 }
382 }
383
384 pub(crate) unsafe fn apply_to_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>(
399 mut self,
400 raw_pdu: *mut coap_pdu_t,
401 session: &S,
402 ) -> Result<*mut coap_pdu_t, MessageConversionError> {
403 assert!(!raw_pdu.is_null(), "attempted to apply CoapMessage to null pointer");
404 coap_pdu_set_type(raw_pdu, self.type_.to_raw_pdu_type());
405 coap_pdu_set_code(raw_pdu, self.code.to_raw_pdu_code());
406 let message = self.as_message_mut();
407 let token: &[u8] = message.token.as_ref().ok_or(MessageConversionError::MissingToken)?;
408 if coap_add_token(raw_pdu, token.len(), token.as_ptr()) == 0 {
409 return Err(MessageConversionError::Unknown);
410 }
411 let mut optlist = None;
412 let option_iter = std::mem::take(&mut message.options).into_iter();
413 for option in option_iter {
414 let optnum = option.number();
415 let entry = option
416 .into_optlist_entry()
417 .map_err(|e| MessageConversionError::InvalidOptionValue(CoapOptionType::try_from(optnum).ok(), e))?;
418 if entry.is_null() {
419 if let Some(optlist) = optlist {
420 coap_delete_optlist(optlist);
421 return Err(MessageConversionError::Unknown);
422 }
423 }
424 match optlist {
425 None => {
426 optlist = Some(entry);
427 },
428 Some(mut optlist) => {
429 coap_insert_optlist(&mut optlist, entry);
430 },
431 }
432 }
433 if let Some(mut optlist) = optlist {
434 let optlist_add_success = coap_add_optlist_pdu(raw_pdu, &mut optlist);
435 coap_delete_optlist(optlist);
436 if optlist_add_success == 0 {
437 return Err(MessageConversionError::Unknown);
438 }
439 }
440 if let Some(data) = message.data.take() {
441 match message.code {
442 CoapMessageCode::Empty => return Err(MessageConversionError::DataInEmptyMessage),
443 CoapMessageCode::Request(_) => {
444 let len = data.len();
445 let box_ptr = Box::into_raw(data);
446 coap_add_data_large_request(
447 session.raw_session_mut(),
448 raw_pdu,
449 len,
450 box_ptr as *mut u8,
451 Some(large_data_cleanup_handler),
452 box_ptr as *mut c_void,
453 );
454 },
455 CoapMessageCode::Response(_) => {
456 let data: &[u8] = data.as_ref();
459 if coap_add_data(raw_pdu, data.len(), data.as_ptr()) == 0 {
460 return Err(MessageConversionError::Unknown);
461 }
462 },
463 }
464 }
465 Ok(raw_pdu)
466 }
467}
468
469impl CoapMessageCommon for CoapMessage {
470 fn as_message(&self) -> &CoapMessage {
471 self
472 }
473
474 fn as_message_mut(&mut self) -> &mut CoapMessage {
475 self
476 }
477}
478
479impl From<CoapRequest> for CoapMessage {
480 fn from(val: CoapRequest) -> Self {
481 val.into_message()
482 }
483}
484
485impl From<CoapResponse> for CoapMessage {
486 fn from(val: CoapResponse) -> Self {
487 val.into_message()
488 }
489}
490
491unsafe extern "C" fn large_data_cleanup_handler(_session: *mut coap_session_t, app_ptr: *mut c_void) {
493 std::mem::drop(Box::from_raw(app_ptr as *mut u8));
494}