From cbb2771b143f0bf404209b06f3980f99b2e8d35e Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Wed, 27 Nov 2024 17:56:29 +0100 Subject: [PATCH] fix(server): Don't send a copy of the PDU instead of applying the response to the original response PDU This will require application-side modifications to server-side request handlers. Namely, the resource handler must no longer call coap_send for the response PDU. This change was made to conform to libcoap's API contract and to enable CoAP Observe support. Additionally, a small workaround for proper CoAP Observe support was added that prevents duplicate addition of the Observe option in server-side resource handlers. This workaround should be removed once #21 has been tackled and the API for message handling was reworked. --- libcoap/src/lib.rs | 8 +- libcoap/src/message/mod.rs | 79 ++++++++++----- libcoap/src/message/response.rs | 21 ++-- libcoap/src/resource.rs | 89 ++++++++-------- libcoap/tests/common/dtls.rs | 37 ++++--- libcoap/tests/common/mod.rs | 46 +++++---- libcoap/tests/dtls_psk_client_server_test.rs | 12 +-- libcoap/tests/tcp_client_server_test.rs | 14 +-- libcoap/tests/udp_client_server_test.rs | 12 ++- libcoap/tests/udp_observe_test.rs | 101 +++++++++++++++++++ 10 files changed, 287 insertions(+), 132 deletions(-) create mode 100644 libcoap/tests/udp_observe_test.rs diff --git a/libcoap/src/lib.rs b/libcoap/src/lib.rs index 9dcda7ab..5f175260 100644 --- a/libcoap/src/lib.rs +++ b/libcoap/src/lib.rs @@ -20,7 +20,7 @@ //! - [x] DTLS //! - [x] DTLS using PSK //! - [x] DTLS using PKI/RPK -//! - [ ] TCP +//! - [x] TCP //! - [ ] TLS //! - [ ] OSCORE //! - [ ] WebSockets @@ -175,14 +175,14 @@ //! // //! // The provided CoapResponse is already filled with the correct token to be //! // interpreted as a response to the correct request by the client. -//! |completed: &mut (), session: &mut CoapServerSession, request: &CoapRequest, mut response: CoapResponse| { +//! |completed: &mut (), session: &mut CoapServerSession, request: &CoapRequest, response: &mut CoapResponse| { //! // Set content of the response message to "Hello World!" //! let data = Vec::::from("Hello World!".as_bytes()); //! response.set_data(Some(data)); //! // Set the response code to 2.00 "Content" //! response.set_code(CoapResponseCode::Content); -//! // Send the response message. -//! session.send(response).expect("Unable to send response"); +//! // Note that you must not explicitly send the response here, libcoap takes care of +//! // that //! }, //! )), //! ); diff --git a/libcoap/src/message/mod.rs b/libcoap/src/message/mod.rs index fd31b57c..de049c8a 100644 --- a/libcoap/src/message/mod.rs +++ b/libcoap/src/message/mod.rs @@ -15,35 +15,32 @@ //! process of creating requests and responses and setting the appropriate options ([CoapRequest] //! and [CoapResponse]). -use std::{ffi::c_void, mem::MaybeUninit, slice::Iter}; -use std::fmt::Write; - -use num_traits::FromPrimitive; +use std::{ffi::c_void, fmt::Write, mem::MaybeUninit, slice::Iter}; use libcoap_sys::{ - coap_add_data, coap_add_data_large_request, coap_add_optlist_pdu, coap_add_token, coap_delete_optlist, - coap_delete_pdu, coap_get_data, coap_insert_optlist, coap_new_optlist, coap_opt_length, coap_opt_t, coap_opt_value, - coap_option_iterator_init, coap_option_next, coap_option_num_t, coap_optlist_t, coap_pdu_get_code, - coap_pdu_get_mid, coap_pdu_get_token, coap_pdu_get_type, coap_pdu_init, coap_pdu_set_code, coap_pdu_set_type, - coap_pdu_t, coap_session_t, + coap_add_data, coap_add_data_large_request, coap_add_optlist_pdu, coap_add_token, coap_check_option, + coap_delete_optlist, coap_delete_pdu, coap_get_data, coap_insert_optlist, coap_new_optlist, coap_opt_iterator_t, + coap_opt_length, coap_opt_t, coap_opt_value, coap_option_iterator_init, coap_option_next, coap_option_num_t, + coap_optlist_t, coap_pdu_get_code, coap_pdu_get_mid, coap_pdu_get_token, coap_pdu_get_type, coap_pdu_init, + coap_pdu_set_code, coap_pdu_set_type, coap_pdu_t, coap_session_t, }; +use num_traits::FromPrimitive; pub use request::CoapRequest; pub use response::CoapResponse; use crate::{ + context::ensure_coap_started, error::{MessageConversionError, OptionValueError}, protocol::{ - Block, CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionNum, CoapOptionType, ContentFormat, ETag, - HopLimit, MaxAge, NoResponse, Observe, ProxyScheme, ProxyUri, Size, UriHost, UriPath, UriPort, UriQuery, + Block, CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionNum, CoapOptionType, ContentFormat, ETag, Echo, + HopLimit, MaxAge, NoResponse, Observe, Oscore, ProxyScheme, ProxyUri, RequestTag, Size, UriHost, UriPath, + UriPort, UriQuery, }, session::CoapSessionCommon, - types::CoapMessageId, -}; -use crate::context::ensure_coap_started; -use crate::protocol::{Echo, Oscore, RequestTag}; -use crate::types::{ - decode_var_len_u16, decode_var_len_u32, decode_var_len_u8, encode_var_len_u16, encode_var_len_u32, - encode_var_len_u8, + types::{ + decode_var_len_u16, decode_var_len_u32, decode_var_len_u8, encode_var_len_u16, encode_var_len_u32, + encode_var_len_u8, CoapMessageId, + }, }; pub mod request; @@ -424,12 +421,11 @@ impl CoapMessage { /// /// The caller is responsible for freeing the returned PDU, either by calling [coap_send()](libcoap_sys::coap_send()) or /// [coap_delete_pdu()]. - pub fn into_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>( + pub fn into_raw_pdu<'a, S: CoapSessionCommon<'a>+?Sized>( mut self, session: &S, ) -> Result<*mut coap_pdu_t, MessageConversionError> { let message = self.as_message_mut(); - // SAFETY: all values are valid, cannot cause UB. let pdu = unsafe { coap_pdu_init( @@ -466,7 +462,7 @@ impl CoapMessage { /// /// # Safety /// raw_pdu must point to a valid mutable instance of coap_pdu_t. - pub(crate) unsafe fn apply_to_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>( + pub(crate) unsafe fn apply_to_raw_pdu<'a, S: CoapSessionCommon<'a>+?Sized>( mut self, raw_pdu: *mut coap_pdu_t, session: &S, @@ -475,14 +471,49 @@ impl CoapMessage { coap_pdu_set_type(raw_pdu, self.type_.to_raw_pdu_type()); coap_pdu_set_code(raw_pdu, self.code.to_raw_pdu_code()); let message = self.as_message_mut(); - let token: &[u8] = message.token.as_ref().ok_or(MessageConversionError::MissingToken)?; - if coap_add_token(raw_pdu, token.len(), token.as_ptr()) == 0 { - return Err(MessageConversionError::Unknown); + let cur_token = coap_pdu_get_token(raw_pdu); + + let cur_token = (cur_token.length > 0).then(|| core::slice::from_raw_parts(cur_token.s, cur_token.length)); + // Return error if no token was supplied. + if cur_token.is_none() && message.token.is_none() { + return Err(MessageConversionError::MissingToken); } + // Update token if necessary. + if message.token.is_some() && cur_token != message.token.as_deref() { + let token = message.token.as_ref().unwrap(); + if coap_add_token(raw_pdu, token.len(), token.as_ptr()) == 0 { + return Err(MessageConversionError::Unknown); + } + } + let mut optlist = None; let option_iter = std::mem::take(&mut message.options).into_iter(); for option in option_iter { let optnum = option.number(); + // TODO this is a really ugly workaround, remove this as soon as we have builder-like + // interfaces for PDUs. + // Do not duplicate Observe option as reapplying to the same PDU would cause the option + // to be added twice otherwise. + if let CoapOption::Observe(option) = option { + // Check if Observe has already been set. + let mut scratch: MaybeUninit = MaybeUninit::uninit(); + let cur_option = unsafe { coap_check_option(raw_pdu, optnum, scratch.as_mut_ptr()) }; + if !cur_option.is_null() { + // Observe has already been set, get value and compare with the one set for this message. + let cur_value = unsafe { + core::slice::from_raw_parts(coap_opt_value(cur_option), coap_opt_length(cur_option) as usize) + }; + let cur_value = decode_var_len_u32(cur_value); + // If Observe option values differ, return an appropriate error. + if option != cur_value { + return Err(MessageConversionError::NonRepeatableOptionRepeated( + CoapOptionType::Observe, + )); + } + // Otherwise, skip adding the option value to the optlist to avoid a duplicate. + continue; + } + } let entry = option .into_optlist_entry() .map_err(|e| MessageConversionError::InvalidOptionValue(CoapOptionType::try_from(optnum).ok(), e))?; diff --git a/libcoap/src/message/response.rs b/libcoap/src/message/response.rs index c3a0cc9a..47996d3c 100644 --- a/libcoap/src/message/response.rs +++ b/libcoap/src/message/response.rs @@ -7,12 +7,14 @@ * See the README as well as the LICENSE file for more information. */ -use crate::error::{MessageConversionError, MessageTypeError, OptionValueError}; -use crate::message::{CoapMessage, CoapMessageCommon, CoapOption, construct_path_string, construct_query_string}; -use crate::protocol::{ - CoapMessageCode, CoapMessageType, CoapOptionType, CoapResponseCode, ContentFormat, Echo, ETag, MaxAge, Observe, +use crate::{ + error::{MessageConversionError, MessageTypeError, OptionValueError}, + message::{construct_path_string, construct_query_string, CoapMessage, CoapMessageCommon, CoapOption}, + protocol::{ + CoapMessageCode, CoapMessageType, CoapOptionType, CoapResponseCode, ContentFormat, ETag, Echo, MaxAge, Observe, + }, + types::CoapUri, }; -use crate::types::CoapUri; #[derive(Debug, Clone, Eq, PartialEq)] pub struct CoapResponse { @@ -171,7 +173,14 @@ impl CoapResponse { self.pdu.add_option(CoapOption::ETag(etag)); } if let Some(observe) = self.observe { - self.pdu.add_option(CoapOption::Observe(observe)); + // TODO this is quite an ugly workaround for the fact the server-side sessions alredy + // come with this option set and we add a duplicate here otherwise. + // The proper solution would be to rewrite the entire message handling API to + // no longer create intermediate copies (and use the underlying structs directly), + // which is currently planned in GitHub issue #21. + if !self.pdu.options.contains(&CoapOption::Observe(observe)) { + self.pdu.add_option(CoapOption::Observe(observe)); + } } self.pdu } diff --git a/libcoap/src/resource.rs b/libcoap/src/resource.rs index 6a752b9f..57726b07 100644 --- a/libcoap/src/resource.rs +++ b/libcoap/src/resource.rs @@ -11,31 +11,27 @@ use std::{ any::Any, - cell::Ref, - cell::RefMut, + cell::{Ref, RefMut}, fmt::{Debug, Formatter}, marker::PhantomData, }; use libc::c_int; - use libcoap_sys::{ - coap_delete_resource, coap_new_str_const, coap_pdu_t, coap_register_request_handler, COAP_RESOURCE_FLAGS_NOTIFY_CON, - COAP_RESOURCE_FLAGS_NOTIFY_NON, COAP_RESOURCE_FLAGS_RELEASE_URI, coap_resource_get_uri_path, coap_resource_get_userdata, - coap_resource_init, coap_resource_notify_observers, coap_resource_set_get_observable, coap_resource_set_mode, coap_resource_set_userdata, coap_resource_t, - coap_send_rst, coap_session_t, coap_string_t, + coap_delete_resource, coap_new_str_const, coap_pdu_t, coap_register_request_handler, coap_resource_get_uri_path, + coap_resource_get_userdata, coap_resource_init, coap_resource_notify_observers, coap_resource_set_get_observable, + coap_resource_set_mode, coap_resource_set_userdata, coap_resource_t, coap_send_rst, coap_session_t, coap_string_t, + COAP_RESOURCE_FLAGS_NOTIFY_CON, COAP_RESOURCE_FLAGS_NOTIFY_NON, COAP_RESOURCE_FLAGS_RELEASE_URI, }; -use crate::{error::MessageConversionError, message::CoapMessage, protocol::CoapRequestCode}; -use crate::context::ensure_coap_started; -use crate::mem::{CoapFfiRcCell, DropInnerExclusively}; -use crate::message::CoapMessageCommon; -use crate::message::request::CoapRequest; -use crate::message::response::CoapResponse; -use crate::protocol::CoapMessageCode; -use crate::protocol::CoapMessageType; -use crate::session::CoapServerSession; -use crate::session::CoapSessionCommon; +use crate::{ + context::ensure_coap_started, + error::MessageConversionError, + mem::{CoapFfiRcCell, DropInnerExclusively}, + message::{request::CoapRequest, response::CoapResponse, CoapMessage, CoapMessageCommon}, + protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode}, + session::CoapServerSession, +}; // Trait aliases are experimental //trait CoapMethodHandlerFn = FnMut(&D, &mut CoapSession, &CoapRequestMessage, &mut CoapResponseMessage); @@ -50,7 +46,7 @@ use crate::session::CoapSessionCommon; macro_rules! resource_handler { ($f:ident, $t:path) => {{ #[allow(clippy::unnecessary_mut_passed)] // We don't know whether the function needs a mutable reference or not. - unsafe extern "C" fn _coap_method_handler_wrapper( + unsafe extern "C" fn _coap_method_handler_wrapper( resource: *mut coap_resource_t, session: *mut coap_session_t, incoming_pdu: *const coap_pdu_t, @@ -59,8 +55,10 @@ macro_rules! resource_handler { ) { let handler_data = prepare_resource_handler_data::<$t>(resource, session, incoming_pdu, query, response_pdu); - if let Ok((mut resource, mut session, incoming_pdu, outgoing_pdu)) = handler_data { - ($f::)(&mut resource, &mut session, &incoming_pdu, outgoing_pdu) + if let Ok((mut resource, mut session, incoming_pdu, mut outgoing_pdu)) = handler_data { + ($f::)(&mut resource, &mut session, &incoming_pdu, &mut outgoing_pdu); + // TODO error handling + let _ = outgoing_pdu.into_message().apply_to_raw_pdu(response_pdu, &session); } } unsafe { CoapRequestHandler::<$t>::from_raw_handler(_coap_method_handler_wrapper::<$t>) } @@ -79,7 +77,7 @@ macro_rules! resource_handler { /// The provided pointers must all be valid and point to the appropriate data structures. #[inline] #[doc(hidden)] -pub unsafe fn prepare_resource_handler_data<'a, D: Any + ?Sized + Debug>( +pub unsafe fn prepare_resource_handler_data<'a, D: Any+?Sized+Debug>( raw_resource: *mut coap_resource_t, raw_session: *mut coap_session_t, raw_incoming_pdu: *const coap_pdu_t, @@ -101,7 +99,7 @@ pub unsafe fn prepare_resource_handler_data<'a, D: Any + ?Sized + Debug>( } /// Trait with functions relating to [CoapResource]s with an unknown data type. -pub trait UntypedCoapResource: Any + Debug { +pub trait UntypedCoapResource: Any+Debug { /// Returns the uri_path this resource responds to. fn uri_path(&self) -> &str; /// Provides a reference to this resource as an [Any] trait object. @@ -134,14 +132,14 @@ pub trait UntypedCoapResource: Any + Debug { } /// Representation of a CoapResource that can be requested from a server. -#[derive(Debug)] -pub struct CoapResource { +#[derive(Debug, Clone)] +pub struct CoapResource { inner: CoapFfiRcCell>, } /// Container for resource handlers for various CoAP methods. #[derive(Debug)] -struct CoapResourceHandlers { +struct CoapResourceHandlers { get: Option>, put: Option>, delete: Option>, @@ -151,7 +149,7 @@ struct CoapResourceHandlers { patch: Option>, } -impl Default for CoapResourceHandlers { +impl Default for CoapResourceHandlers { fn default() -> Self { CoapResourceHandlers { get: None, @@ -165,7 +163,7 @@ impl Default for CoapResourceHandlers { } } -impl CoapResourceHandlers { +impl CoapResourceHandlers { #[inline] fn handler(&self, code: CoapRequestCode) -> Option<&CoapRequestHandler> { match code { @@ -226,13 +224,13 @@ impl CoapResourceHandlers { /// Inner part of a [CoapResource], which is referenced inside the raw resource and might be /// referenced multiple times, e.g. outside and inside of a resource handler. #[derive(Debug)] -pub(crate) struct CoapResourceInner { +pub(crate) struct CoapResourceInner { raw_resource: *mut coap_resource_t, user_data: Box, handlers: CoapResourceHandlers, } -impl CoapResource { +impl CoapResource { /// Creates a new CoapResource for the given `uri_path`. /// /// Handlers that are associated with this resource have to be able to take a reference to the @@ -321,15 +319,13 @@ impl CoapResource { &self, session: &mut CoapServerSession, req_message: &CoapRequest, - mut rsp_message: CoapResponse, + mut rsp_message: &mut CoapResponse, ) { let mut inner = self.inner.borrow_mut(); let req_code = match req_message.code() { CoapMessageCode::Request(req_code) => req_code, _ => { rsp_message.set_type_(CoapMessageType::Rst); - // TODO some better error handling - session.send(rsp_message).expect("error while sending RST packet"); return; }, }; @@ -350,7 +346,7 @@ impl CoapResource { self, session, req_message, - rsp_message, + &mut rsp_message, ); // Put the handler function back into the resource, unless the handler was replaced. @@ -362,7 +358,7 @@ impl CoapResource { } } -impl UntypedCoapResource for CoapResource { +impl UntypedCoapResource for CoapResource { fn uri_path(&self) -> &str { unsafe { let raw_path = coap_resource_get_uri_path(self.inner.borrow().raw_resource); @@ -384,13 +380,13 @@ impl UntypedCoapResource for CoapResource { } #[doc(hidden)] -impl From>> for CoapResource { +impl From>> for CoapResource { fn from(raw_cell: CoapFfiRcCell>) -> Self { CoapResource { inner: raw_cell } } } -impl Drop for CoapResourceInner { +impl Drop for CoapResourceInner { fn drop(&mut self) { // SAFETY: We set the user data on creation of the inner resource, so it cannot be invalid. std::mem::drop(unsafe { @@ -434,7 +430,7 @@ impl Drop for CoapResourceInner { // We'll allow the complex type as trait aliases are experimental and we'll probably want to use // those instead of aliasing the entire type including wrappers. #[allow(clippy::type_complexity)] -pub struct CoapRequestHandler { +pub struct CoapRequestHandler { raw_handler: unsafe extern "C" fn( resource: *mut coap_resource_t, session: *mut coap_session_t, @@ -443,13 +439,13 @@ pub struct CoapRequestHandler { response_pdu: *mut coap_pdu_t, ), dynamic_handler_function: - Option, &mut CoapServerSession, &CoapRequest, CoapResponse)>>, + Option, &mut CoapServerSession, &CoapRequest, &mut CoapResponse)>>, __handler_data_type: PhantomData, } -impl CoapRequestHandler { +impl CoapRequestHandler { /// Creates a new CoapResourceHandler with the given function as the handler function to call. - pub fn new( + pub fn new( mut handler: F, ) -> CoapRequestHandler { CoapRequestHandler::new_resource_ref(move |resource, session, request, response| { @@ -464,7 +460,7 @@ impl CoapRequestHandler { /// `CoapResource`. This way, you can perform actions on the resource directly (e.g., notify /// observers). pub fn new_resource_ref< - F: 'static + FnMut(&CoapResource, &mut CoapServerSession, &CoapRequest, CoapResponse), + F: 'static+FnMut(&CoapResource, &mut CoapServerSession, &CoapRequest, &mut CoapResponse), >( handler: F, ) -> CoapRequestHandler { @@ -494,8 +490,9 @@ impl CoapRequestHandler { ), ) -> CoapRequestHandler { ensure_coap_started(); - let handler_fn: Option, &mut CoapServerSession, &CoapRequest, CoapResponse)>> = - None; + let handler_fn: Option< + Box, &mut CoapServerSession, &CoapRequest, &mut CoapResponse)>, + > = None; CoapRequestHandler { raw_handler, dynamic_handler_function: handler_fn, @@ -504,17 +501,17 @@ impl CoapRequestHandler { } } -impl Debug for CoapRequestHandler { +impl Debug for CoapRequestHandler { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("CoapRequestHandler").finish() } } -fn coap_resource_handler_dynamic_wrapper( +fn coap_resource_handler_dynamic_wrapper( resource: &CoapResource, session: &mut CoapServerSession, req_message: &CoapRequest, - rsp_message: CoapResponse, + rsp_message: &mut CoapResponse, ) { resource.call_dynamic_handler(session, req_message, rsp_message); } diff --git a/libcoap/tests/common/dtls.rs b/libcoap/tests/common/dtls.rs index 138ab58f..993c4e2e 100644 --- a/libcoap/tests/common/dtls.rs +++ b/libcoap/tests/common/dtls.rs @@ -1,31 +1,34 @@ -use crate::common; -use libcoap_rs::crypto::pki_rpk::{ - KeyDef, KeyType, NonCertVerifying, PkiRpkContext, PkiRpkContextBuilder, ServerPkiRpkCryptoContext, +use std::{path::PathBuf, time::Duration}; + +use libcoap_rs::{ + crypto::{ + pki_rpk::{KeyDef, KeyType, NonCertVerifying, PkiRpkContext, PkiRpkContextBuilder, ServerPkiRpkCryptoContext}, + ClientCryptoContext, + }, + message::CoapMessageCommon, + protocol::{CoapMessageCode, CoapResponseCode}, + session::{CoapClientSession, CoapSessionCommon}, + CoapContext, }; -use libcoap_rs::crypto::ClientCryptoContext; -use libcoap_rs::message::CoapMessageCommon; -use libcoap_rs::protocol::{CoapMessageCode, CoapResponseCode}; -use libcoap_rs::session::{CoapClientSession, CoapSessionCommon}; -use libcoap_rs::CoapContext; -use std::path::PathBuf; -use std::time::Duration; + +use crate::common; // Is used in some test cases, but not in others (causing a compiler warning) #[allow(unused)] pub fn dtls_client_server_request_common( - client_key: impl KeyDef + 'static, - server_key: impl KeyDef + 'static + Send, + client_key: impl KeyDef+'static, + server_key: impl KeyDef+'static+Send, client_ctx_setup: FC, server_ctx_setup: FS, ) where - FC: FnOnce(PkiRpkContextBuilder<'static, KTY, NonCertVerifying>) -> PkiRpkContext<'static, KTY> + 'static, - FS: FnOnce(PkiRpkContextBuilder<'static, KTY, NonCertVerifying>) -> PkiRpkContext<'static, KTY> + Send + 'static, + FC: FnOnce(PkiRpkContextBuilder<'static, KTY, NonCertVerifying>) -> PkiRpkContext<'static, KTY>+'static, + FS: FnOnce(PkiRpkContextBuilder<'static, KTY, NonCertVerifying>) -> PkiRpkContext<'static, KTY>+Send+'static, ServerPkiRpkCryptoContext<'static>: From>, ClientCryptoContext<'static>: From>, { let server_address = common::get_unused_server_addr(); let client_crypto_ctx = client_ctx_setup(PkiRpkContextBuilder::<'static, KTY, NonCertVerifying>::new(client_key)); - let server_handle = common::spawn_test_server(move |mut context: CoapContext| { + let server_handle = common::spawn_test_server(move |mut context: CoapContext, _request_complete| { let server_crypto_ctx = server_ctx_setup(PkiRpkContextBuilder::<'static, KTY, NonCertVerifying>::new(server_key)); context.set_pki_rpk_context(server_crypto_ctx).unwrap(); @@ -47,7 +50,9 @@ pub fn dtls_client_server_request_common( for response in session.poll_handle(&req_handle) { assert_eq!(response.code(), CoapMessageCode::Response(CoapResponseCode::Content)); assert_eq!(response.data().unwrap().as_ref(), "Hello World!".as_bytes()); - server_handle.join().expect("Test server crashed with failure."); + if let Err(e) = server_handle.join() { + std::panic::resume_unwind(e); + } return; } } diff --git a/libcoap/tests/common/mod.rs b/libcoap/tests/common/mod.rs index af109b34..70965abb 100644 --- a/libcoap/tests/common/mod.rs +++ b/libcoap/tests/common/mod.rs @@ -10,17 +10,22 @@ #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))] pub mod dtls; -use std::net::{SocketAddr, UdpSocket}; -use std::rc::Rc; -use std::sync::{Arc, Condvar, Mutex}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::thread::JoinHandle; -use std::time::Duration; +use std::{ + net::{SocketAddr, UdpSocket}, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Condvar, Mutex, + }, + thread::JoinHandle, + time::Duration, +}; -use libcoap_rs::{CoapContext, CoapRequestHandler, CoapResource}; -use libcoap_rs::message::{CoapMessageCommon, CoapRequest, CoapResponse}; -use libcoap_rs::protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode}; -use libcoap_rs::session::CoapSessionCommon; +use libcoap_rs::{ + message::{CoapMessageCommon, CoapRequest, CoapResponse}, + protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode}, + CoapContext, CoapRequestHandler, CoapResource, +}; use libcoap_sys::{coap_dtls_set_log_level, coap_log_t, coap_set_log_level}; pub(crate) fn get_unused_server_addr() -> SocketAddr { @@ -45,7 +50,9 @@ pub(crate) fn get_unused_server_addr() -> SocketAddr { /// As the context_configurator closure is responsible for binding to sockets, this can be used to /// spawn a test server and wait for it to be ready to accept requests before returning (avoiding /// test failure due to "Connection Refused" errors). -pub(crate) fn spawn_test_server) -> CoapContext<'static> + Send + 'static>( +pub(crate) fn spawn_test_server< + F: FnOnce(CoapContext<'static>, Rc) -> CoapContext<'static>+Send+'static, +>( context_configurator: F, ) -> JoinHandle<()> { let ready_condition = Arc::new((Mutex::new(false), Condvar::new())); @@ -53,8 +60,8 @@ pub(crate) fn spawn_test_server) -> CoapContext<' let server_handle = std::thread::spawn(move || { let (ready_var, ready_cond) = &*ready_condition2; - run_test_server(|context| { - let context = context_configurator(context); + run_test_server(|context, request_complete| { + let context = context_configurator(context, request_complete); let mut ready_var = ready_var.lock().expect("ready condition mutex is poisoned"); *ready_var = true; ready_cond.notify_all(); @@ -74,24 +81,25 @@ pub(crate) fn spawn_test_server) -> CoapContext<' } /// Configures and starts a test server in the current thread. -pub(crate) fn run_test_server) -> CoapContext<'static>>(context_configurator: F) { +pub(crate) fn run_test_server, Rc) -> CoapContext<'static>>( + context_configurator: F, +) { unsafe { libcoap_sys::coap_startup_with_feature_checks(); coap_dtls_set_log_level(coap_log_t::COAP_LOG_DEBUG); coap_set_log_level(coap_log_t::COAP_LOG_DEBUG); } let mut context = CoapContext::new().unwrap(); - context = context_configurator(context); let request_completed = Rc::new(AtomicBool::new(false)); + context = context_configurator(context, Rc::clone(&request_completed)); let resource = CoapResource::new("test1", request_completed.clone(), false); resource.set_method_handler( CoapRequestCode::Get, Some(CoapRequestHandler::new( - |completed: &mut Rc, sess, _req, mut rsp: CoapResponse| { + |completed: &mut Rc, sess, _req, rsp: &mut CoapResponse| { let data = Vec::::from("Hello World!".as_bytes()); rsp.set_data(Some(data)); rsp.set_code(CoapMessageCode::Response(CoapResponseCode::Content)); - sess.send(rsp).unwrap(); completed.store(true, Ordering::Relaxed); }, )), @@ -100,14 +108,14 @@ pub(crate) fn run_test_server) -> CoapContext<'st context.add_resource(resource); loop { assert!( - context.do_io(Some(Duration::from_secs(10))).unwrap() < Duration::from_secs(10), + context.do_io(Some(Duration::from_secs(1000))).unwrap() < Duration::from_secs(1000), "timeout while waiting for test client request" ); if request_completed.load(Ordering::Relaxed) { break; } } - context.shutdown(Some(Duration::from_secs(0))).unwrap(); + context.shutdown(Some(Duration::from_secs(5))).unwrap(); } pub(crate) fn gen_test_request() -> CoapRequest { diff --git a/libcoap/tests/dtls_psk_client_server_test.rs b/libcoap/tests/dtls_psk_client_server_test.rs index 757e9612..58009a38 100644 --- a/libcoap/tests/dtls_psk_client_server_test.rs +++ b/libcoap/tests/dtls_psk_client_server_test.rs @@ -10,13 +10,11 @@ #![cfg(feature = "dtls-psk")] use std::time::Duration; -use libcoap_rs::crypto::psk::PskKey; -use libcoap_rs::crypto::psk::{ClientPskContextBuilder, ServerPskContextBuilder}; -use libcoap_rs::session::CoapClientSession; use libcoap_rs::{ + crypto::psk::{ClientPskContextBuilder, PskKey, ServerPskContextBuilder}, message::CoapMessageCommon, protocol::{CoapMessageCode, CoapResponseCode}, - session::CoapSessionCommon, + session::{CoapClientSession, CoapSessionCommon}, CoapContext, }; @@ -28,7 +26,7 @@ pub fn dtls_psk_client_server_request() { let dummy_key = PskKey::new(Some("dtls_test_id"), "dtls_test_key___"); let client_psk_context = ClientPskContextBuilder::new(dummy_key.clone()).build(); - let server_handle = common::spawn_test_server(move |mut context| { + let server_handle = common::spawn_test_server(move |mut context, _req_complete| { let server_psk_context = ServerPskContextBuilder::new(dummy_key.clone()).build(); context.set_psk_context(server_psk_context).unwrap(); context.add_endpoint_dtls(server_address).unwrap(); @@ -45,7 +43,9 @@ pub fn dtls_psk_client_server_request() { for response in session.poll_handle(&req_handle) { assert_eq!(response.code(), CoapMessageCode::Response(CoapResponseCode::Content)); assert_eq!(response.data().unwrap().as_ref(), "Hello World!".as_bytes()); - server_handle.join().expect("Test server crashed with failure."); + if let Err(e) = server_handle.join() { + std::panic::resume_unwind(e); + } return; } } diff --git a/libcoap/tests/tcp_client_server_test.rs b/libcoap/tests/tcp_client_server_test.rs index e5a64258..9d388ec3 100644 --- a/libcoap/tests/tcp_client_server_test.rs +++ b/libcoap/tests/tcp_client_server_test.rs @@ -6,16 +6,16 @@ * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved. * See the README as well as the LICENSE file for more information. */ - #![cfg(feature = "tcp")] +#![cfg(feature = "tcp")] + +use std::time::Duration; -use libcoap_rs::session::CoapClientSession; use libcoap_rs::{ message::CoapMessageCommon, protocol::{CoapMessageCode, CoapResponseCode}, - session::CoapSessionCommon, + session::{CoapClientSession, CoapSessionCommon}, CoapContext, }; -use std::time::Duration; mod common; @@ -23,7 +23,7 @@ mod common; pub fn basic_client_server_request() { let server_address = common::get_unused_server_addr(); - let server_handle = common::spawn_test_server(move |mut context| { + let server_handle = common::spawn_test_server(move |mut context, _request_complete| { context.add_endpoint_tcp(server_address).unwrap(); context }); @@ -38,7 +38,9 @@ pub fn basic_client_server_request() { for response in session.poll_handle(&req_handle) { assert_eq!(response.code(), CoapMessageCode::Response(CoapResponseCode::Content)); assert_eq!(response.data().unwrap().as_ref(), "Hello World!".as_bytes()); - server_handle.join().unwrap(); + if let Err(e) = server_handle.join() { + std::panic::resume_unwind(e); + } return; } } diff --git a/libcoap/tests/udp_client_server_test.rs b/libcoap/tests/udp_client_server_test.rs index c3d9d27e..006a8bd6 100644 --- a/libcoap/tests/udp_client_server_test.rs +++ b/libcoap/tests/udp_client_server_test.rs @@ -7,14 +7,14 @@ * See the README as well as the LICENSE file for more information. */ -use libcoap_rs::session::CoapClientSession; +use std::time::Duration; + use libcoap_rs::{ message::CoapMessageCommon, protocol::{CoapMessageCode, CoapResponseCode}, - session::CoapSessionCommon, + session::{CoapClientSession, CoapSessionCommon}, CoapContext, }; -use std::time::Duration; mod common; @@ -22,7 +22,7 @@ mod common; pub fn basic_client_server_request() { let server_address = common::get_unused_server_addr(); - let server_handle = common::spawn_test_server(move |mut context| { + let server_handle = common::spawn_test_server(move |mut context, _request_complete| { context.add_endpoint_udp(server_address).unwrap(); context }); @@ -37,7 +37,9 @@ pub fn basic_client_server_request() { for response in session.poll_handle(&req_handle) { assert_eq!(response.code(), CoapMessageCode::Response(CoapResponseCode::Content)); assert_eq!(response.data().unwrap().as_ref(), "Hello World!".as_bytes()); - server_handle.join().unwrap(); + if let Err(e) = server_handle.join() { + std::panic::resume_unwind(e); + } return; } } diff --git a/libcoap/tests/udp_observe_test.rs b/libcoap/tests/udp_observe_test.rs new file mode 100644 index 00000000..241eed6a --- /dev/null +++ b/libcoap/tests/udp_observe_test.rs @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * dtls_client_server_test.rs - Tests for UDP clients+servers. + * This file is part of the libcoap-rs crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ +use std::{ + rc::Rc, + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; + +use libcoap_rs::{ + message::{CoapMessageCommon, CoapRequest}, + protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode}, + session::{CoapClientSession, CoapSessionCommon}, + types::CoapUri, + CoapContext, CoapRequestHandler, CoapResource, +}; + +mod common; + +#[test] +pub fn observe_client_server_request() { + let server_address = common::get_unused_server_addr(); + + let uri: CoapUri = "/observe-test".parse().expect("unable to parse request URI"); + + let server_handle = common::spawn_test_server(move |mut context: CoapContext, request_complete| { + context.add_endpoint_udp(server_address).unwrap(); + + let observe_resource = CoapResource::new("observe-test", (0u8, request_complete), true); + observe_resource.set_get_observable(true); + observe_resource.set_method_handler( + CoapRequestCode::Get, + Some(CoapRequestHandler::new( + |data: &mut (u8, Rc), session, request, response| { + response.set_code(CoapResponseCode::Content); + response.set_data(Some([data.0])); + if data.0 != 0u8 { + data.1.store(true, Ordering::Relaxed); + } + }, + )), + ); + observe_resource.set_method_handler( + CoapRequestCode::Post, + Some(CoapRequestHandler::new_resource_ref( + move |resource: &CoapResource<(u8, Rc)>, session, request, response| { + resource.user_data_mut().0 = request.data().expect("sent empty data")[0]; + response.set_code(CoapResponseCode::Changed); + resource.notify_observers(); + }, + )), + ); + context.add_resource(observe_resource); + context + }); + + let mut context = CoapContext::new().unwrap(); + let session = CoapClientSession::connect_udp(&mut context, server_address).unwrap(); + + let mut request = CoapRequest::new(CoapMessageType::Non, CoapRequestCode::Get, uri.clone()).unwrap(); + request.set_observe(Some(0)); + let mut update_request_handle = None; + + let req_handle = session.send_request(request).unwrap(); + let mut expected_request_response = 0u8; + loop { + assert!(context.do_io(Some(Duration::from_secs(10))).expect("error during IO") <= Duration::from_secs(10)); + if let Some(update_request_handle) = update_request_handle.as_ref() { + for response in session.poll_handle(&update_request_handle) { + assert_eq!(response.code(), CoapMessageCode::Response(CoapResponseCode::Changed)); + expected_request_response = 1u8; + } + } + for response in session.poll_handle(&req_handle) { + assert_eq!(response.code(), CoapMessageCode::Response(CoapResponseCode::Content)); + assert_eq!(response.data().unwrap().as_ref(), [expected_request_response]); + if expected_request_response != 0u8 { + if let Err(e) = server_handle.join() { + std::panic::resume_unwind(e); + } + return; + } + if update_request_handle.is_none() { + // trigger update of resource to test observe notification + let mut update_request = + CoapRequest::new(CoapMessageType::Non, CoapRequestCode::Post, uri.clone()).unwrap(); + update_request.set_data(Some([1u8])); + update_request_handle = Some( + session + .send_request(update_request) + .expect("unable to send update request"), + ); + } + } + } +}