libcoap-rs 0.2.1

An idiomatic wrapper around the libcoap CoAP library for Rust.
Documentation
// SPDX-License-Identifier: BSD-2-Clause
/*
 * context.rs - CoAP context related code.
 * Copyright (c) 2022 The NAMIB Project Developers, all rights reserved.
 * See the README as well as the LICENSE file for more information.
 */
//! Module containing context-internal types and traits.

use std::{any::Any, ffi::c_void, fmt::Debug, marker::PhantomData, net::SocketAddr, ops::Sub, time::Duration};

use libc::c_uint;

use libcoap_sys::{
    coap_add_resource, coap_bin_const_t, coap_can_exit, coap_context_get_csm_max_message_size,
    coap_context_get_csm_timeout, coap_context_get_max_handshake_sessions, coap_context_get_max_idle_sessions,
    coap_context_get_session_timeout, coap_context_set_block_mode, coap_context_set_csm_max_message_size,
    coap_context_set_csm_timeout, coap_context_set_keepalive, coap_context_set_max_handshake_sessions,
    coap_context_set_max_idle_sessions, coap_context_set_psk2, coap_context_set_session_timeout, coap_context_t,
    coap_dtls_spsk_info_t, coap_dtls_spsk_t, coap_event_t, coap_free_context, coap_get_app_data, coap_io_process,
    coap_new_context, coap_register_response_handler, coap_set_app_data, coap_set_event_handler,
    COAP_BLOCK_SINGLE_BODY, COAP_BLOCK_USE_LIBCOAP, COAP_DTLS_SPSK_SETUP_VERSION, COAP_IO_WAIT,
};

#[cfg(feature = "dtls")]
use crate::crypto::{dtls_server_id_callback, dtls_server_sni_callback, CoapServerCryptoProvider};
#[cfg(feature = "dtls")]
use crate::crypto::{CoapCryptoProviderResponse, CoapCryptoPskIdentity, CoapCryptoPskInfo};
use crate::event::{event_handler_callback, CoapEventHandler};
use crate::mem::{CoapLendableFfiRcCell, CoapLendableFfiWeakCell, DropInnerExclusively};

use crate::session::CoapClientSession;

use crate::session::CoapServerSession;
use crate::session::CoapSession;
#[cfg(feature = "dtls")]
use crate::transport::CoapDtlsEndpoint;
use crate::{
    error::{ContextCreationError, EndpointCreationError, IoProcessError},
    resource::{CoapResource, UntypedCoapResource},
    session::session_response_handler,
    transport::{CoapEndpoint, CoapUdpEndpoint},
};

#[derive(Debug)]
struct CoapContextInner<'a> {
    /// Reference to the raw context this context wraps around.
    raw_context: *mut coap_context_t,
    /// A list of endpoints that this context is currently associated with.
    endpoints: Vec<CoapEndpoint>,
    /// A list of resources associated with this context.
    resources: Vec<Box<dyn UntypedCoapResource>>,
    /// A list of client-side sessions that were created using the `connect_*` methods
    ///
    /// Note that these are not necessarily all sessions there are. Most notably, server sessions are
    /// automatically created and managed by the underlying C library and are not stored here.
    client_sessions: Vec<CoapClientSession<'a>>,
    /// A list of server-side sessions that are currently active.
    server_sessions: Vec<CoapServerSession<'a>>,
    /// The event handler responsible for library-user side handling of events.
    event_handler: Option<Box<dyn CoapEventHandler>>,
    /// The provider for cryptography information for server-side sessions.
    #[cfg(feature = "dtls")]
    crypto_provider: Option<Box<dyn CoapServerCryptoProvider>>,
    /// Last default cryptography info provided to libcoap.
    #[cfg(feature = "dtls")]
    crypto_default_info: Option<CoapCryptoPskInfo>,
    /// Container for SNI information so that the libcoap C library can keep referring to the memory
    /// locations.
    #[cfg(feature = "dtls")]
    crypto_sni_info_container: Vec<CoapCryptoPskInfo>,
    /// Last provided cryptography information for server-side sessions (temporary storage as
    /// libcoap makes defensive copies).
    #[cfg(feature = "dtls")]
    crypto_current_data: Option<CoapCryptoPskInfo>,
    /// Structure referring to the last provided cryptography information for server-side sessions.
    /// coap_dtls_spsk_info_t created upon calling dtls_server_sni_callback() as the SNI validation callback.
    /// The caller of the validate_sni_call_back will make a defensive copy, so this one only has
    /// to be valid for a very short time and can always be overridden by dtls_server_sni_callback().
    #[cfg(feature = "dtls")]
    crypto_last_info_ref: coap_dtls_spsk_info_t,
    _context_lifetime_marker: PhantomData<&'a coap_context_t>,
}

