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

            
13
use core::ffi::c_uint;
14
#[cfg(feature = "dtls-pki")]
15
use std::ffi::CString;
16
#[cfg(feature = "dtls")]
17
use std::ptr::NonNull;
18
use 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))]
28
use std::{os::unix::ffi::OsStrExt, path::Path};
29

            
30
#[cfg(feature = "dtls-pki")]
31
use libcoap_sys::coap_context_set_pki_root_cas;
32
use 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")]
56
use libcoap_sys::{coap_context_oscore_server, coap_delete_oscore_recipient, coap_new_oscore_recipient};
57

            
58
#[cfg(any(feature = "dtls-rpk", feature = "dtls-pki"))]
59
use crate::crypto::pki_rpk::ServerPkiRpkCryptoContext;
60
#[cfg(feature = "dtls-psk")]
61
use crate::crypto::psk::ServerPskContext;
62
use 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")]
72
use crate::{
73
    error::{OscoreRecipientError, OscoreServerCreationError},
74
    OscoreConf,
75
};
76

            
77
static COAP_STARTUP_ONCE: Once = Once::new();
78

            
79
#[inline(always)]
80
2715
pub(crate) fn ensure_coap_started() {
81
2715
    COAP_STARTUP_ONCE.call_once(coap_startup_with_feature_checks);
82
2715
}
83

            
84
#[derive(Debug)]
85
struct 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)]
115
pub struct CoapContext<'a> {
116
    inner: CoapLendableFfiRcCell<CoapContextInner<'a>>,
117
}
118

            
119
impl<'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
466
    pub fn new() -> Result<CoapContext<'a>, ContextConfigurationError> {
126
466
        ensure_coap_started();
127
        // SAFETY: Providing null here is fine, the context will just not be bound to an endpoint
128
        // yet.
129
466
        let raw_context = unsafe { coap_new_context(std::ptr::null()) };
130
466
        if raw_context.is_null() {
131
            return Err(ContextConfigurationError::Unknown);
132
466
        }
133
        // SAFETY: We checked that raw_context is not null.
134
466
        unsafe {
135
466
            coap_context_set_block_mode(
136
466
                raw_context,
137
466
                // In some versions of libcoap, bindgen infers COAP_BLOCK_USE_LIBCOAP and
138
466
                // COAP_BLOCK_SINGLE_BODY to be u32, while the function parameter is u8.
139
466
                // Therefore, we use `try_into()` to convert to the right type, and panic if this is
140
466
                // not possible (should never happen)
141
466
                (COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY)
142
466
                    .try_into()
143
466
                    .expect("coap_context_set_block_mode() flags have invalid type for function"),
144
466
            );
145
466
            coap_register_response_handler(raw_context, Some(session_response_handler));
146
466
        }
147
466
        let inner = CoapLendableFfiRcCell::new(CoapContextInner {
148
466
            raw_context,
149
466
            endpoints: Vec::new(),
150
466
            resources: Vec::new(),
151
466
            server_sessions: Vec::new(),
152
466
            event_handler: None,
153
466
            #[cfg(feature = "dtls-psk")]
154
466
            psk_context: None,
155
466
            #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
156
466
            pki_rpk_context: None,
157
466
            #[cfg(feature = "oscore")]
158
466
            provided_oscore_information: false,
159
466
            #[cfg(feature = "oscore")]
160
466
            recipients: Vec::new(),
161
466
        });
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
466
        unsafe {
167
466
            coap_set_app_data(raw_context, inner.create_raw_weak_box() as *mut c_void);
168
466
            coap_register_event_handler(raw_context, Some(event_handler_callback));
169
466
        }
170

            
171
466
        Ok(CoapContext { inner })
172
466
    }
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
559
    pub(crate) unsafe fn from_raw(raw_context: *mut coap_context_t) -> CoapContext<'a> {
180
559
        assert!(!raw_context.is_null());
181
559
        let inner = CoapLendableFfiRcCell::clone_raw_weak_box(
182
559
            coap_get_app_data(raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>
183
        );
184

            
185
559
        CoapContext { inner }
186
559
    }
187

            
188
    /// Handle an incoming event provided by libcoap.
189
559
    pub(crate) fn handle_event(&self, mut session: CoapSession<'a>, event: coap_event_t) {
190
559
        let inner_ref = &mut *self.inner.borrow_mut();
191
        // Call event handler for event.
192
559
        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
559
        }
244
        // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed.
245
559
        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
396
            match event {
250
233
                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
163
                _ => {},
260
            }
261
163
        }
262
559
    }
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
35
    pub fn set_psk_context(&mut self, psk_context: ServerPskContext<'a>) -> Result<(), ContextConfigurationError> {
273
35
        let mut inner = self.inner.borrow_mut();
274
35
        if inner.psk_context.is_some() {
275
            return Err(ContextConfigurationError::CryptoContextAlreadySet);
276
35
        }
277
35
        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
35
            inner
283
35
                .psk_context
284
35
                .as_ref()
285
35
                .unwrap()
286
35
                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
287
        }
