1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * context.rs - CoAP context related code.
4
 * This file is part of the libcoap-rs crate, see the README and LICENSE files for
5
 * more information and terms of use.
6
 * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
7
 * See the README as well as the LICENSE file for more information.
8
 */
9

            
10
//! Module containing context-internal types and traits.
11

            
12
#[cfg(feature = "dtls-pki")]
13
use std::ffi::CString;
14
#[cfg(dtls)]
15
use std::ptr::NonNull;
16
use std::{any::Any, ffi::c_void, fmt::Debug, net::SocketAddr, ops::Sub, sync::Once, time::Duration};
17
#[cfg(all(feature = "dtls-pki", unix))]
18
use std::{os::unix::ffi::OsStrExt, path::Path};
19

            
20
use libc::c_uint;
21
#[cfg(feature = "dtls-pki")]
22
use libcoap_sys::coap_context_set_pki_root_cas;
23
use libcoap_sys::{
24
    coap_add_resource, coap_can_exit, coap_context_get_csm_max_message_size, coap_context_get_csm_timeout,
25
    coap_context_get_max_handshake_sessions, coap_context_get_max_idle_sessions, coap_context_get_session_timeout,
26
    coap_context_set_block_mode, coap_context_set_csm_max_message_size, coap_context_set_csm_timeout,
27
    coap_context_set_keepalive, coap_context_set_max_handshake_sessions, coap_context_set_max_idle_sessions,
28
    coap_context_set_session_timeout, coap_context_t, coap_event_t, coap_free_context, coap_get_app_data,
29
    coap_io_process, coap_new_context, coap_proto_t, coap_register_event_handler, coap_register_response_handler,
30
    coap_set_app_data, coap_startup_with_feature_checks, COAP_BLOCK_SINGLE_BODY, COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT,
31
};
32

            
33
#[cfg(any(feature = "dtls-rpk", feature = "dtls-pki"))]
34
use crate::crypto::pki_rpk::ServerPkiRpkCryptoContext;
35
#[cfg(feature = "dtls-psk")]
36
use crate::crypto::psk::ServerPskContext;
37
use crate::{
38
    error::{ContextConfigurationError, EndpointCreationError, IoProcessError},
39
    event::{event_handler_callback, CoapEventHandler},
40
    mem::{CoapLendableFfiRcCell, CoapLendableFfiWeakCell, DropInnerExclusively},
41
    resource::{CoapResource, UntypedCoapResource},
42
    session::{session_response_handler, CoapServerSession, CoapSession},
43
    transport::CoapEndpoint,
44
};
45

            
46
static COAP_STARTUP_ONCE: Once = Once::new();
47

            
48
#[inline(always)]
49
2715
pub(crate) fn ensure_coap_started() {
50
2715
    COAP_STARTUP_ONCE.call_once(coap_startup_with_feature_checks);
51
2715
}
52

            
53
#[derive(Debug)]
54
struct CoapContextInner<'a> {
55
    /// Reference to the raw context this context wraps around.
56
    raw_context: *mut coap_context_t,
57
    /// A list of endpoints that this context is currently associated with.
58
    endpoints: Vec<CoapEndpoint>,
59
    /// A list of resources associated with this context.
60
    resources: Vec<Box<dyn UntypedCoapResource>>,
61
    /// A list of server-side sessions that are currently active.
62
    server_sessions: Vec<CoapServerSession<'a>>,
63
    /// The event handler responsible for library-user side handling of events.
64
    event_handler: Option<Box<dyn CoapEventHandler>>,
65
    /// PSK context for encrypted server-side sessions.
66
    #[cfg(feature = "dtls-psk")]
67
    psk_context: Option<ServerPskContext<'a>>,
68
    /// PKI context for encrypted server-side sessions.
69
    #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
70
    pki_rpk_context: Option<ServerPkiRpkCryptoContext<'a>>,
71
}
72

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

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

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

            
129
466
        Ok(CoapContext { inner })
130
466
    }
131

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

            
143
559
        CoapContext { inner }
144
559
    }
145

            
146
    /// Handle an incoming event provided by libcoap.
