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::{any::Any, ffi::c_void, fmt::Debug, net::SocketAddr, ops::Sub, sync::Once, time::Duration};
19
#[cfg(all(feature = "dtls-pki", unix))]
20
use std::{os::unix::ffi::OsStrExt, path::Path};
21

            
22
#[cfg(feature = "dtls-pki")]
23
use libcoap_sys::coap_context_set_pki_root_cas;
24
use libcoap_sys::{
25
    coap_add_resource, coap_can_exit, coap_context_get_csm_max_message_size, coap_context_get_csm_timeout,
26
    coap_context_get_max_handshake_sessions, coap_context_get_max_idle_sessions, coap_context_get_session_timeout,
27
    coap_context_set_block_mode, coap_context_set_csm_max_message_size, coap_context_set_csm_timeout,
28
    coap_context_set_keepalive, coap_context_set_max_handshake_sessions, coap_context_set_max_idle_sessions,
29
    coap_context_set_session_timeout, coap_context_t, coap_event_t, coap_event_t_COAP_EVENT_BAD_PACKET,
30
    coap_event_t_COAP_EVENT_DTLS_CLOSED, coap_event_t_COAP_EVENT_DTLS_CONNECTED, coap_event_t_COAP_EVENT_DTLS_ERROR,
31
    coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE, coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE,
32
    coap_event_t_COAP_EVENT_MSG_RETRANSMITTED, coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR,
33
    coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE, coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR,
34
    coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED, coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD,
35
    coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY, coap_event_t_COAP_EVENT_PARTIAL_BLOCK,
36
    coap_event_t_COAP_EVENT_SERVER_SESSION_DEL, coap_event_t_COAP_EVENT_SERVER_SESSION_NEW,
37
    coap_event_t_COAP_EVENT_SESSION_CLOSED, coap_event_t_COAP_EVENT_SESSION_CONNECTED,
38
    coap_event_t_COAP_EVENT_SESSION_FAILED, coap_event_t_COAP_EVENT_TCP_CLOSED, coap_event_t_COAP_EVENT_TCP_CONNECTED,
39
    coap_event_t_COAP_EVENT_TCP_FAILED, coap_event_t_COAP_EVENT_WS_CLOSED, coap_event_t_COAP_EVENT_WS_CONNECTED,
40
    coap_event_t_COAP_EVENT_WS_PACKET_SIZE, coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL, coap_free_context,
41
    coap_get_app_data, coap_io_process, coap_new_context, coap_proto_t, coap_proto_t_COAP_PROTO_DTLS,
42
    coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP, coap_register_event_handler,
43
    coap_register_response_handler, coap_set_app_data, coap_startup_with_feature_checks, COAP_BLOCK_SINGLE_BODY,
44
    COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT,
45
};
46

            
47
#[cfg(any(feature = "dtls-rpk", feature = "dtls-pki"))]
48
use crate::crypto::pki_rpk::ServerPkiRpkCryptoContext;
49
#[cfg(feature = "dtls-psk")]
50
use crate::crypto::psk::ServerPskContext;
51
use crate::{
52
    error::{ContextConfigurationError, EndpointCreationError, IoProcessError},
53
    event::{event_handler_callback, CoapEventHandler},
54
    mem::{CoapLendableFfiRcCell, CoapLendableFfiWeakCell, DropInnerExclusively},
55
    resource::{CoapResource, UntypedCoapResource},
56
    session::{session_response_handler, CoapServerSession, CoapSession},
57
    transport::CoapEndpoint,
58
};
59

            
60
static COAP_STARTUP_ONCE: Once = Once::new();
61

            
62
#[inline(always)]
63
2715
pub(crate) fn ensure_coap_started() {
64
2715
    COAP_STARTUP_ONCE.call_once(coap_startup_with_feature_checks);
65
2715
}
66

            
67
#[derive(Debug)]
68
struct CoapContextInner<'a> {
69
    /// Reference to the raw context this context wraps around.
70
    raw_context: *mut coap_context_t,
71
    /// A list of endpoints that this context is currently associated with.
72
    endpoints: Vec<CoapEndpoint>,
73
    /// A list of resources associated with this context.
