libcoap_rs/
context.rs

1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * Copyright © The libcoap-rs Contributors, all rights reserved.
4 * This file is part of the libcoap-rs project, see the README file for
5 * general information on this project and the NOTICE.md and LICENSE files
6 * for information regarding copyright ownership and terms of use.
7 *
8 * context.rs - CoAP context related code.
9 */
10
11//! Module containing context-internal types and traits.
12
13use core::ffi::c_uint;
14#[cfg(feature = "dtls-pki")]
15use libcoap_sys::coap_context_set_pki_root_cas;
16use libcoap_sys::{
17    coap_add_resource, coap_can_exit, coap_context_get_csm_max_message_size, coap_context_get_csm_timeout,
18    coap_context_get_max_handshake_sessions, coap_context_get_max_idle_sessions, coap_context_get_session_timeout,
19    coap_context_set_block_mode, coap_context_set_csm_max_message_size, coap_context_set_csm_timeout,
20    coap_context_set_keepalive, coap_context_set_max_handshake_sessions, coap_context_set_max_idle_sessions,
21    coap_context_set_session_timeout, coap_context_t, coap_event_t, coap_event_t_COAP_EVENT_BAD_PACKET,
22    coap_event_t_COAP_EVENT_DTLS_CLOSED, coap_event_t_COAP_EVENT_DTLS_CONNECTED, coap_event_t_COAP_EVENT_DTLS_ERROR,
23    coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE, coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE,
24    coap_event_t_COAP_EVENT_MSG_RETRANSMITTED, coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR,
25    coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE, coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR,
26    coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED, coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD,
27    coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY, coap_event_t_COAP_EVENT_PARTIAL_BLOCK,
28    coap_event_t_COAP_EVENT_SERVER_SESSION_DEL, coap_event_t_COAP_EVENT_SERVER_SESSION_NEW,
29    coap_event_t_COAP_EVENT_SESSION_CLOSED, coap_event_t_COAP_EVENT_SESSION_CONNECTED,
30    coap_event_t_COAP_EVENT_SESSION_FAILED, coap_event_t_COAP_EVENT_TCP_CLOSED, coap_event_t_COAP_EVENT_TCP_CONNECTED,
31    coap_event_t_COAP_EVENT_TCP_FAILED, coap_event_t_COAP_EVENT_WS_CLOSED, coap_event_t_COAP_EVENT_WS_CONNECTED,
32    coap_event_t_COAP_EVENT_WS_PACKET_SIZE, coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL, coap_free_context,
33    coap_get_app_data, coap_io_process, coap_new_context, coap_proto_t, coap_proto_t_COAP_PROTO_DTLS,
34    coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP, coap_register_event_handler,
35    coap_register_response_handler, coap_set_app_data, coap_startup_with_feature_checks, COAP_BLOCK_SINGLE_BODY,
36    COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT,
37};
38use std::ffi::CStr;
39#[cfg(feature = "dtls-pki")]
40use std::ffi::CString;
41#[cfg(feature = "dtls")]
42use std::ptr::NonNull;
43use std::{any::Any, ffi::c_void, fmt::Debug, net::SocketAddr, ops::Sub, sync::Once, time::Duration};
44#[cfg(all(feature = "dtls-pki", unix))]
45use std::{os::unix::ffi::OsStrExt, path::Path};
46
47#[cfg(any(feature = "dtls-rpk", feature = "dtls-pki"))]
48use crate::crypto::pki_rpk::ServerPkiRpkCryptoContext;
49#[cfg(feature = "dtls-psk")]
50use crate::crypto::psk::ServerPskContext;
51use crate::{
52    error::{ContextConfigurationError, EndpointCreationError, IoProcessError, MulticastGroupJoinError},
53    event::{event_handler_callback, CoapEventHandler},
54    mem::{CoapLendableFfiRcCell, CoapLendableFfiWeakCell, DropInnerExclusively},
55    resource::{CoapResource, UntypedCoapResource},
56    session::{session_response_handler, CoapServerSession, CoapSession},
57    transport::CoapEndpoint,
58};
59
60//TODO: New feature?
61use libcoap_sys::coap_join_mcast_group_intf;
62
63static COAP_STARTUP_ONCE: Once = Once::new();
64
65#[inline(always)]
66pub(crate) fn ensure_coap_started() {
67    COAP_STARTUP_ONCE.call_once(coap_startup_with_feature_checks);
68}
69
70#[derive(Debug)]
71struct CoapContextInner<'a> {
72    /// Reference to the raw context this context wraps around.
73    raw_context: *mut coap_context_t,
74    /// A list of endpoints that this context is currently associated with.
75    endpoints: Vec<CoapEndpoint>,
76    /// A list of resources associated with this context.
77    resources: Vec<Box<dyn UntypedCoapResource>>,
78    /// A list of server-side sessions that are currently active.
79    server_sessions: Vec<CoapServerSession<'a>>,
80    /// The event handler responsible for library-user side handling of events.
81    event_handler: Option<Box<dyn CoapEventHandler>>,
82    /// PSK context for encrypted server-side sessions.
83    #[cfg(feature = "dtls-psk")]
84    psk_context: Option<ServerPskContext<'a>>,
85    /// PKI context for encrypted server-side sessions.
86    #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
87    pki_rpk_context: Option<ServerPkiRpkCryptoContext<'a>>,
88}
89
90/// A CoAP Context — container for general state and configuration information relating to CoAP
91///
92/// The equivalent to the [coap_context_t] type in libcoap.
93#[derive(Debug)]
94pub struct CoapContext<'a> {
95    inner: CoapLendableFfiRcCell<CoapContextInner<'a>>,
96}
97
98impl<'a> CoapContext<'a> {
99    /// Creates a new context.
100    ///
101    /// # Errors
102    /// Returns an error if the underlying libcoap library was unable to create a new context
103    /// (probably an allocation error?).
104    pub fn new() -> Result<CoapContext<'a>, ContextConfigurationError> {
105        ensure_coap_started();
106        // SAFETY: Providing null here is fine, the context will just not be bound to an endpoint
107        // yet.
108        let raw_context = unsafe { coap_new_context(std::ptr::null()) };
109        if raw_context.is_null() {
110            return Err(ContextConfigurationError::Unknown);
111        }
112        // SAFETY: We checked that raw_context is not null.
113        unsafe {
114            coap_context_set_block_mode(
115                raw_context,
116                // In some versions of libcoap, bindgen infers COAP_BLOCK_USE_LIBCOAP and
117                // COAP_BLOCK_SINGLE_BODY to be u32, while the function parameter is u8.
118                // Therefore, we use `try_into()` to convert to the right type, and panic if this is
119                // not possible (should never happen)
120                (COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY)
121                    .try_into()
122                    .expect("coap_context_set_block_mode() flags have invalid type for function"),
123            );
124            coap_register_response_handler(raw_context, Some(session_response_handler));
125        }
126        let inner = CoapLendableFfiRcCell::new(CoapContextInner {
127            raw_context,
128            endpoints: Vec::new(),
129            resources: Vec::new(),
130            server_sessions: Vec::new(),
131            event_handler: None,
132            #[cfg(feature = "dtls-psk")]
133            psk_context: None,
134            #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
135            pki_rpk_context: None,
136        });
137
138        // SAFETY: We checked that the raw context is not null, the provided function is valid and
139        // the app data pointer provided must be valid as we just created it using
140        // `create_raw_weak_box()`.
141        unsafe {
142            coap_set_app_data(raw_context, inner.create_raw_weak_box() as *mut c_void);
143            coap_register_event_handler(raw_context, Some(event_handler_callback));
144        }
145
146        Ok(CoapContext { inner })
147    }
148
149    /// Restores a CoapContext from its raw counterpart.
150    ///
151    /// # Safety
152    /// Provided pointer must point to as valid instance of a raw context whose application data
153    /// points to a `*mut CoapLendableFfiWeakCell<CoapContextInner>`.
154    pub(crate) unsafe fn from_raw(raw_context: *mut coap_context_t) -> CoapContext<'a> {
155        assert!(!raw_context.is_null());
156        let inner = CoapLendableFfiRcCell::clone_raw_weak_box(
157            coap_get_app_data(raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>
158        );
159
160        CoapContext { inner }
161    }
162
163    /// Handle an incoming event provided by libcoap.
164    pub(crate) fn handle_event(&self, mut session: CoapSession<'a>, event: coap_event_t) {
165        let inner_ref = &mut *self.inner.borrow_mut();
166        // Call event handler for event.
167        if let Some(handler) = &mut inner_ref.event_handler {
168            // Variant names are named by bindgen, we have no influence on this.
169            // Ref: https://github.com/rust-lang/rust/issues/39371
170            #[allow(non_upper_case_globals)]
171            match event {
172                coap_event_t_COAP_EVENT_DTLS_CLOSED => handler.handle_dtls_closed(&mut session),
173                coap_event_t_COAP_EVENT_DTLS_CONNECTED => handler.handle_dtls_connected(&mut session),
174                coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE => handler.handle_dtls_renegotiate(&mut session),
175                coap_event_t_COAP_EVENT_DTLS_ERROR => handler.handle_dtls_error(&mut session),
176                coap_event_t_COAP_EVENT_TCP_CONNECTED => handler.handle_tcp_connected(&mut session),
177                coap_event_t_COAP_EVENT_TCP_CLOSED => handler.handle_tcp_closed(&mut session),
178                coap_event_t_COAP_EVENT_TCP_FAILED => handler.handle_tcp_failed(&mut session),
179                coap_event_t_COAP_EVENT_SESSION_CONNECTED => handler.handle_session_connected(&mut session),
180                coap_event_t_COAP_EVENT_SESSION_CLOSED => handler.handle_session_closed(&mut session),
181                coap_event_t_COAP_EVENT_SESSION_FAILED => handler.handle_session_failed(&mut session),
182                coap_event_t_COAP_EVENT_PARTIAL_BLOCK => handler.handle_partial_block(&mut session),
183                coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => {
184                    if let CoapSession::Server(server_session) = &mut session {
185                        handler.handle_server_session_new(server_session)
186                    } else {
187                        panic!("server-side session event fired for non-server-side session");
188                    }
189                },
190                coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => {
191                    if let CoapSession::Server(server_session) = &mut session {
192                        handler.handle_server_session_del(server_session)
193                    } else {
194                        panic!("server-side session event fired for non-server-side session");
195                    }
196                },
197                coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL => handler.handle_xmit_block_fail(&mut session),
198                coap_event_t_COAP_EVENT_BAD_PACKET => handler.handle_bad_packet(&mut session),
199                coap_event_t_COAP_EVENT_MSG_RETRANSMITTED => handler.handle_msg_retransmitted(&mut session),
200                coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE => {
201                    handler.handle_oscore_decryption_failure(&mut session)
202                },
203                coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED => handler.handle_oscore_not_enabled(&mut session),
204                coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD => {
205                    handler.handle_oscore_no_protected_payload(&mut session)
206                },
207                coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY => handler.handle_oscore_no_security(&mut session),
208                coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR => handler.handle_oscore_internal_error(&mut session),
209                coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR => handler.handle_oscore_decode_error(&mut session),
210                coap_event_t_COAP_EVENT_WS_PACKET_SIZE => handler.handle_ws_packet_size(&mut session),
211                coap_event_t_COAP_EVENT_WS_CONNECTED => handler.handle_ws_connected(&mut session),
212                coap_event_t_COAP_EVENT_WS_CLOSED => handler.handle_ws_closed(&mut session),
213                coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE => handler.handle_keepalive_failure(&mut session),
214                _ => {
215                    // TODO probably a log message is justified here.
216                },
217            }
218        }
219        // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed.
220        if let CoapSession::Server(serv_sess) = session {
221            // Variant names are named by bindgen, we have no influence on this.
222            // Ref: https://github.com/rust-lang/rust/issues/39371
223            #[allow(non_upper_case_globals)]
224            match event {
225                coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => inner_ref.server_sessions.push(serv_sess),
226                coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => {
227                    std::mem::drop(inner_ref.server_sessions.remove(
228                        inner_ref.server_sessions.iter().position(|v| v.eq(&serv_sess)).expect(
229                            "attempted to remove session wrapper from context that was never associated with it",
230                        ),
231                    ));
232                    serv_sess.drop_exclusively();
233                },
234                _ => {},
235            }
236        }
237    }
238
239    /// Sets the server-side cryptography information provider.
240    ///
241    /// # Errors
242    ///
243    /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
244    /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
245    /// has already been set previously.
246    #[cfg(feature = "dtls-psk")]
247    pub fn set_psk_context(&mut self, psk_context: ServerPskContext<'a>) -> Result<(), ContextConfigurationError> {
248        let mut inner = self.inner.borrow_mut();
249        if inner.psk_context.is_some() {
250            return Err(ContextConfigurationError::CryptoContextAlreadySet);
251        }
252        inner.psk_context = Some(psk_context);
253        // SAFETY: raw context is valid, we ensure that an already set encryption context will not
254        // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
255        // dropped (ensuring the encryption context outlives the CoAP context).
256        unsafe {
257            inner
258                .psk_context
259                .as_ref()
260                .unwrap()
261                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
262        }
263    }
264
265    /// Sets the server-side cryptography information provider.
266    ///
267    /// # Errors
268    ///
269    /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
270    /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
271    /// has already been set previously.
272    #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
273    pub fn set_pki_rpk_context(
274        &mut self,
275        pki_context: impl Into<ServerPkiRpkCryptoContext<'a>>,
276    ) -> Result<(), ContextConfigurationError> {
277        let mut inner = self.inner.borrow_mut();
278        if inner.pki_rpk_context.is_some() {
279            return Err(ContextConfigurationError::CryptoContextAlreadySet);
280        }
281        inner.pki_rpk_context = Some(pki_context.into());
282        // SAFETY: raw context is valid, we ensure that an already set encryption context will not
283        // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
284        // dropped (ensuring the encryption context outlives the CoAP context).
285        unsafe {
286            inner
287                .pki_rpk_context
288                .as_ref()
289                .unwrap()
290                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
291        }
292    }
293
294    /// Convenience wrapper around [`set_pki_root_cas`](CoapContext::set_pki_root_cas) that can be
295    /// provided with any type that implements `AsRef<Path>`.
296    ///
297    /// `ca_file` should be the full path of a PEM-encoded file containing all root CAs to be used
298    /// or `None`, `ca_dir` should be a directory path containing PEM-encoded CA certificates to
299    /// be used or `None`.
300    ///
301    /// As not all implementations of [`OsString`] (which is the basis of [`Path`]) provide
302    /// conversions to non-zero byte sequences, this function is only available on Unix.
303    /// On other operating systems, perform manual conversion of paths into [`CString`] and call
304    /// [`set_pki_root_cas`](CoapContext::set_pki_root_cas) directly instead.
305    ///
306    /// See the Rust standard library documentation on [FFI conversions](https://doc.rust-lang.org/std/ffi/index.html#conversions])
307    /// and the [`OsString` type](https://doc.rust-lang.org/std/ffi/struct.OsString.html) for more
308    /// information.
309    ///
310    /// # Errors
311    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
312    /// function fails (indicating an error in either libcoap or the underlying TLS library).
313    #[cfg(all(feature = "dtls-pki", unix))]
314    pub fn set_pki_root_ca_paths(
315        &mut self,
316        ca_file: Option<impl AsRef<Path>>,
317        ca_dir: Option<impl AsRef<Path>>,
318    ) -> Result<(), ContextConfigurationError> {
319        let ca_file = ca_file.as_ref().map(|v| {
320            let v = v.as_ref();
321            assert!(v.is_file(), "attempted to set non-file as CA file for libcoap");
322            // Unix paths never contain null bytes, so we can unwrap here.
323            CString::new(v.as_os_str().as_bytes()).unwrap()
324        });
325        let ca_dir = ca_dir.as_ref().map(|v| {
326            let v = v.as_ref();
327            assert!(v.is_dir(), "attempted to set non-directory as CA directory for libcoap");
328            // Unix paths never contain null bytes, so we can unwrap here.
329            CString::new(v.as_os_str().as_bytes()).unwrap()
330        });
331
332        self.set_pki_root_cas(ca_file, ca_dir)
333    }
334
335    /// Sets the path to a CA certificate file and/or a directory of CA certificate files that
336    /// should be used as this context's default root CA information.
337    ///
338    /// `ca_file` should be the full path of a PEM-encoded file containing all root CAs to be used
339    /// or `None`, `ca_dir` should be a directory path containing PEM-encoded CA certificates to
340    /// be used or `None`.
341    ///
342    /// # Errors
343    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
344    /// function fails (indicating an error in either libcoap or the underlying TLS library).
345    #[cfg(feature = "dtls-pki")]
346    pub fn set_pki_root_cas(
347        &mut self,
348        ca_file: Option<CString>,
349        ca_dir: Option<CString>,
350    ) -> Result<(), ContextConfigurationError> {
351        let inner = self.inner.borrow();
352
353        let result = unsafe {
354            coap_context_set_pki_root_cas(
355                inner.raw_context,
356                ca_file.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
357                ca_dir.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
358            )
359        };
360        if result == 1 {
361            Ok(())
362        } else {
363            Err(ContextConfigurationError::Unknown)
364        }
365    }
366}
367
368impl CoapContext<'_> {
369    /// Performs a controlled shutdown of the CoAP context.
370    ///
371    /// This will perform all still outstanding IO operations until [coap_can_exit()] confirms that
372    /// the context has no more outstanding IO and can be dropped without interrupting sessions.
373    pub fn shutdown(mut self, exit_wait_timeout: Option<Duration>) -> Result<(), IoProcessError> {
374        let mut remaining_time = exit_wait_timeout;
375        // Send remaining packets until we can cleanly shutdown.
376        // SAFETY: Provided context is always valid as an invariant of this struct.
377        while unsafe { coap_can_exit(self.inner.borrow_mut().raw_context) } == 0 {
378            let spent_time = self.do_io(remaining_time)?;
379            remaining_time = remaining_time.map(|v| v.sub(spent_time));
380        }
381        Ok(())
382    }
383
384    /// Store reference to the endpoint
385    fn add_endpoint(&mut self, addr: SocketAddr, proto: coap_proto_t) -> Result<(), EndpointCreationError> {
386        let endpoint = CoapEndpoint::new_endpoint(self, addr, proto)?;
387
388        let mut inner_ref = self.inner.borrow_mut();
389        inner_ref.endpoints.push(endpoint);
390        Ok(())
391    }
392
393    /// Creates a new UDP endpoint that is bound to the given address.
394    pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
395        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_UDP)
396    }
397
398    /// Creates a new TCP endpoint that is bound to the given address.
399    #[cfg(feature = "tcp")]
400    pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
401        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TCP)
402    }
403
404    /// Creates a new DTLS endpoint that is bound to the given address.
405    ///
406    /// Note that in order to actually connect to DTLS clients, you need to set a crypto provider
407    /// using [CoapContext::set_psk_context] and/or [CoapContext::set_pki_rpk_context].
408    #[cfg(feature = "dtls")]
409    pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
410        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_DTLS)
411    }
412
413    /// Joins a multicast group.
414    ///
415    /// `groupname` is usually a multicast IP address, while `ifname` is the used interface.
416    /// If `ifname` is set to `None`, the first appropriate interface will be chosen by the operating system.
417    pub fn join_mcast_group_intf(
418        &mut self,
419        groupname: impl AsRef<CStr>,
420        ifname: Option<&CStr>,
421    ) -> Result<(), MulticastGroupJoinError> {
422        let inner_ref = self.inner.borrow();
423        let mcast_groupname = groupname.as_ref().as_ptr();
424        let mcast_ifname = ifname.map(|v| v.as_ptr()).unwrap_or(std::ptr::null());
425        // SAFETY: `inner_ref.raw_context` is always valid by construction of this type, group and interface name are pointers to valid C strings.
426        unsafe {
427            let ret = coap_join_mcast_group_intf(inner_ref.raw_context, mcast_groupname, mcast_ifname);
428            if ret != 0 {
429                return Err(MulticastGroupJoinError::Unknown);
430            }
431        };
432        Ok(())
433    }
434
435    // /// TODO
436    // #[cfg(all(feature = "tcp", dtls))]
437    // pub fn add_endpoint_tls(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> {
438    //     todo!()
439    //     // TODO: self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TLS)
440    // }
441
442    /// Adds the given resource to the resource pool of this context.
443    pub fn add_resource<D: Any + ?Sized + Debug>(&mut self, res: CoapResource<D>) {
444        let mut inner_ref = self.inner.borrow_mut();
445        inner_ref.resources.push(Box::new(res));
446        // SAFETY: raw context is valid, raw resource is also guaranteed to be valid as long as
447        // contract of CoapResource is upheld.
448        unsafe {
449            coap_add_resource(
450                inner_ref.raw_context,
451                inner_ref.resources.last_mut().unwrap().raw_resource(),
452            );
453        };
454    }
455
456    /// Performs currently outstanding IO operations, waiting for a maximum duration of `timeout`.
457    ///
458    /// This is the function where most of the IO operations made using this library are actually
459    /// executed. It is recommended to call this function in a loop for as long as the CoAP context
460    /// is used.
461    pub fn do_io(&mut self, timeout: Option<Duration>) -> Result<Duration, IoProcessError> {
462        let mut inner_ref = self.inner.borrow_mut();
463        // Round up the duration if it is not a clean number of seconds.
464        let timeout = if let Some(timeout) = timeout {
465            let mut temp_timeout = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX);
466            if timeout.subsec_micros() > 0 || timeout.subsec_nanos() > 0 {
467                temp_timeout = temp_timeout.saturating_add(1);
468            }
469            temp_timeout
470        } else {
471            // If no timeout is set, wait indefinitely.
472            COAP_IO_WAIT
473        };
474        let raw_ctx_ptr = inner_ref.raw_context;
475        // Lend the current mutable reference to potential callers of CoapContext functions on the
476        // other side of the FFI barrier.
477        let lend_handle = self.inner.lend_ref_mut(&mut inner_ref);
478        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
479        // deleted until the CoapContextInner is dropped.
480        // Other raw structs used by libcoap are encapsulated in a way that they cannot be in use
481        // while in this function (considering that they are all !Send).
482        let spent_time = unsafe { coap_io_process(raw_ctx_ptr, timeout) };
483        // Demand the return of the lent handle, ensuring that the mutable reference is no longer
484        // used anywhere.
485        lend_handle.unlend();
486        // Check for errors.
487        if spent_time < 0 {
488            return Err(IoProcessError::Unknown);
489        }
490        // Return with duration of call.
491        Ok(Duration::from_millis(spent_time.unsigned_abs() as u64))
492    }
493
494    /// Return the duration that idle server-side sessions are kept alive if they are not referenced
495    /// or used anywhere else.
496    pub fn session_timeout(&self) -> Duration {
497        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
498        // deleted until the CoapContextInner is dropped.
499        let timeout = unsafe { coap_context_get_session_timeout(self.inner.borrow().raw_context) };
500        Duration::from_secs(timeout as u64)
501    }
502
503    /// Set the duration that idle server-side sessions are kept alive if they are not referenced or
504    /// used anywhere else.
505    ///
506    /// # Panics
507    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
508    /// [libc::c_uint]).
509    pub fn set_session_timeout(&self, timeout: Duration) {
510        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
511        // deleted until the CoapContextInner is dropped.
512        unsafe {
513            coap_context_set_session_timeout(
514                self.inner.borrow_mut().raw_context,
515                timeout
516                    .as_secs()
517                    .try_into()
518                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
519            )
520        }
521    }
522
523    /// Returns the maximum number of server-side sessions that can concurrently be in a handshake
524    /// state.
525    ///
526    /// If this number is exceeded, no new handshakes will be accepted.
527    pub fn max_handshake_sessions(&self) -> c_uint {
528        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
529        // deleted until the CoapContextInner is dropped.
530        unsafe { coap_context_get_max_handshake_sessions(self.inner.borrow().raw_context) }
531    }
532
533    /// Sets the maximum number of server-side sessions that can concurrently be in a handshake
534    /// state.
535    ///
536    /// If this number is exceeded, no new handshakes will be accepted.
537
538    pub fn set_max_handshake_sessions(&self, max_handshake_sessions: c_uint) {
539        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
540        // deleted until the CoapContextInner is dropped.
541        unsafe { coap_context_set_max_handshake_sessions(self.inner.borrow().raw_context, max_handshake_sessions) };
542    }
543
544    /// Returns the maximum number of idle server-side sessions for this context.
545    ///
546    /// If this number is exceeded, the oldest unreferenced session will be freed.
547    pub fn max_idle_sessions(&self) -> c_uint {
548        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
549        // deleted until the CoapContextInner is dropped.
550        unsafe { coap_context_get_max_idle_sessions(self.inner.borrow().raw_context) }
551    }
552
553    /// Sets the maximum number of idle server-side sessions for this context.
554    ///
555    /// If this number is exceeded, the oldest unreferenced session will be freed.
556    pub fn set_max_idle_sessions(&self, max_idle_sessions: c_uint) {
557        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
558        // deleted until the CoapContextInner is dropped.
559        unsafe { coap_context_set_max_idle_sessions(self.inner.borrow().raw_context, max_idle_sessions) };
560    }
561
562    /// Returns the maximum size for Capabilities and Settings Messages
563    ///
564    /// CSMs are used in CoAP over TCP as specified in
565    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
566    pub fn csm_max_message_size(&self) -> u32 {
567        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
568        // deleted until the CoapContextInner is dropped.
569        unsafe { coap_context_get_csm_max_message_size(self.inner.borrow().raw_context) }
570    }
571
572    /// Sets the maximum size for Capabilities and Settings Messages
573    ///
574    /// CSMs are used in CoAP over TCP as specified in
575    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
576    pub fn set_csm_max_message_size(&self, csm_max_message_size: u32) {
577        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
578        // deleted until the CoapContextInner is dropped.
579        unsafe { coap_context_set_csm_max_message_size(self.inner.borrow().raw_context, csm_max_message_size) };
580    }
581
582    /// Returns the timeout for Capabilities and Settings Messages
583    ///
584    /// CSMs are used in CoAP over TCP as specified in
585    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
586    pub fn csm_timeout(&self) -> Duration {
587        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
588        // deleted until the CoapContextInner is dropped.
589        let timeout = unsafe { coap_context_get_csm_timeout(self.inner.borrow().raw_context) };
590        Duration::from_secs(timeout as u64)
591    }
592
593    /// Sets the timeout for Capabilities and Settings Messages
594    ///
595    /// CSMs are used in CoAP over TCP as specified in
596    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
597    ///
598    /// # Panics
599    /// Panics if the provided timeout is too large for libcoap (> [u32::MAX]).
600    pub fn set_csm_timeout(&self, csm_timeout: Duration) {
601        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
602        // deleted until the CoapContextInner is dropped.
603        unsafe {
604            coap_context_set_csm_timeout(
605                self.inner.borrow().raw_context,
606                csm_timeout
607                    .as_secs()
608                    .try_into()
609                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
610            )
611        };
612    }
613
614    /// Sets the number of seconds to wait before sending a CoAP keepalive message for idle
615    /// sessions.
616    ///
617    /// If the provided value is None, CoAP-level keepalive messages will be disabled.
618    ///
619    /// # Panics
620    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
621    /// [libc::c_uint]).
622    pub fn set_keepalive(&self, timeout: Option<Duration>) {
623        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
624        // deleted until the CoapContextInner is dropped.
625        unsafe {
626            coap_context_set_keepalive(
627                self.inner.borrow().raw_context,
628                timeout.map_or(0, |v| {
629                    v.as_secs()
630                        .try_into()
631                        .expect("provided keepalive time is too large for libcoap (> c_uint)")
632                }),
633            )
634        };
635    }
636
637    /// Returns a reference to the raw context contained in this struct.
638    ///
639    /// # Safety
640    /// In general, you should not do anything that would interfere with the safe functions of this
641    /// struct.
642    /// Most notably, this includes the following:
643    /// - Associating raw resources with the context and not removing them before the context is
644    ///   dropped (may cause segfaults on drop).
645    /// - Associating raw sessions that have a reference count != 0 when the CoapContext is dropped
646    ///   (will cause an abort on drop)
647    /// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
648    ///   cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
649    ///   use anything related to the context again, but why would you do that?)
650    // Kept here for consistency, even though it is unused.
651    #[allow(unused)]
652    pub(crate) unsafe fn as_raw_context(&self) -> &coap_context_t {
653        // SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
654        // freed by anything outside of here (assuming the contract of this function is kept), and
655        // the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
656        // is).
657        &*self.inner.borrow().raw_context
658    }
659
660    /// Returns a mutable reference to the raw context contained in this struct.
661    ///
662    /// # Safety
663    /// In general, you should not do anything that would interfere with the safe functions of this
664    /// struct.
665    /// Most notably, this includes the following:
666    /// - Associating raw resources with the context and not removing them before the context is
667    ///   dropped (may cause segfaults on drop).
668    /// - Associating raw sessions that have a reference count != 0 when the CoapContext is dropped
669    ///   (will cause an abort on drop)
670    /// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
671    ///   cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
672    ///   use anything related to the context again, but why would you do that?)
673    pub(crate) unsafe fn as_mut_raw_context(&mut self) -> &mut coap_context_t {
674        // SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
675        // freed by anything outside of here (assuming the contract of this function is kept), and
676        // the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
677        // is).
678        &mut *self.inner.borrow_mut().raw_context
679    }
680
681    // TODO coap_session_get_by_peer
682}
683
684impl Drop for CoapContextInner<'_> {
685    fn drop(&mut self) {
686        // Disable event handler before dropping, as we would otherwise need to lend our reference
687        // and because calling event handlers is probably undesired when we are already dropping
688        // the context.
689        // SAFETY: Validity of our raw context is always given for the lifetime of CoapContextInner
690        // unless coap_free_context() is called during a violation of the [as_mut_raw_context()] and
691        // [as_mut_context()] contracts (we check validity of the pointer on construction).
692        // Passing a NULL handler/None to coap_register_event_handler() is allowed as per the
693        // documentation.
694        unsafe {
695            coap_register_event_handler(self.raw_context, None);
696        }
697        for session in std::mem::take(&mut self.server_sessions).into_iter() {
698            session.drop_exclusively();
699        }
700        // Clear endpoints because coap_free_context() would free their underlying raw structs.
701        self.endpoints.clear();
702        // Extract reference to CoapContextInner from raw context and drop it.
703        // SAFETY: Value is set upon construction of the inner context and never deleted.
704        unsafe {
705            std::mem::drop(CoapLendableFfiWeakCell::<CoapContextInner>::from_raw_box(
706                coap_get_app_data(self.raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>,
707            ))
708        }
709        // Attempt to regain sole ownership over all resources.
710        // As long as [CoapResource::into_inner] isn't used and we haven't given out owned
711        // CoapResource instances whose raw resource is attached to the raw context, this should
712        // never fail.
713        std::mem::take(&mut self.resources)
714            .into_iter()
715            .for_each(UntypedCoapResource::drop_inner_exclusive);
716        // SAFETY: We have already dropped all endpoints and contexts which could be freed alongside
717        // the actual context, and our raw context reference is valid (as long as the contracts of
718        // [as_mut_raw_context()] and [as_mut_context()] are fulfilled).
719        unsafe {
720            coap_free_context(self.raw_context);
721        }
722    }
723}