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