74
    resources: Vec<Box<dyn UntypedCoapResource>>,
75
    /// A list of server-side sessions that are currently active.
76
    server_sessions: Vec<CoapServerSession<'a>>,
77
    /// The event handler responsible for library-user side handling of events.
78
    event_handler: Option<Box<dyn CoapEventHandler>>,
79
    /// PSK context for encrypted server-side sessions.
80
    #[cfg(feature = "dtls-psk")]
81
    psk_context: Option<ServerPskContext<'a>>,
82
    /// PKI context for encrypted server-side sessions.
83
    #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
84
    pki_rpk_context: Option<ServerPkiRpkCryptoContext<'a>>,
85
}
86

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

            
95
impl<'a> CoapContext<'a> {
96
    /// Creates a new context.
97
    ///
98
    /// # Errors
99
    /// Returns an error if the underlying libcoap library was unable to create a new context
100
    /// (probably an allocation error?).
101
466
    pub fn new() -> Result<CoapContext<'a>, ContextConfigurationError> {
102
466
        ensure_coap_started();
103
466
        // SAFETY: Providing null here is fine, the context will just not be bound to an endpoint
104
466
        // yet.
105
466
        let raw_context = unsafe { coap_new_context(std::ptr::null()) };
106
466
        if raw_context.is_null() {
107
            return Err(ContextConfigurationError::Unknown);
108
466
        }
109
466
        // SAFETY: We checked that raw_context is not null.
110
466
        unsafe {
111
466
            coap_context_set_block_mode(
112
466
                raw_context,
113
466
                // In some versions of libcoap, bindgen infers COAP_BLOCK_USE_LIBCOAP and
114
466
                // COAP_BLOCK_SINGLE_BODY to be u32, while the function parameter is u8.
115
466
                // Therefore, we use `try_into()` to convert to the right type, and panic if this is
116
466
                // not possible (should never happen)
117
466
                (COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY)
118
466
                    .try_into()
119
466
                    .expect("coap_context_set_block_mode() flags have invalid type for function"),
120
466
            );
121
466
            coap_register_response_handler(raw_context, Some(session_response_handler));
122
466
        }
123
466
        let inner = CoapLendableFfiRcCell::new(CoapContextInner {
124
466
            raw_context,
125
466
            endpoints: Vec::new(),
126
466
            resources: Vec::new(),
127
466
            server_sessions: Vec::new(),
128
466
            event_handler: None,
129
466
            #[cfg(feature = "dtls-psk")]
130
466
            psk_context: None,
131
466
            #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
132
466
            pki_rpk_context: None,
133
466
        });
134
466

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

            
143
466
        Ok(CoapContext { inner })
144
466
    }
145

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

            
157
569
        CoapContext { inner }
158
569
    }
159

            
160
    /// Handle an incoming event provided by libcoap.