/// A CoAP Context — container for general state and configuration information relating to CoAP
///
/// The equivalent to the [coap_context_t] type in libcoap.
#[derive(Debug)]
pub struct CoapContext<'a> {
    inner: CoapLendableFfiRcCell<CoapContextInner<'a>>,
}

impl<'a> CoapContext<'a> {
    /// Creates a new context.
    ///
    /// # Errors
    /// Returns an error if the underlying libcoap library was unable to create a new context
    /// (probably an allocation error?).
    pub fn new() -> Result<CoapContext<'a>, ContextCreationError> {
        // SAFETY: Providing null here is fine, the context will just not be bound to an endpoint
        // yet.
        let raw_context = unsafe { coap_new_context(std::ptr::null()) };
        if raw_context.is_null() {
            return Err(ContextCreationError::Unknown);
        }
        // SAFETY: We checked that raw_context is not null.
        unsafe {
            coap_context_set_block_mode(raw_context, (COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY) as u8);
            coap_register_response_handler(raw_context, Some(session_response_handler));
        }
        let inner = CoapLendableFfiRcCell::new(CoapContextInner {
            raw_context,
            endpoints: Vec::new(),
            resources: Vec::new(),
            client_sessions: Vec::new(),
            server_sessions: Vec::new(),
            event_handler: None,
            #[cfg(feature = "dtls")]
            crypto_provider: None,
            #[cfg(feature = "dtls")]
            crypto_default_info: None,
            #[cfg(feature = "dtls")]
            crypto_sni_info_container: Vec::new(),
            #[cfg(feature = "dtls")]
            crypto_current_data: None,
            #[cfg(feature = "dtls")]
            crypto_last_info_ref: coap_dtls_spsk_info_t {
                hint: coap_bin_const_t {
                    length: 0,
                    s: std::ptr::null(),
                },
                key: coap_bin_const_t {
                    length: 0,
                    s: std::ptr::null(),
                },
            },
            _context_lifetime_marker: Default::default(),
        });

        // SAFETY: We checked that the raw context is not null, the provided function is valid and
        // the app data pointer provided must be valid as we just created it using
        // `create_raw_weak_box()`.
        unsafe {
            coap_set_app_data(raw_context, inner.create_raw_weak_box() as *mut c_void);
            coap_set_event_handler(raw_context, Some(event_handler_callback));
        }