147
559
    pub(crate) fn handle_event(&self, mut session: CoapSession<'a>, event: coap_event_t) {
148
559
        let inner_ref = &mut *self.inner.borrow_mut();
149
        // Call event handler for event.
150
559
        if let Some(handler) = &mut inner_ref.event_handler {
151
            match event {
152
                coap_event_t::COAP_EVENT_DTLS_CLOSED => handler.handle_dtls_closed(&mut session),
153
                coap_event_t::COAP_EVENT_DTLS_CONNECTED => handler.handle_dtls_connected(&mut session),
154
                coap_event_t::COAP_EVENT_DTLS_RENEGOTIATE => handler.handle_dtls_renegotiate(&mut session),
155
                coap_event_t::COAP_EVENT_DTLS_ERROR => handler.handle_dtls_error(&mut session),
156
                coap_event_t::COAP_EVENT_TCP_CONNECTED => handler.handle_tcp_connected(&mut session),
157
                coap_event_t::COAP_EVENT_TCP_CLOSED => handler.handle_tcp_closed(&mut session),
158
                coap_event_t::COAP_EVENT_TCP_FAILED => handler.handle_tcp_failed(&mut session),
159
                coap_event_t::COAP_EVENT_SESSION_CONNECTED => handler.handle_session_connected(&mut session),
160
                coap_event_t::COAP_EVENT_SESSION_CLOSED => handler.handle_session_closed(&mut session),
161
                coap_event_t::COAP_EVENT_SESSION_FAILED => handler.handle_session_failed(&mut session),
162
                coap_event_t::COAP_EVENT_PARTIAL_BLOCK => handler.handle_partial_block(&mut session),
163
                coap_event_t::COAP_EVENT_SERVER_SESSION_NEW => {
164
                    if let CoapSession::Server(server_session) = &mut session {
165
                        handler.handle_server_session_new(server_session)
166
                    } else {
167
                        panic!("server-side session event fired for non-server-side session");
168
                    }
169
                },
170
                coap_event_t::COAP_EVENT_SERVER_SESSION_DEL => {
171
                    if let CoapSession::Server(server_session) = &mut session {
172
                        handler.handle_server_session_del(server_session)
173
                    } else {
174
                        panic!("server-side session event fired for non-server-side session");
175
                    }
176
                },
177
                coap_event_t::COAP_EVENT_XMIT_BLOCK_FAIL => handler.handle_xmit_block_fail(&mut session),
178
                coap_event_t::COAP_EVENT_BAD_PACKET => handler.handle_bad_packet(&mut session),
179
                coap_event_t::COAP_EVENT_MSG_RETRANSMITTED => handler.handle_msg_retransmitted(&mut session),
180
                coap_event_t::COAP_EVENT_OSCORE_DECRYPTION_FAILURE => {
181
                    handler.handle_oscore_decryption_failure(&mut session)
182
                },
183
                coap_event_t::COAP_EVENT_OSCORE_NOT_ENABLED => handler.handle_oscore_not_enabled(&mut session),
184
                coap_event_t::COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD => {
185
                    handler.handle_oscore_no_protected_payload(&mut session)
186
                },
187
                coap_event_t::COAP_EVENT_OSCORE_NO_SECURITY => handler.handle_oscore_no_security(&mut session),
188
                coap_event_t::COAP_EVENT_OSCORE_INTERNAL_ERROR => handler.handle_oscore_internal_error(&mut session),
189
                coap_event_t::COAP_EVENT_OSCORE_DECODE_ERROR => handler.handle_oscore_decode_error(&mut session),
190
                coap_event_t::COAP_EVENT_WS_PACKET_SIZE => handler.handle_ws_packet_size(&mut session),
191
                coap_event_t::COAP_EVENT_WS_CONNECTED => handler.handle_ws_connected(&mut session),
192
                coap_event_t::COAP_EVENT_WS_CLOSED => handler.handle_ws_closed(&mut session),
193
                coap_event_t::COAP_EVENT_KEEPALIVE_FAILURE => handler.handle_keepalive_failure(&mut session),
194
                _ => {
195
                    // TODO probably a log message is justified here.
196
                },
197
            }
198
559
        }
199
        // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed.
200
559
        if let CoapSession::Server(serv_sess) = session {
201
396
            match event {
202
233
                coap_event_t::COAP_EVENT_SERVER_SESSION_NEW => inner_ref.server_sessions.push(serv_sess),
203
                coap_event_t::COAP_EVENT_SERVER_SESSION_DEL => {
204
                    std::mem::drop(inner_ref.server_sessions.remove(
205
                        inner_ref.server_sessions.iter().position(|v| v.eq(&serv_sess)).expect(
206
                            "attempted to remove session wrapper from context that was never associated with it",
207
                        ),
208
                    ));
209
                    serv_sess.drop_exclusively();
210
                },
211
163
                _ => {},
212
            }
213
163
        }
214
559
    }