161
569
    pub(crate) fn handle_event(&self, mut session: CoapSession<'a>, event: coap_event_t) {
162
569
        let inner_ref = &mut *self.inner.borrow_mut();
163
        // Call event handler for event.
164
569
        if let Some(handler) = &mut inner_ref.event_handler {
165
            // Variant names are named by bindgen, we have no influence on this.
166
            // Ref: https://github.com/rust-lang/rust/issues/39371
167
            #[allow(non_upper_case_globals)]
168
            match event {
169
                coap_event_t_COAP_EVENT_DTLS_CLOSED => handler.handle_dtls_closed(&mut session),
170
                coap_event_t_COAP_EVENT_DTLS_CONNECTED => handler.handle_dtls_connected(&mut session),
171
                coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE => handler.handle_dtls_renegotiate(&mut session),
172
                coap_event_t_COAP_EVENT_DTLS_ERROR => handler.handle_dtls_error(&mut session),
173
                coap_event_t_COAP_EVENT_TCP_CONNECTED => handler.handle_tcp_connected(&mut session),
174
                coap_event_t_COAP_EVENT_TCP_CLOSED => handler.handle_tcp_closed(&mut session),
175
                coap_event_t_COAP_EVENT_TCP_FAILED => handler.handle_tcp_failed(&mut session),
176
                coap_event_t_COAP_EVENT_SESSION_CONNECTED => handler.handle_session_connected(&mut session),
177
                coap_event_t_COAP_EVENT_SESSION_CLOSED => handler.handle_session_closed(&mut session),
178
                coap_event_t_COAP_EVENT_SESSION_FAILED => handler.handle_session_failed(&mut session),
179
                coap_event_t_COAP_EVENT_PARTIAL_BLOCK => handler.handle_partial_block(&mut session),
180
                coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => {
181
                    if let CoapSession::Server(server_session) = &mut session {
182
                        handler.handle_server_session_new(server_session)
183
                    } else {
184
                        panic!("server-side session event fired for non-server-side session");
185
                    }
186
                },
187
                coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => {
188
                    if let CoapSession::Server(server_session) = &mut session {
189
                        handler.handle_server_session_del(server_session)
190
                    } else {
191
                        panic!("server-side session event fired for non-server-side session");
192
                    }
193
                },
194
                coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL => handler.handle_xmit_block_fail(&mut session),
195
                coap_event_t_COAP_EVENT_BAD_PACKET => handler.handle_bad_packet(&mut session),
196
                coap_event_t_COAP_EVENT_MSG_RETRANSMITTED => handler.handle_msg_retransmitted(&mut session),
197
                coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE => {
198
                    handler.handle_oscore_decryption_failure(&mut session)
199
                },
200
                coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED => handler.handle_oscore_not_enabled(&mut session),
201
                coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD => {
202
                    handler.handle_oscore_no_protected_payload(&mut session)
203
                },
204
                coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY => handler.handle_oscore_no_security(&mut session),
205
                coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR => handler.handle_oscore_internal_error(&mut session),
206
                coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR => handler.handle_oscore_decode_error(&mut session),
207
                coap_event_t_COAP_EVENT_WS_PACKET_SIZE => handler.handle_ws_packet_size(&mut session),
208
                coap_event_t_COAP_EVENT_WS_CONNECTED => handler.handle_ws_connected(&mut session),
209
                coap_event_t_COAP_EVENT_WS_CLOSED => handler.handle_ws_closed(&mut session),
210
                coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE => handler.handle_keepalive_failure(&mut session),
211
                _ => {
212
                    // TODO probably a log message is justified here.
213
                },
214
            }
215
569
        }
216
        // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed.
217
569
        if let CoapSession::Server(serv_sess) = session {
218
            // Variant names are named by bindgen, we have no influence on this.
219
            // Ref: https://github.com/rust-lang/rust/issues/39371
220
            #[allow(non_upper_case_globals)]
221
401
            match event {
222
233
                coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => inner_ref.server_sessions.push(serv_sess),
223
                coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => {
224
                    std::mem::drop(inner_ref.server_sessions.remove(
225
                        inner_ref.server_sessions.iter().position(|v| v.eq(&serv_sess)).expect(
226
                            "attempted to remove session wrapper from context that was never associated with it",
227
                        ),
228
                    ));
229
                    serv_sess.drop_exclusively();
230
                },
231
168
                _ => {},
232
            }
233
168
        }
234
569
    }
235

            
236
    /// Sets the server-side cryptography information provider.
237
    ///
238
    /// # Errors
239
    ///
240
    /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
241
    /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
242
    /// has already been set previously.
243
    #[cfg(feature = "dtls-psk")]
244
35
    pub fn set_psk_context(&mut self, psk_context: ServerPskContext<'a>) -> Result<(), ContextConfigurationError> {
245
35
        let mut inner = self.inner.borrow_mut();
246
35
        if inner.psk_context.is_some() {
247
            return Err(ContextConfigurationError::CryptoContextAlreadySet);
248
35
        }
249
35
        inner.psk_context = Some(psk_context);
250
35
        // SAFETY: raw context is valid, we ensure that an already set encryption context will not
251
35
        // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
252
35
        // dropped (ensuring the encryption context outlives the CoAP context).
253
35
        unsafe {
254
35
            inner
255
35
                .psk_context
256
35
                .as_ref()
257
35
                .unwrap()
258
35
                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
259
        }
260
35
    }
261

            
262
    /// Sets the server-side cryptography information provider.
