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