215

            
216
    /// Sets the server-side cryptography information provider.
217
    ///
218
    /// # Errors
219
    ///
220
    /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
221
    /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
222
    /// has already been set previously.
223
    #[cfg(feature = "dtls-psk")]
224
35
    pub fn set_psk_context(&mut self, psk_context: ServerPskContext<'a>) -> Result<(), ContextConfigurationError> {
225
35
        let mut inner = self.inner.borrow_mut();
226
35
        if inner.psk_context.is_some() {
227
            return Err(ContextConfigurationError::CryptoContextAlreadySet);
228
35
        }
229
35
        inner.psk_context = Some(psk_context);
230
35
        // SAFETY: raw context is valid, we ensure that an already set encryption context will not
231
35
        // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
232
35
        // dropped (ensuring the encryption context outlives the CoAP context).
233
35
        unsafe {
234
35
            inner
235
35
                .psk_context
236
35
                .as_ref()
237
35
                .unwrap()
238
35
                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
239
        }
240
35
    }
241

            
242
    /// Sets the server-side cryptography information provider.
243
    ///
244
    /// # Errors
245
    ///
246
    /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
247
    /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
248
    /// has already been set previously.
249
    #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
250
13
    pub fn set_pki_rpk_context(
251
13
        &mut self,
252
13
        pki_context: impl Into<ServerPkiRpkCryptoContext<'a>>,
253
13
    ) -> Result<(), ContextConfigurationError> {
254
13
        let mut inner = self.inner.borrow_mut();
255
13
        if inner.pki_rpk_context.is_some() {
256
            return Err(ContextConfigurationError::CryptoContextAlreadySet);
257
13
        }
258
13
        inner.pki_rpk_context = Some(pki_context.into());
259
13
        // SAFETY: raw context is valid, we ensure that an already set encryption context will not
260
13
        // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
261
13
        // dropped (ensuring the encryption context outlives the CoAP context).
262
13
        unsafe {
263
13
            inner
264
13
                .pki_rpk_context
265
13
                .as_ref()
266
13
                .unwrap()
267
13
                .apply_to_context(NonNull::new(inner.raw_context).unwrap())
268
        }
269
13
    }
270

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

            
309
24
        self.set_pki_root_cas(ca_file, ca_dir)
310
24
    }
311

            
312
    /// Sets the path to a CA certificate file and/or a directory of CA certificate files that
313
    /// should be used as this context's default root CA information.
314
    ///
315
    /// `ca_file` should be the full path of a PEM-encoded file containing all root CAs to be used
316
    /// or `None`, `ca_dir` should be a directory path containing PEM-encoded CA certificates to
317
    /// be used or `None`.
318
    ///
319
    /// # Errors
320
    /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
321
    /// function fails (indicating an error in either libcoap or the underlying TLS library).
322
    #[cfg(feature = "dtls-pki")]
323
248
    pub fn set_pki_root_cas(
324
248
        &mut self,
325
248
        ca_file: Option<CString>,
326
248
        ca_dir: Option<CString>,
327
248
    ) -> Result<(), ContextConfigurationError> {
328
248
        let inner = self.inner.borrow();
329
248

            
330
248
        let result = unsafe {
331
248
            coap_context_set_pki_root_cas(
332
248
                inner.raw_context,
333
248
                ca_file.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
334
248
                ca_dir.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
335
248
            )
336
248
        };
337
248
        if result == 1 {
338
248
            Ok(())
339
        } else {
340
            Err(ContextConfigurationError::Unknown)
341
        }
342
248
    }
343
}
344

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

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

            
365
233
        let mut inner_ref = self.inner.borrow_mut();
366
233
        inner_ref.endpoints.push(endpoint);
367
233
        Ok(())
368
233
    }
369

            
370
    /// Creates a new UDP endpoint that is bound to the given address.
371
35
    pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
372
35
        self.add_endpoint(addr, coap_proto_t::COAP_PROTO_UDP)