263
    ///
264
    /// # Errors
265
    ///
266
    /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
267
    /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
268
    /// has already been set previously.
269
    #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
270
13
    pub fn set_pki_rpk_context(
271
13
        &mut self,
272
13
        pki_context: impl Into<ServerPkiRpkCryptoContext<'a>>,
273
13
    ) -> Result<(), ContextConfigurationError> {
274
13
        let mut inner = self.inner.borrow_mut();
275
13
        if inner.pki_rpk_context.is_some() {
276
            return Err(ContextConfigurationError::CryptoContextAlreadySet);
277
13
        }
278
13
        inner.pki_rpk_context = Some(pki_context.into());
279
13
        // SAFETY: raw context is valid, we ensure that an already set encryption context will not
280
13
        // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
281
13
        // dropped (ensuring the encryption context outlives the CoAP context).
282
13
        unsafe {
283
13
            inner
284
13
                .pki_rpk_context
285
13
                .as_ref()
286
13
                .unwrap()
287
13
                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
288
        }
289
13
    }
290

            
291
    /// Convenience wrapper around [`set_pki_root_cas`](CoapContext::set_pki_root_cas) that can be
292
    /// provided with any type that implements `AsRef<Path>`.
293
    ///
294
    /// `ca_file` should be the full path of a PEM-encoded file containing all root CAs to be used
295
    /// or `None`, `ca_dir` should be a directory path containing PEM-encoded CA certificates to
296
    /// be used or `None`.
297
    ///
298
    /// As not all implementations of [`OsString`] (which is the basis of [`Path`]) provide
299
    /// conversions to non-zero byte sequences, this function is only available on Unix.
300
    /// On other operating systems, perform manual conversion of paths into [`CString`] and call
301
    /// [`set_pki_root_cas`](CoapContext::set_pki_root_cas) directly instead.
302
    ///
303
    /// See the Rust standard library documentation on [FFI conversions](https://doc.rust-lang.org/std/ffi/index.html#conversions])
304
    /// and the [`OsString` type](https://doc.rust-lang.org/std/ffi/struct.OsString.html) for more
305
    /// information.
306
    ///
307
    /// # Errors
308
    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
309
    /// function fails (indicating an error in either libcoap or the underlying TLS library).
310
    #[cfg(all(feature = "dtls-pki", unix))]
311
24
    pub fn set_pki_root_ca_paths(
312
24
        &mut self,
313
24
        ca_file: Option<impl AsRef<Path>>,
314
24
        ca_dir: Option<impl AsRef<Path>>,
315
24
    ) -> Result<(), ContextConfigurationError> {
316
24
        let ca_file = ca_file.as_ref().map(|v| {
317
24
            let v = v.as_ref();
318
24
            assert!(v.is_file(), "attempted to set non-file as CA file for libcoap");
319
            // Unix paths never contain null bytes, so we can unwrap here.
320
24
            CString::new(v.as_os_str().as_bytes()).unwrap()
321
24
        });
322
24
        let ca_dir = ca_dir.as_ref().map(|v| {
323
            let v = v.as_ref();
324
            assert!(v.is_dir(), "attempted to set non-directory as CA directory for libcoap");
325
            // Unix paths never contain null bytes, so we can unwrap here.
326
            CString::new(v.as_os_str().as_bytes()).unwrap()
327
24
        });
328
24

            
329
24
        self.set_pki_root_cas(ca_file, ca_dir)
330
24
    }
331

            
332
    /// Sets the path to a CA certificate file and/or a directory of CA certificate files that
333
    /// should be used as this context's default root CA information.
334
    ///
335
    /// `ca_file` should be the full path of a PEM-encoded file containing all root CAs to be used
336
    /// or `None`, `ca_dir` should be a directory path containing PEM-encoded CA certificates to
337
    /// be used or `None`.
338
    ///
339
    /// # Errors
340
    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
341
    /// function fails (indicating an error in either libcoap or the underlying TLS library).
342
    #[cfg(feature = "dtls-pki")]
