1use std::{ffi::c_void, fmt::Write, mem::MaybeUninit, slice::Iter};
20
21use libcoap_sys::{
22 coap_add_data, coap_add_data_large_request, coap_add_optlist_pdu, coap_add_token, coap_delete_optlist,
23 coap_delete_pdu, coap_get_data, coap_insert_optlist, coap_new_optlist, coap_opt_length, coap_opt_t, coap_opt_value,
24 coap_option_iterator_init, coap_option_next, coap_option_num_t, coap_optlist_t, coap_pdu_get_code,
25 coap_pdu_get_mid, coap_pdu_get_token, coap_pdu_get_type, coap_pdu_init, coap_pdu_set_code, coap_pdu_set_type,
26 coap_pdu_t, coap_session_t,
27};
28use num_traits::FromPrimitive;
29pub use request::CoapRequest;
30pub use response::CoapResponse;
31
32use crate::{
33 context::ensure_coap_started,
34 error::{MessageConversionError, OptionValueError},
35 protocol::{
36 Block, CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionNum, CoapOptionType, ContentFormat, ETag, Echo,
37 HopLimit, MaxAge, NoResponse, Observe, Oscore, ProxyScheme, ProxyUri, RequestTag, Size, UriHost, UriPath,
38 UriPort, UriQuery,
39 },
40 session::CoapSessionCommon,
41 types::{
42 decode_var_len_u16, decode_var_len_u32, decode_var_len_u8, encode_var_len_u16, encode_var_len_u32,
43 encode_var_len_u8, CoapMessageId,
44 },
45};
46
47pub mod request;
48pub mod response;
49
50#[derive(Debug, Hash, Eq, PartialEq, Clone)]
55pub enum CoapOption {
56 IfMatch(CoapMatch),
57 IfNoneMatch,
58 UriHost(UriHost),
59 UriPort(UriPort),
60 UriPath(UriPath),
61 UriQuery(UriQuery),
62 LocationPath(UriPath),
63 LocationQuery(UriQuery),
64 ProxyUri(ProxyUri),
65 ProxyScheme(ProxyScheme),
66 ContentFormat(ContentFormat),
67 Accept(ContentFormat),
68 Size1(Size),
69 Size2(Size),
70 Block1(Block),
71 Block2(Block),
72 HopLimit(HopLimit),
73 NoResponse(NoResponse),
74 ETag(ETag),
75 MaxAge(MaxAge),
76 Observe(Observe),
77 Oscore(Oscore),
78 Echo(Echo),
79 RTag(RequestTag),
80 QBlock1(Block),
81 QBlock2(Block),
82 Other(CoapOptionNum, Box<[u8]>),
83}
84
85impl CoapOption {
86 pub(crate) unsafe fn from_raw_opt(
92 number: coap_option_num_t,
93 opt: *const coap_opt_t,
94 ) -> Result<CoapOption, OptionValueError> {
95 let value = Vec::from(std::slice::from_raw_parts(
96 coap_opt_value(opt),
97 coap_opt_length(opt) as usize,
98 ));
99 Self::from_type_value(number, value)
100 }
101
102 pub fn number(&self) -> CoapOptionNum {
104 match self {
105 CoapOption::IfMatch(_) => CoapOptionType::IfMatch as u16,
106 CoapOption::IfNoneMatch => CoapOptionType::IfNoneMatch as u16,
107 CoapOption::UriHost(_) => CoapOptionType::UriHost as u16,
108 CoapOption::UriPort(_) => CoapOptionType::UriPort as u16,
109 CoapOption::UriPath(_) => CoapOptionType::UriPath as u16,
110 CoapOption::UriQuery(_) => CoapOptionType::UriQuery as u16,
111 CoapOption::LocationPath(_) => CoapOptionType::LocationPath as u16,
112 CoapOption::LocationQuery(_) => CoapOptionType::LocationQuery as u16,
113 CoapOption::ProxyUri(_) => CoapOptionType::ProxyUri as u16,
114 CoapOption::ProxyScheme(_) => CoapOptionType::ProxyScheme as u16,
115 CoapOption::ContentFormat(_) => CoapOptionType::ContentFormat as u16,
116 CoapOption::Accept(_) => CoapOptionType::Accept as u16,
117 CoapOption::Size1(_) => CoapOptionType::Size1 as u16,
118 CoapOption::Size2(_) => CoapOptionType::Size2 as u16,
119 CoapOption::Block1(_) => CoapOptionType::Block1 as u16,
120 CoapOption::Block2(_) => CoapOptionType::Block2 as u16,
121 CoapOption::HopLimit(_) => CoapOptionType::HopLimit as u16,
122 CoapOption::NoResponse(_) => CoapOptionType::NoResponse as u16,
123 CoapOption::ETag(_) => CoapOptionType::ETag as u16,
124 CoapOption::MaxAge(_) => CoapOptionType::MaxAge as u16,
125 CoapOption::Observe(_) => CoapOptionType::Observe as u16,
126 CoapOption::Oscore(_) => CoapOptionType::Oscore as u16,
127 CoapOption::Echo(_) => CoapOptionType::Echo as u16,
128 CoapOption::RTag(_) => CoapOptionType::RTag as u16,
129 CoapOption::QBlock1(_) => CoapOptionType::QBlock1 as u16,
130 CoapOption::QBlock2(_) => CoapOptionType::QBlock2 as u16,
131 CoapOption::Other(num, _) => *num,
132 }
133 }
134
135 pub fn into_value_bytes(self) -> Result<Box<[u8]>, OptionValueError> {
137 let num = self.number();
138 let bytes = match self {
139 CoapOption::IfMatch(val) => match val {
140 CoapMatch::ETag(tag) => tag,
141 CoapMatch::Empty => Box::new([]),
142 },
143 CoapOption::IfNoneMatch => Box::new([]),
144 CoapOption::UriHost(value) => value.into_boxed_str().into_boxed_bytes(),
145 CoapOption::UriPort(value) => encode_var_len_u16(value),
146 CoapOption::UriPath(value) => value.into_boxed_str().into_boxed_bytes(),
147 CoapOption::UriQuery(value) => value.into_boxed_str().into_boxed_bytes(),
148 CoapOption::LocationPath(value) => value.into_boxed_str().into_boxed_bytes(),
149 CoapOption::LocationQuery(value) => value.into_boxed_str().into_boxed_bytes(),
150 CoapOption::ProxyUri(value) => value.into_boxed_str().into_boxed_bytes(),
151 CoapOption::ProxyScheme(value) => value.into_boxed_str().into_boxed_bytes(),
152 CoapOption::ContentFormat(value) => encode_var_len_u16(value),
153 CoapOption::Accept(value) => encode_var_len_u16(value),
154 CoapOption::Size1(value) => encode_var_len_u32(value),
155 CoapOption::Size2(value) => encode_var_len_u32(value),
156 CoapOption::Block1(value) => encode_var_len_u32(value),
157 CoapOption::Block2(value) => encode_var_len_u32(value),
158 CoapOption::HopLimit(value) => encode_var_len_u16(value),
159 CoapOption::NoResponse(value) => encode_var_len_u8(value),
160 CoapOption::ETag(value) => value,
161 CoapOption::MaxAge(value) => encode_var_len_u32(value),
162 CoapOption::Observe(value) => encode_var_len_u32(value),
163 CoapOption::Oscore(value) => value,
164 CoapOption::Echo(value) => value,
165 CoapOption::RTag(value) => value,
166 CoapOption::QBlock1(value) => encode_var_len_u32(value),
167 CoapOption::QBlock2(value) => encode_var_len_u32(value),
168 CoapOption::Other(_num, data) => data,
169 };
170 if let Some(opt_type) = <CoapOptionType as FromPrimitive>::from_u16(num) {
171 if bytes.len() < opt_type.min_len() {
172 return Err(OptionValueError::TooShort);
173 } else if bytes.len() > opt_type.max_len() {
174 return Err(OptionValueError::TooLong);
175 }
176 }
177 Ok(bytes)
178 }
179
180 pub(crate) fn into_optlist_entry(self) -> Result<*mut coap_optlist_t, OptionValueError> {
183 let num = self.number();
184 let value = self.into_value_bytes()?;
185 Ok(unsafe { coap_new_optlist(num, value.len(), value.as_ptr()) })
186 }
187
188 pub(crate) unsafe fn from_optlist_entry(optlist_entry: &coap_optlist_t) -> Result<Self, OptionValueError> {
199 let value = Vec::from(std::slice::from_raw_parts(optlist_entry.data, optlist_entry.length));
200 Self::from_type_value(optlist_entry.number, value)
201 }
202
203 fn from_type_value(type_: coap_option_num_t, value: Vec<u8>) -> Result<Self, OptionValueError> {
205 match CoapOptionType::try_from(type_) {
206 Ok(opt_type) => {
207 if opt_type.min_len() > value.len() {
208 return Err(OptionValueError::TooShort);
209 } else if opt_type.max_len() < value.len() {
210 return Err(OptionValueError::TooLong);
211 }
212 match opt_type {
213 CoapOptionType::IfMatch => Ok(CoapOption::IfMatch(if value.is_empty() {
214 CoapMatch::Empty
215 } else {
216 CoapMatch::ETag(value.into_boxed_slice())
217 })),
218 CoapOptionType::UriHost => Ok(CoapOption::UriHost(String::from_utf8(value)?)),
219 CoapOptionType::ETag => Ok(CoapOption::ETag(value.into_boxed_slice())),
220 CoapOptionType::IfNoneMatch => Ok(CoapOption::IfNoneMatch),
221 CoapOptionType::UriPort => Ok(CoapOption::UriPort(decode_var_len_u16(value.as_slice()))),
222 CoapOptionType::LocationPath => Ok(CoapOption::LocationPath(String::from_utf8(value)?)),
223 CoapOptionType::UriPath => Ok(CoapOption::UriPath(String::from_utf8(value)?)),
224 CoapOptionType::ContentFormat => {
225 Ok(CoapOption::ContentFormat(decode_var_len_u16(value.as_slice())))
226 },
227 CoapOptionType::MaxAge => Ok(CoapOption::MaxAge(decode_var_len_u32(value.as_slice()))),
228 CoapOptionType::UriQuery => Ok(CoapOption::UriQuery(String::from_utf8(value)?)),
229 CoapOptionType::Accept => Ok(CoapOption::Accept(decode_var_len_u16(value.as_slice()))),
230 CoapOptionType::LocationQuery => Ok(CoapOption::LocationQuery(String::from_utf8(value)?)),
231 CoapOptionType::ProxyUri => Ok(CoapOption::ProxyUri(String::from_utf8(value)?)),
232 CoapOptionType::ProxyScheme => Ok(CoapOption::ProxyScheme(String::from_utf8(value)?)),
233 CoapOptionType::Size1 => Ok(CoapOption::Size1(decode_var_len_u32(value.as_slice()))),
234 CoapOptionType::Size2 => Ok(CoapOption::Size2(decode_var_len_u32(value.as_slice()))),
235 CoapOptionType::Block1 => Ok(CoapOption::Block1(decode_var_len_u32(value.as_slice()))),
236 CoapOptionType::Block2 => Ok(CoapOption::Block2(decode_var_len_u32(value.as_slice()))),
237 CoapOptionType::HopLimit => Ok(CoapOption::HopLimit(decode_var_len_u16(value.as_slice()))),
238 CoapOptionType::NoResponse => {
239 Ok(CoapOption::NoResponse(decode_var_len_u8(value.as_slice()) as NoResponse))
240 },
241 CoapOptionType::Observe => Ok(CoapOption::Observe(decode_var_len_u32(value.as_slice()))),
242 CoapOptionType::Oscore => Ok(CoapOption::Oscore(value.into_boxed_slice())),
243 CoapOptionType::Echo => Ok(CoapOption::Echo(value.into_boxed_slice())),
244 CoapOptionType::RTag => Ok(CoapOption::RTag(value.into_boxed_slice())),
245 CoapOptionType::QBlock1 => Ok(CoapOption::QBlock1(decode_var_len_u32(value.as_slice()))),
246 CoapOptionType::QBlock2 => Ok(CoapOption::QBlock2(decode_var_len_u32(value.as_slice()))),
247 }
248 },
249 _ => Ok(CoapOption::Other(type_, value.into_boxed_slice())),
250 }
251 }
252}
253
254pub(crate) fn construct_path_string(path_components: Vec<String>) -> String {
256 path_components.into_iter().fold(String::new(), |mut a: String, v| {
257 write!(&mut a, "/{}", v).expect("unable to create path string");
260 a
261 })
262}
263
264pub(crate) fn construct_query_string(query_components: Vec<String>) -> String {
266 let mut iter = query_components.iter();
267 let mut out_str = String::from("?");
268 if let Some(q) = iter.next() {
269 out_str = out_str + q
270 }
271 for q in iter {
272 out_str += format!("&{}", q).as_ref();
273 }
274 out_str
275}
276
277pub trait CoapMessageCommon {
279 fn add_option(&mut self, option: CoapOption) {
281 self.as_message_mut().options.push(option);
282 }
283
284 fn clear_options(&mut self) {
286 self.as_message_mut().options.clear();
287 }
288
289 fn options_iter(&self) -> Iter<CoapOption> {
291 self.as_message().options.iter()
292 }
293
294 fn type_(&self) -> CoapMessageType {
296 self.as_message().type_
297 }
298
299 fn set_type_(&mut self, type_: CoapMessageType) {
301 self.as_message_mut().type_ = type_;
302 }
303
304 fn code(&self) -> CoapMessageCode {
308 self.as_message().code
309 }
310
311 fn set_code<C: Into<CoapMessageCode>>(&mut self, code: C) {
313 self.as_message_mut().code = code.into();
314 }
315
316 fn mid(&self) -> Option<CoapMessageId> {
318 self.as_message().mid
319 }
320
321 fn set_mid(&mut self, mid: Option<CoapMessageId>) {
323 self.as_message_mut().mid = mid;
324 }
325
326 fn data(&self) -> Option<&[u8]> {
328 self.as_message().data.as_ref().map(|v| v.as_ref())
329 }
330
331 fn set_data<D: Into<Box<[u8]>>>(&mut self, data: Option<D>) {
333 self.as_message_mut().data = data.map(Into::into);
334 }
335
336 fn token(&self) -> Option<&[u8]> {
338 self.as_message().token.as_ref().map(|v| v.as_ref())
339 }
340
341 fn set_token<D: Into<Box<[u8]>>>(&mut self, token: Option<D>) {
346 self.as_message_mut().token = token.map(Into::into);
347 }
348
349 fn as_message(&self) -> &CoapMessage;
351 fn as_message_mut(&mut self) -> &mut CoapMessage;
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Hash)]
357pub struct CoapMessage {
358 type_: CoapMessageType,
360 code: CoapMessageCode,
362 mid: Option<CoapMessageId>,
364 options: Vec<CoapOption>,
366 token: Option<Box<[u8]>>,
368 data: Option<Box<[u8]>>,
370}
371
372impl CoapMessage {
373 pub fn new(type_: CoapMessageType, code: CoapMessageCode) -> CoapMessage {
375 ensure_coap_started();
376 CoapMessage {
377 type_,
378 code,
379 mid: None,
380 options: Vec::new(),
381 token: None,
382 data: None,
383 }
384 }
385
386 pub unsafe fn from_raw_pdu(raw_pdu: *const coap_pdu_t) -> Result<CoapMessage, MessageConversionError> {
391 ensure_coap_started();
392 let mut option_iter = MaybeUninit::zeroed();
393 coap_option_iterator_init(raw_pdu, option_iter.as_mut_ptr(), std::ptr::null());
394 let mut option_iter = option_iter.assume_init();
395 let mut options = Vec::new();
396 while let Some(read_option) = coap_option_next(&mut option_iter).as_ref() {
397 options.push(CoapOption::from_raw_opt(option_iter.number, read_option).map_err(|e| {
398 MessageConversionError::InvalidOptionValue(CoapOptionType::try_from(option_iter.number).ok(), e)
399 })?);
400 }
401 let mut len: usize = 0;
402 let mut data = std::ptr::null();
403 coap_get_data(raw_pdu, &mut len, &mut data);
404 let data = match len {
405 0 => None,
406 len => Some(Vec::from(std::slice::from_raw_parts(data, len)).into_boxed_slice()),
407 };
408 let raw_token = coap_pdu_get_token(raw_pdu);
409 let token = Vec::from(std::slice::from_raw_parts(raw_token.s, raw_token.length));
410 Ok(CoapMessage {
411 type_: coap_pdu_get_type(raw_pdu).into(),
412 code: coap_pdu_get_code(raw_pdu).try_into().unwrap(),
413 mid: Some(coap_pdu_get_mid(raw_pdu)),
414 options,
415 token: Some(token.into_boxed_slice()),
416 data,
417 })
418 }
419
420 pub fn into_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>(
426 mut self,
427 session: &S,
428 ) -> Result<*mut coap_pdu_t, MessageConversionError> {
429 let message = self.as_message_mut();
430
431 let pdu = unsafe {
433 coap_pdu_init(
434 message.type_.to_raw_pdu_type(),
435 message.code.to_raw_pdu_code(),
436 message.mid.ok_or(MessageConversionError::MissingMessageId)?,
437 session.max_pdu_size(),
438 )
439 };
440 if pdu.is_null() {
441 return Err(MessageConversionError::Unknown);
442 }
443 unsafe {
445 let result = self.apply_to_raw_pdu(pdu, session);
446 if result.is_err() {
447 coap_delete_pdu(pdu);
448 }
449 result
450 }
451 }
452
453 pub(crate) unsafe fn apply_to_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>(
468 mut self,
469 raw_pdu: *mut coap_pdu_t,
470 session: &S,
471 ) -> Result<*mut coap_pdu_t, MessageConversionError> {
472 assert!(!raw_pdu.is_null(), "attempted to apply CoapMessage to null pointer");
473 coap_pdu_set_type(raw_pdu, self.type_.to_raw_pdu_type());
474 coap_pdu_set_code(raw_pdu, self.code.to_raw_pdu_code());
475 let message = self.as_message_mut();
476 let token: &[u8] = message.token.as_ref().ok_or(MessageConversionError::MissingToken)?;
477 if coap_add_token(raw_pdu, token.len(), token.as_ptr()) == 0 {
478 return Err(MessageConversionError::Unknown);
479 }
480 let mut optlist = None;
481 let option_iter = std::mem::take(&mut message.options).into_iter();
482 for option in option_iter {
483 let optnum = option.number();
484 let entry = option
485 .into_optlist_entry()
486 .map_err(|e| MessageConversionError::InvalidOptionValue(CoapOptionType::try_from(optnum).ok(), e))?;
487 if entry.is_null() {
488 if let Some(optlist) = optlist {
489 coap_delete_optlist(optlist);
490 return Err(MessageConversionError::Unknown);
491 }
492 }
493 match optlist {
494 None => {
495 optlist = Some(entry);
496 },
497 Some(mut optlist) => {
498 coap_insert_optlist(&mut optlist, entry);
499 },
500 }
501 }
502 if let Some(mut optlist) = optlist {
503 let optlist_add_success = coap_add_optlist_pdu(raw_pdu, &mut optlist);
504 coap_delete_optlist(optlist);
505 if optlist_add_success == 0 {
506 return Err(MessageConversionError::Unknown);
507 }
508 }
509 if let Some(data) = message.data.take() {
510 match message.code {
511 CoapMessageCode::Empty => return Err(MessageConversionError::DataInEmptyMessage),
512 CoapMessageCode::Request(_) => {
513 let len = data.len();
514 let box_ptr = Box::into_raw(data);
515 coap_add_data_large_request(
516 session.raw_session_mut(),
517 raw_pdu,
518 len,
519 box_ptr as *mut u8,
520 Some(large_data_cleanup_handler),
521 box_ptr as *mut c_void,
522 );
523 },
524 CoapMessageCode::Response(_) => {
525 let data: &[u8] = data.as_ref();
528 if coap_add_data(raw_pdu, data.len(), data.as_ptr()) == 0 {
529 return Err(MessageConversionError::Unknown);
530 }
531 },
532 }
533 }
534 Ok(raw_pdu)
535 }
536}
537
538impl CoapMessageCommon for CoapMessage {
539 fn as_message(&self) -> &CoapMessage {
540 self
541 }
542
543 fn as_message_mut(&mut self) -> &mut CoapMessage {
544 self
545 }
546}
547
548impl From<CoapRequest> for CoapMessage {
549 fn from(val: CoapRequest) -> Self {
550 val.into_message()
551 }
552}
553
554impl From<CoapResponse> for CoapMessage {
555 fn from(val: CoapResponse) -> Self {
556 val.into_message()
557 }
558}
559
560unsafe extern "C" fn large_data_cleanup_handler(_session: *mut coap_session_t, app_ptr: *mut c_void) {
562 std::mem::drop(Box::from_raw(app_ptr as *mut u8));
563}