        Ok(CoapContext { inner })
    }

    /// Attaches a new client-side session to this context.
    ///
    /// # Safety
    /// The provided session's raw counterpart must belong to this context's underlying raw context.
    pub(crate) unsafe fn attach_client_session(&mut self, session: CoapClientSession<'a>) {
        self.inner.borrow_mut().client_sessions.push(session);
    }

    /// Restores a CoapContext from its raw counterpart.
    ///
    /// # Safety
    /// Provided pointer must point to as valid instance of a raw context whose application data
    /// points to a `*mut CoapLenadableFfiWeakCell<CoapContextInner>`.
    pub(crate) unsafe fn from_raw(raw_context: *mut coap_context_t) -> CoapContext<'a> {
        assert!(!raw_context.is_null());
        let inner = CoapLendableFfiRcCell::clone_raw_weak_box(
            coap_get_app_data(raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>
        );

        CoapContext { inner }
    }

    /// Handle an incoming event provided by libcoap.
    pub(crate) fn handle_event(&self, mut session: CoapSession<'a>, event: coap_event_t) {
        let inner_ref = &mut *self.inner.borrow_mut();
        // Call event handler for event.
        if let Some(handler) = &mut inner_ref.event_handler {
            match event {
                coap_event_t::COAP_EVENT_DTLS_CLOSED => handler.handle_dtls_closed(&mut session),
                coap_event_t::COAP_EVENT_DTLS_CONNECTED => handler.handle_dtls_connected(&mut session),
                coap_event_t::COAP_EVENT_DTLS_RENEGOTIATE => handler.handle_dtls_renegotiate(&mut session),
                coap_event_t::COAP_EVENT_DTLS_ERROR => handler.handle_dtls_error(&mut session),
                coap_event_t::COAP_EVENT_TCP_CONNECTED => handler.handle_tcp_connected(&mut session),
                coap_event_t::COAP_EVENT_TCP_CLOSED => handler.handle_tcp_closed(&mut session),
                coap_event_t::COAP_EVENT_TCP_FAILED => handler.handle_tcp_failed(&mut session),
                coap_event_t::COAP_EVENT_SESSION_CONNECTED => handler.handle_session_connected(&mut session),
                coap_event_t::COAP_EVENT_SESSION_CLOSED => handler.handle_session_closed(&mut session),
                coap_event_t::COAP_EVENT_SESSION_FAILED => handler.handle_session_failed(&mut session),
                coap_event_t::COAP_EVENT_PARTIAL_BLOCK => handler.handle_partial_block(&mut session),
                coap_event_t::COAP_EVENT_SERVER_SESSION_NEW => {
                    if let CoapSession::Server(server_session) = &mut session {
                        handler.handle_server_session_new(server_session)
                    } else {
                        panic!("server-side session event fired for non-server-side session");
                    }
                },
                coap_event_t::COAP_EVENT_SERVER_SESSION_DEL => {
                    if let CoapSession::Server(server_session) = &mut session {
                        handler.handle_server_session_del(server_session)
                    } else {
                        panic!("server-side session event fired for non-server-side session");
                    }
                },
                _ => {
                    // TODO probably a log message is justified here.
                },
            }
        }
        // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed.
        if let CoapSession::Server(serv_sess) = session {
            match event {
                coap_event_t::COAP_EVENT_SERVER_SESSION_NEW => inner_ref.server_sessions.push(serv_sess),
                coap_event_t::COAP_EVENT_SERVER_SESSION_DEL => {
                    std::mem::drop(inner_ref.server_sessions.remove(
                        inner_ref.server_sessions.iter().position(|v| v.eq(&serv_sess)).expect(
                            "attempted to remove session wrapper from context that was never associated with it",
                        ),
                    ));
                    serv_sess.drop_exclusively();
                },
                _ => {},
            }
        }
    }
}