373
35
    }
374

            
375
    /// Creates a new TCP endpoint that is bound to the given address.
376
    #[cfg(feature = "tcp")]
377
35
    pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
378
35
        self.add_endpoint(addr, coap_proto_t::COAP_PROTO_TCP)
379
35
    }
380

            
381
    /// Creates a new DTLS endpoint that is bound to the given address.
382
    ///
383
    /// Note that in order to actually connect to DTLS clients, you need to set a crypto provider
384
    /// using [CoapContext::set_psk_context] and/or [CoapContext::set_pki_rpk_context].
385
    #[cfg(dtls)]
386
163
    pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
387
163
        self.add_endpoint(addr, coap_proto_t::COAP_PROTO_DTLS)
388
163
    }
389

            
390
    // /// TODO
391
    // #[cfg(all(feature = "tcp", dtls))]
392
    // pub fn add_endpoint_tls(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> {
393
    //     todo!()
394
    //     // TODO: self.add_endpoint(addr, coap_proto_t::COAP_PROTO_TLS)
395
    // }
396

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

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

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

            
458
    /// Set the duration that idle server-side sessions are kept alive if they are not referenced or
459
    /// used anywhere else.
460
    ///
461
    /// # Panics
462
    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
463
    /// [libc::c_uint]).
464
    pub fn set_session_timeout(&self, timeout: Duration) {
465
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
466
        // deleted until the CoapContextInner is dropped.
467
        unsafe {
468
            coap_context_set_session_timeout(
469
                self.inner.borrow_mut().raw_context,
470
                timeout
471
                    .as_secs()
472
                    .try_into()
473
                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
474
            )
475
        }
476
    }
477

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

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

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

            
499
    /// Returns the maximum number of idle server-side sessions for this context.
500
    ///
501
    /// If this number is exceeded, the oldest unreferenced session will be freed.
502
    pub fn max_idle_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_idle_sessions(self.inner.borrow().raw_context) }
506
    }
507

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

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

            
527
    /// Sets the maximum size for Capabilities and Settings Messages
528
    ///
529
    /// CSMs are used in CoAP over TCP as specified in
530
    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
531
    pub fn set_csm_max_message_size(&self, csm_max_message_size: u32) {
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_csm_max_message_size(self.inner.borrow().raw_context, csm_max_message_size) };
535
    }
536

            
537
    /// Returns the timeout 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_timeout(&self) -> Duration {
542
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
543
        // deleted until the CoapContextInner is dropped.
544
        let timeout = unsafe { coap_context_get_csm_timeout(self.inner.borrow().raw_context) };
545
        Duration::from_secs(timeout as u64)
546
    }
547

            
548
    /// Sets the timeout for Capabilities and Settings Messages
549
    ///
550
    /// CSMs are used in CoAP over TCP as specified in
551
    /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
552
    ///
553
    /// # Panics
554
    /// Panics if the provided timeout is too large for libcoap (> [u32::MAX]).
555
    pub fn set_csm_timeout(&self, csm_timeout: Duration) {
556
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
557
        // deleted until the CoapContextInner is dropped.
558
        unsafe {
559
            coap_context_set_csm_timeout(
560
                self.inner.borrow().raw_context,
561
                csm_timeout
562
                    .as_secs()
563
                    .try_into()
564
                    .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
565
            )
566
        };
567
    }
568

            
569
    /// Sets the number of seconds to wait before sending a CoAP keepalive message for idle
570
    /// sessions.
571
    ///
572
    /// If the provided value is None, CoAP-level keepalive messages will be disabled.
573
    ///
574
    /// # Panics
575
    /// Panics if the provided duration is too large to be provided to libcoap (larger than a
576
    /// [libc::c_uint]).
577
    pub fn set_keepalive(&self, timeout: Option<Duration>) {
578
        // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
579
        // deleted until the CoapContextInner is dropped.
580
        unsafe {
581
            coap_context_set_keepalive(
582
                self.inner.borrow().raw_context,
583
                timeout.map_or(0, |v| {
584
                    v.as_secs()
585
                        .try_into()
586
                        .expect("provided keepalive time is too large for libcoap (> c_uint)")
587
                }),
588
            )
589
        };
590
    }
591

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

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

            
636
    // TODO coap_session_get_by_peer
637
}
638

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