288
35
    }
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
13
    pub fn set_pki_rpk_context(
299
13
        &mut self,
300
13
        pki_context: impl Into<ServerPkiRpkCryptoContext<'a>>,
301
13
    ) -> Result<(), ContextConfigurationError> {
302
13
        let mut inner = self.inner.borrow_mut();
303
13
        if inner.pki_rpk_context.is_some() {
304
            return Err(ContextConfigurationError::CryptoContextAlreadySet);
305
13
        }
306
13
        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
13
            inner
312
13
                .pki_rpk_context
313
13
                .as_ref()
314
13
                .unwrap()
315
13
                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
316
        }
317
13
    }
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
24
    pub fn set_pki_root_ca_paths(
340
24
        &mut self,
341
24
        ca_file: Option<impl AsRef<Path>>,
342
24
        ca_dir: Option<impl AsRef<Path>>,
343
24
    ) -> Result<(), ContextConfigurationError> {
344
24
        let ca_file = ca_file.as_ref().map(|v| {
345
24
            let v = v.as_ref();
346
24
            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
24
            CString::new(v.as_os_str().as_bytes()).unwrap()
349
24
        });
350
24
        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
24
        self.set_pki_root_cas(ca_file, ca_dir)
358
24
    }
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
248
    pub fn set_pki_root_cas(
372
248
        &mut self,
373
248
        ca_file: Option<CString>,
374
248
        ca_dir: Option<CString>,
375
248
    ) -> Result<(), ContextConfigurationError> {
376
248
        let inner = self.inner.borrow();
377

            
378
248
        let result = unsafe {
379
248
            coap_context_set_pki_root_cas(
380
248
                inner.raw_context,
381
248
                ca_file.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
382
248
                ca_dir.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
383
            )
384
        };
385
248
        if result == 1 {
386
248
            Ok(())
387
        } else {
388
            Err(ContextConfigurationError::Unknown)
389
        }
390
248
    }
391
}
392

            
393
impl 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
233
    pub fn shutdown(mut self, exit_wait_timeout: Option<Duration>) -> Result<(), IoProcessError> {
399
233
        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
233
        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
233
        Ok(())
407
233
    }
408

            
409
    /// Store reference to the endpoint
410
233
    fn add_endpoint(&mut self, addr: SocketAddr, proto: coap_proto_t) -> Result<(), EndpointCreationError> {
411
233
        let endpoint = CoapEndpoint::new_endpoint(self, addr, proto)?;
412

            
413
233
        let mut inner_ref = self.inner.borrow_mut();
414
233
        inner_ref.endpoints.push(endpoint);
415
233
        Ok(())
416
233
    }
417

            
418
    /// Creates a new UDP endpoint that is bound to the given address.
419
35
    pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
420
35
        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_UDP)
421
35
    }
422

            
423
    /// Creates a new TCP endpoint that is bound to the given address.
424
    #[cfg(feature = "tcp")]
425
35
    pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
426
35
        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TCP)
427
35
    }
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
163
    pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
588
163
        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_DTLS)
589
163
    }
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
25
    pub fn add_resource<D: Any + ?Sized + Debug>(&mut self, res: CoapResource<D>) {
622
25
        let mut inner_ref = self.inner.borrow_mut();
623
25
        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
25
        unsafe {
627
25
            coap_add_resource(
628
25
                inner_ref.raw_context,
629
25
                inner_ref.resources.last_mut().unwrap().raw_resource(),
630
25
            );
631
25
        };
632
25
    }
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
1990
    pub fn do_io(&mut self, timeout: Option<Duration>) -> Result<Duration, IoProcessError> {
640
1990
        let mut inner_ref = self.inner.borrow_mut();
641
        // Round up the duration if it is not a clean number of seconds.
642
1990
        let timeout = if let Some(timeout) = timeout {
643
1990
            let mut temp_timeout = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX);
644
1990
            if timeout.subsec_micros() > 0 || timeout.subsec_nanos() > 0 {
645
                temp_timeout = temp_timeout.saturating_add(1);
646
1990
            }
647
1990
            temp_timeout
648
        } else {
649
            // If no timeout is set, wait indefinitely.
650
            COAP_IO_WAIT
651
        };
652
1990
        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
1990
        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
1990
        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
1990
        lend_handle.unlend();
664
        // Check for errors.
665
1990
        if spent_time < 0 {
666
            return Err(IoProcessError::Unknown);
667
1990
        }
668
        // Return with duration of call.
669
1990
        Ok(Duration::from_millis(spent_time.unsigned_abs() as u64))
670
1990
    }
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
466
    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
466
        &mut *self.inner.borrow_mut().raw_context
857
466
    }
858

            
859
    // TODO coap_session_get_by_peer
860
}
861

            
862
impl Drop for CoapContextInner<'_> {
863
466
    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
466
        unsafe {
873
466
            coap_register_event_handler(self.raw_context, None);
874
466
        }
875
466
        for session in std::mem::take(&mut self.server_sessions).into_iter() {
876
233
            session.drop_exclusively();
877
233
        }
878
        // Clear endpoints because coap_free_context() would free their underlying raw structs.
879
466
        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
466
            std::mem::drop(CoapLendableFfiWeakCell::<CoapContextInner>::from_raw_box(
884
466
                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
466
        std::mem::take(&mut self.resources)
892
466
            .into_iter()
893
466
            .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
466
        unsafe {
898
466
            coap_free_context(self.raw_context);
899
466
        }
900

            
901
        // Drop all recipients as coap_free_context() has dropped all associated raw_recipients.
902
        #[cfg(feature = "oscore")]
903
        self.recipients.clear();
904
466
    }
905
}