impl CoapContext<'_> {
    /// Performs a controlled shutdown of the CoAP context.
    ///
    /// This will perform all still outstanding IO operations until [coap_can_exit()] confirms that
    /// the context has no more outstanding IO and can be dropped without interrupting sessions.
    pub fn shutdown(mut self, exit_wait_timeout: Option<Duration>) -> Result<(), IoProcessError> {
        let mut remaining_time = exit_wait_timeout;
        // Send remaining packets until we can cleanly shutdown.
        // SAFETY: Provided context is always valid as an invariant of this struct.
        while unsafe { coap_can_exit(self.inner.borrow_mut().raw_context) } == 0 {
            let spent_time = self.do_io(remaining_time)?;
            remaining_time = remaining_time.map(|v| v.sub(spent_time));
        }
        Ok(())
    }

    /// Creates a new UDP endpoint that is bound to the given address.
    pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
        // SAFETY: Because we never return an owned reference to the endpoint, it cannot outlive the
        // context it is bound to (i.e. this one).
        let endpoint = unsafe { CoapUdpEndpoint::new(self, addr)? }.into();
        let mut inner_ref = self.inner.borrow_mut();
        inner_ref.endpoints.push(endpoint);
        // Cannot fail, we just pushed to the Vec.
        Ok(())
    }

    /// TODO
    #[cfg(feature = "tcp")]
    pub fn add_endpoint_tcp(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> {
        todo!()
    }

    /// Creates a new DTLS endpoint that is bound to the given address.
    ///
    /// Note that in order to actually connect to DTLS clients, you need to set a crypto provider
    /// using [set_server_crypto_provider()](CoapContext::set_server_crypto_provider())
    #[cfg(feature = "dtls")]
    pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
        // SAFETY: Provided context (i.e., this instance) will not outlive the endpoint, as we will
        // drop it alongside this context and never hand out copies of it.
        let endpoint = unsafe { CoapDtlsEndpoint::new(self, addr)? }.into();
        let mut inner_ref = self.inner.borrow_mut();
        inner_ref.endpoints.push(endpoint);
        // Cannot fail, we just pushed to the Vec.
        Ok(())
    }

    /// TODO
    #[cfg(all(feature = "tcp", feature = "dtls"))]
    pub fn add_endpoint_tls(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> {
        todo!()
    }

    /// Adds the given resource to the resource pool of this context.
    pub fn add_resource<D: Any + ?Sized + Debug>(&mut self, res: CoapResource<D>) {
        let mut inner_ref = self.inner.borrow_mut();
        inner_ref.resources.push(Box::new(res));
        // SAFETY: raw context is valid, raw resource is also guaranteed to be valid as long as
        // contract of CoapResource is upheld.
        unsafe {
            coap_add_resource(
                inner_ref.raw_context,
                inner_ref.resources.last_mut().unwrap().raw_resource(),
            );
        };
    }

    /// Sets the server-side cryptography information provider.
    #[cfg(feature = "dtls")]
    pub fn set_server_crypto_provider(&mut self, provider: Option<Box<dyn CoapServerCryptoProvider>>) {
        let mut inner_ref = self.inner.borrow_mut();
        // TODO replace Option<Box<Something>> with Option<Borrow<Something>> in libcoap-rs to simplify API.
        inner_ref.crypto_provider = provider;
        if let Some(provider) = &mut inner_ref.crypto_provider {
            inner_ref.crypto_default_info = Some(provider.provide_default_info());
            let initial_data = coap_dtls_spsk_info_t {
                hint: coap_bin_const_t {
                    length: inner_ref.crypto_default_info.as_ref().unwrap().key.len(),
                    s: inner_ref.crypto_default_info.as_ref().unwrap().key.as_ptr(),
                },
                key: coap_bin_const_t {
                    length: inner_ref.crypto_default_info.as_ref().unwrap().key.len(),
                    s: inner_ref.crypto_default_info.as_ref().unwrap().key.as_ptr(),
                },
            };
            // SAFETY: raw context is valid, setup_data is of the right type and contains
            // only valid information.
            unsafe {
                coap_context_set_psk2(
                    inner_ref.raw_context,
                    Box::into_raw(Box::new(coap_dtls_spsk_t {
                        version: COAP_DTLS_SPSK_SETUP_VERSION as u8,
                        reserved: [0; 7],
                        validate_id_call_back: Some(dtls_server_id_callback),
                        id_call_back_arg: inner_ref.raw_context as *mut c_void,
                        validate_sni_call_back: Some(dtls_server_sni_callback),
                        sni_call_back_arg: inner_ref.raw_context as *mut c_void,
                        psk_info: initial_data,
                    })),
                )
            };
        }
    }

    /// Performs currently outstanding IO operations, waiting for a maximum duration of `timeout`.
    ///
    /// This is the function where most of the IO operations made using this library are actually
    /// executed. It is recommended to call this function in a loop for as long as the CoAP context
    /// is used.
    pub fn do_io(&mut self, timeout: Option<Duration>) -> Result<Duration, IoProcessError> {
        let mut inner_ref = self.inner.borrow_mut();
        // Round up the duration if it is not a clean number of seconds.
        let timeout = if let Some(timeout) = timeout {
            let mut temp_timeout = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX);
            if timeout.subsec_micros() > 0 || timeout.subsec_nanos() > 0 {
                temp_timeout = temp_timeout.saturating_add(1);
            }
            temp_timeout
        } else {
            // If no timeout is set, wait indefinitely.
            COAP_IO_WAIT
        };
        let raw_ctx_ptr = inner_ref.raw_context;
        // Lend the current mutable reference to potential callers of CoapContext functions on the
        // other side of the FFI barrier.
        let lend_handle = self.inner.lend_ref_mut(&mut inner_ref);
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        let spent_time = unsafe { coap_io_process(raw_ctx_ptr, timeout) };
        // Demand the return of the lent handle, ensuring that the mutable reference is no longer
        // used anywhere.
        lend_handle.unlend();
        // Check for errors.
        if spent_time < 0 {
            return Err(IoProcessError::Unknown);
        }
        // Return with duration of call.
        Ok(Duration::from_millis(spent_time.unsigned_abs() as u64))
    }

    /// Return the duration that idle server-side sessions are kept alive if they are not referenced
    /// or used anywhere else.
    pub fn session_timeout(&self) -> Duration {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        let timeout = unsafe { coap_context_get_session_timeout(self.inner.borrow().raw_context) };
        Duration::from_secs(timeout as u64)
    }

    /// Set the duration that idle server-side sessions are kept alive if they are not referenced or
    /// used anywhere else.
    ///
    /// # Panics
    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
    /// [libc::c_uint]).
    pub fn set_session_timeout(&self, timeout: Duration) {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe {
            coap_context_set_session_timeout(
                self.inner.borrow_mut().raw_context,
                timeout
                    .as_secs()
                    .try_into()
                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
            )
        }
    }

    /// Returns the maximum number of server-side sessions that can concurrently be in a handshake
    /// state.
    ///
    /// If this number is exceeded, no new handshakes will be accepted.
    pub fn max_handshake_sessions(&self) -> c_uint {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe { coap_context_get_max_handshake_sessions(self.inner.borrow().raw_context) }
    }

    /// Sets the maximum number of server-side sessions that can concurrently be in a handshake
    /// state.
    ///
    /// If this number is exceeded, no new handshakes will be accepted.

    pub fn set_max_handshake_sessions(&self, max_handshake_sessions: c_uint) {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe { coap_context_set_max_handshake_sessions(self.inner.borrow().raw_context, max_handshake_sessions) };
    }

    /// Returns the maximum number of idle server-side sessions for this context.
    ///
    /// If this number is exceeded, the oldest unreferenced session will be freed.
    pub fn max_idle_sessions(&self) -> c_uint {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe { coap_context_get_max_idle_sessions(self.inner.borrow().raw_context) }
    }

    /// Sets the maximum number of idle server-side sessions for this context.
    ///
    /// If this number is exceeded, the oldest unreferenced session will be freed.
    pub fn set_max_idle_sessions(&self, max_idle_sessions: c_uint) {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe { coap_context_set_max_idle_sessions(self.inner.borrow().raw_context, max_idle_sessions) };
    }

    /// Returns the maximum size for Capabilities and Settings Messages
    ///
    /// CSMs are used in CoAP over TCP as specified in
    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
    pub fn csm_max_message_size(&self) -> u32 {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe { coap_context_get_csm_max_message_size(self.inner.borrow().raw_context) }
    }

    /// Sets the maximum size for Capabilities and Settings Messages
    ///
    /// CSMs are used in CoAP over TCP as specified in
    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
    pub fn set_csm_max_message_size(&self, csm_max_message_size: u32) {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe { coap_context_set_csm_max_message_size(self.inner.borrow().raw_context, csm_max_message_size) };
    }

    /// Returns the timeout for Capabilities and Settings Messages
    ///
    /// CSMs are used in CoAP over TCP as specified in
    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
    pub fn csm_timeout(&self) -> Duration {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        let timeout = unsafe { coap_context_get_csm_timeout(self.inner.borrow().raw_context) };
        Duration::from_secs(timeout as u64)
    }

    /// Sets the timeout for Capabilities and Settings Messages
    ///
    /// CSMs are used in CoAP over TCP as specified in
    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
    ///
    /// # Panics
    /// Panics if the provided timeout is too large for libcoap (> [u32::MAX]).
    pub fn set_csm_timeout(&self, csm_timeout: Duration) {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe {
            coap_context_set_csm_timeout(
                self.inner.borrow().raw_context,
                csm_timeout
                    .as_secs()
                    .try_into()
                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
            )
        };
    }

    /// Sets the number of seconds to wait before sending a CoAP keepalive message for idle
    /// sessions.
    ///
    /// If the provided value is None, CoAP-level keepalive messages will be disabled.
    ///
    /// # Panics
    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
    /// [libc::c_uint]).
    pub fn set_keepalive(&self, timeout: Option<Duration>) {
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
        // deleted until the CoapContextInner is dropped.
        unsafe {
            coap_context_set_keepalive(
                self.inner.borrow().raw_context,
                timeout.map_or(0, |v| {
                    v.as_secs()
                        .try_into()
                        .expect("provided keepalive time is too large for libcoap (> c_uint)")
                }),
            )
        };
    }

    /// Provide a raw key for a given identity using the CoapContext's set server crypto provider.
    ///
    /// # Safety
    /// Returned pointer should only be used if the context is borrowed.
    /// Calling this function may override previous returned values of this function.
    #[cfg(feature = "dtls")]
    pub(crate) unsafe fn provide_raw_key_for_identity(
        &self,
        identity: &CoapCryptoPskIdentity,
    ) -> Option<*const coap_bin_const_t> {
        let inner_ref = &mut *self.inner.borrow_mut();
        match inner_ref
            .crypto_provider
            .as_mut()
            .map(|v| v.provide_key_for_identity(identity))
        {
            Some(CoapCryptoProviderResponse::UseNew(new_data)) => {
                inner_ref.crypto_current_data = Some(CoapCryptoPskInfo {
                    identity: Box::from(identity),
                    key: new_data,
                });
                let curr_data = inner_ref.crypto_current_data.as_ref().unwrap();
                curr_data.apply_to_spsk_info(&mut inner_ref.crypto_last_info_ref);
                Some(&inner_ref.crypto_last_info_ref.key as *const coap_bin_const_t)
            },
            Some(CoapCryptoProviderResponse::UseCurrent) => inner_ref.crypto_current_data.as_ref().map(|v| {
                v.apply_to_spsk_info(&mut inner_ref.crypto_last_info_ref);
                &inner_ref.crypto_last_info_ref.key as *const coap_bin_const_t
            }),
            None | Some(CoapCryptoProviderResponse::Unacceptable) => None,
        }
    }

    /// Provide a hint for a given SNI name using the CoapContext's set server crypto provider.
    ///
    /// # Safety
    /// Returned pointer should only be used if the context is borrowed.
    /// Calling this function may override previous returned values of this function.
    #[cfg(all(feature = "dtls"))]
    pub(crate) unsafe fn provide_raw_hint_for_sni(&self, sni: &str) -> Option<*const coap_dtls_spsk_info_t> {
        let inner_ref = &mut *self.inner.borrow_mut();
        match inner_ref.crypto_provider.as_mut().map(|v| v.provide_hint_for_sni(sni)) {
            Some(CoapCryptoProviderResponse::UseNew(new_info)) => {
                inner_ref.crypto_sni_info_container.push(new_info);
                inner_ref
                    .crypto_sni_info_container
                    .last()
                    .unwrap()
                    .apply_to_spsk_info(&mut inner_ref.crypto_last_info_ref);
                Some(&inner_ref.crypto_last_info_ref as *const coap_dtls_spsk_info_t)
            },
            Some(CoapCryptoProviderResponse::UseCurrent) => {
                if inner_ref.crypto_default_info.is_some() {
                    inner_ref
                        .crypto_default_info
                        .as_ref()
                        .unwrap()
                        .apply_to_spsk_info(&mut inner_ref.crypto_last_info_ref);
                    Some(&inner_ref.crypto_last_info_ref as *const coap_dtls_spsk_info_t)
                } else {
                    None
                }
            },
            None | Some(CoapCryptoProviderResponse::Unacceptable) => None,
        }
    }

    /// Returns a reference to the raw context contained in this struct.
    ///
    /// # Safety
    /// In general, you should not do anything that would interfere with the safe functions of this
    /// struct.
    /// Most notably, this includes the following:
    /// - Associating raw resources with the context and not removing them before the context is
    ///   dropped (may cause segfaults on drop).
    /// - Associating raw sessions that have a reference count != 0 when the CoapContext is dropped
    ///   (will cause an abort on drop)
    /// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
    ///   cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
    ///   use anything related to the context again, but why would you do that?)  
    // Kept here for consistency, even though it is unused.
    #[allow(unused)]
    pub(crate) unsafe fn as_raw_context(&self) -> &coap_context_t {
        // SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
        // freed by anything outside of here (assuming the contract of this function is kept), and
        // the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
        // is).
        &*self.inner.borrow().raw_context
    }

    /// Returns a mutable reference to the raw context contained in this struct.
    ///
    /// # Safety
    /// In general, you should not do anything that would interfere with the safe functions of this
    /// struct.
    /// Most notably, this includes the following:
    /// - Associating raw resources with the context and not removing them before the context is
    ///   dropped (may cause segfaults on drop).
    /// - Associating raw sessions that have a reference count != 0 when the CoapContext is dropped
    ///   (will cause an abort on drop)
    /// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
    ///   cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
    ///   use anything related to the context again, but why would you do that?)  
    pub(crate) unsafe fn as_mut_raw_context(&mut self) -> &mut coap_context_t {
        // SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
        // freed by anything outside of here (assuming the contract of this function is kept), and
        // the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
        // is).
        &mut *self.inner.borrow_mut().raw_context
    }

    // TODO coap_session_get_by_peer
}