343
248
    pub fn set_pki_root_cas(
344
248
        &mut self,
345
248
        ca_file: Option<CString>,
346
248
        ca_dir: Option<CString>,
347
248
    ) -> Result<(), ContextConfigurationError> {
348
248
        let inner = self.inner.borrow();
349
248

            
350
248
        let result = unsafe {
351
248
            coap_context_set_pki_root_cas(
352
248
                inner.raw_context,
353
248
                ca_file.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
354
248
                ca_dir.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
355
248
            )
356
248
        };
357
248
        if result == 1 {
358
248
            Ok(())
359
        } else {
360
            Err(ContextConfigurationError::Unknown)
361
        }
362
248
    }
363
}
364

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

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

            
385
233
        let mut inner_ref = self.inner.borrow_mut();
386
233
        inner_ref.endpoints.push(endpoint);
387
233
        Ok(())
388
233
    }
389

            
390
    /// Creates a new UDP endpoint that is bound to the given address.
391
35
    pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
392
35
        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_UDP)
393
35
    }
394

            
395
    /// Creates a new TCP endpoint that is bound to the given address.
396
    #[cfg(feature = "tcp")]
397
35
    pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
398
35
        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TCP)
399
35
    }
400

            
401
    /// Creates a new DTLS endpoint that is bound to the given address.
402
    ///
403
    /// Note that in order to actually connect to DTLS clients, you need to set a crypto provider
404
    /// using [CoapContext::set_psk_context] and/or [CoapContext::set_pki_rpk_context].
405
    #[cfg(feature = "dtls")]
406
163
    pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
407
163
        self.add_endpoint(addr, coap_proto_t_COAP_PROTO_DTLS)
408
163
    }
409

            
410
    // /// TODO
411
    // #[cfg(all(feature = "tcp", dtls))]
412
    // pub fn add_endpoint_tls(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> {
413
    //     todo!()
414
    //     // TODO: self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TLS)
415
    // }
416

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

            
431
    /// Performs currently outstanding IO operations, waiting for a maximum duration of `timeout`.
432
    ///
433
    /// This is the function where most of the IO operations made using this library are actually
434
    /// executed. It is recommended to call this function in a loop for as long as the CoAP context
435
    /// is used.
436
1993
    pub fn do_io(&mut self, timeout: Option<Duration>) -> Result<Duration, IoProcessError> {
437
1993
        let mut inner_ref = self.inner.borrow_mut();
438
        // Round up the duration if it is not a clean number of seconds.
439
1993
        let timeout = if let Some(timeout) = timeout {
440
1993
            let mut temp_timeout = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX);
441
1993
            if timeout.subsec_micros() > 0 || timeout.subsec_nanos() > 0 {
442
                temp_timeout = temp_timeout.saturating_add(1);
443
1993
            }
444
1993
            temp_timeout
445
        } else {
446
            // If no timeout is set, wait indefinitely.
447
            COAP_IO_WAIT
448
        };
449
1993
        let raw_ctx_ptr = inner_ref.raw_context;
450
1993
        // Lend the current mutable reference to potential callers of CoapContext functions on the
451
1993
        // other side of the FFI barrier.
452
1993
        let lend_handle = self.inner.lend_ref_mut(&mut inner_ref);
453
1993
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
454
1993
        // deleted until the CoapContextInner is dropped.
455
1993
        // Other raw structs used by libcoap are encapsulated in a way that they cannot be in use
456
1993
        // while in this function (considering that they are all !Send).
457
1993
        let spent_time = unsafe { coap_io_process(raw_ctx_ptr, timeout) };
458
1993
        // Demand the return of the lent handle, ensuring that the mutable reference is no longer
459
1993
        // used anywhere.
460
1993
        lend_handle.unlend();
461
1993
        // Check for errors.
462
1993
        if spent_time < 0 {
463
            return Err(IoProcessError::Unknown);
464
1993
        }
465
1993
        // Return with duration of call.
466
1993
        Ok(Duration::from_millis(spent_time.unsigned_abs() as u64))
467
1993
    }
468

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

            
478
    /// Set the duration that idle server-side sessions are kept alive if they are not referenced or
479
    /// used anywhere else.
480
    ///
481
    /// # Panics
482
    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
483
    /// [libc::c_uint]).
484
    pub fn set_session_timeout(&self, timeout: Duration) {
485
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
486
        // deleted until the CoapContextInner is dropped.
487
        unsafe {
488
            coap_context_set_session_timeout(
489
                self.inner.borrow_mut().raw_context,
490
                timeout
491
                    .as_secs()
492
                    .try_into()
493
                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
494
            )
495
        }
496
    }
497

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

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

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

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

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

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

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

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

            
568
    /// Sets the timeout for Capabilities and Settings Messages
569
    ///
570
    /// CSMs are used in CoAP over TCP as specified in
571
    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
572
    ///
573
    /// # Panics
574
    /// Panics if the provided timeout is too large for libcoap (> [u32::MAX]).
575
    pub fn set_csm_timeout(&self, csm_timeout: Duration) {
576
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
577
        // deleted until the CoapContextInner is dropped.
578
        unsafe {
579
            coap_context_set_csm_timeout(
580
                self.inner.borrow().raw_context,
581
                csm_timeout
582
                    .as_secs()
583
                    .try_into()
584
                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
585
            )
586
        };
587
    }
588

            
589
    /// Sets the number of seconds to wait before sending a CoAP keepalive message for idle
590
    /// sessions.
591
    ///
592
    /// If the provided value is None, CoAP-level keepalive messages will be disabled.
593
    ///
594
    /// # Panics
595
    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
596
    /// [libc::c_uint]).
597
    pub fn set_keepalive(&self, timeout: Option<Duration>) {
598
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
599
        // deleted until the CoapContextInner is dropped.
600
        unsafe {
601
            coap_context_set_keepalive(
602
                self.inner.borrow().raw_context,
603
                timeout.map_or(0, |v| {
604
                    v.as_secs()
605
                        .try_into()
606
                        .expect("provided keepalive time is too large for libcoap (> c_uint)")
607
                }),
608
            )
609
        };
610
    }
611

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

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

            
656
    // TODO coap_session_get_by_peer
657
}
658

            
659
impl Drop for CoapContextInner<'_> {
660
466
    fn drop(&mut self) {
661
466
        // Disable event handler before dropping, as we would otherwise need to lend our reference
662
466
        // and because calling event handlers is probably undesired when we are already dropping
663
466
        // the context.
664
466
        // SAFETY: Validity of our raw context is always given for the lifetime of CoapContextInner
665
466
        // unless coap_free_context() is called during a violation of the [as_mut_raw_context()] and
666
466
        // [as_mut_context()] contracts (we check validity of the pointer on construction).
667
466
        // Passing a NULL handler/None to coap_register_event_handler() is allowed as per the
668
466
        // documentation.
669
466
        unsafe {
670
466
            coap_register_event_handler(self.raw_context, None);
671
466
        }
672
466
        for session in std::mem::take(&mut self.server_sessions).into_iter() {
673
233
            session.drop_exclusively();
674
233
        }
675
        // Clear endpoints because coap_free_context() would free their underlying raw structs.
676
466
        self.endpoints.clear();
677
466
        // Extract reference to CoapContextInner from raw context and drop it.
678
466
        // SAFETY: Value is set upon construction of the inner context and never deleted.
679
466
        unsafe {
680
466
            std::mem::drop(CoapLendableFfiWeakCell::<CoapContextInner>::from_raw_box(
681
466
                coap_get_app_data(self.raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>,
682
466
            ))
683
466
        }
684
466
        // Attempt to regain sole ownership over all resources.
685
466
        // As long as [CoapResource::into_inner] isn't used and we haven't given out owned
686
466
        // CoapResource instances whose raw resource is attached to the raw context, this should
687
466
        // never fail.
688
466
        std::mem::take(&mut self.resources)
689
466
            .into_iter()
690
466
            .for_each(UntypedCoapResource::drop_inner_exclusive);
691
466
        // SAFETY: We have already dropped all endpoints and contexts which could be freed alongside
692
466
        // the actual context, and our raw context reference is valid (as long as the contracts of
693
466
        // [as_mut_raw_context()] and [as_mut_context()] are fulfilled).
694
466
        unsafe {
695
466
            coap_free_context(self.raw_context);
696
466
        }
697
466
    }
698
}