impl Drop for CoapContextInner<'_> {
    fn drop(&mut self) {
        // Clean up sessions while the remainder of the context is still available.
        for session in std::mem::take(&mut self.client_sessions).into_iter() {
            session.drop_exclusively();
        }
        for session in std::mem::take(&mut self.server_sessions).into_iter() {
            session.drop_exclusively();
        }
        // Clear endpoints because coap_free_context() would free their underlying raw structs.
        self.endpoints.clear();
        // Extract reference to CoapContextInner from raw context and drop it.
        // SAFETY: Value is set upon construction of the inner context and never deleted.
        unsafe {
            std::mem::drop(CoapLendableFfiWeakCell::<CoapContextInner>::from_raw_box(
                coap_get_app_data(self.raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>,
            ))
        }
        // Attempt to regain sole ownership over all resources.
        // As long as [CoapResource::into_inner] isn't used and we haven't given out owned
        // CoapResource instances whose raw resource is attached to the raw context, this should
        // never fail.
        std::mem::take(&mut self.resources)
            .into_iter()
            .for_each(UntypedCoapResource::drop_inner_exclusive);
        // SAFETY: We have already dropped all endpoints and contexts which could be freed alongside
        // the actual context, and our raw context reference is valid (as long as the contracts of
        // [as_mut_raw_context()] and [as_mut_context()] are fulfilled).
        unsafe {
            coap_free_context(self.raw_context);
        }
    }
}