libcoap_rs/context.rs
1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * Copyright © The libcoap-rs Contributors, all rights reserved.
4 * This file is part of the libcoap-rs project, see the README file for
5 * general information on this project and the NOTICE.md and LICENSE files
6 * for information regarding copyright ownership and terms of use.
7 *
8 * context.rs - CoAP context related code.
9 */
10
11//! Module containing context-internal types and traits.
12
13use core::ffi::c_uint;
14#[cfg(feature = "dtls-pki")]
15use std::ffi::CString;
16#[cfg(feature = "dtls")]
17use std::ptr::NonNull;
18use std::{any::Any, ffi::c_void, fmt::Debug, net::SocketAddr, ops::Sub, sync::Once, time::Duration};
19#[cfg(all(feature = "dtls-pki", unix))]
20use std::{os::unix::ffi::OsStrExt, path::Path};
21
22#[cfg(feature = "dtls-pki")]
23use libcoap_sys::coap_context_set_pki_root_cas;
24use 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"))]
48use crate::crypto::pki_rpk::ServerPkiRpkCryptoContext;
49#[cfg(feature = "dtls-psk")]
50use crate::crypto::psk::ServerPskContext;
51use 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
60static COAP_STARTUP_ONCE: Once = Once::new();
61
62#[inline(always)]
63pub(crate) fn ensure_coap_started() {
64 COAP_STARTUP_ONCE.call_once(coap_startup_with_feature_checks);
65}
66
67#[derive(Debug)]
68struct 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)]
91pub struct CoapContext<'a> {
92 inner: CoapLendableFfiRcCell<CoapContextInner<'a>>,
93}
94
95impl<'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 pub fn new() -> Result<CoapContext<'a>, ContextConfigurationError> {
102 ensure_coap_started();
103 // SAFETY: Providing null here is fine, the context will just not be bound to an endpoint
104 // yet.
105 let raw_context = unsafe { coap_new_context(std::ptr::null()) };
106 if raw_context.is_null() {
107 return Err(ContextConfigurationError::Unknown);
108 }
109 // SAFETY: We checked that raw_context is not null.
110 unsafe {
111 coap_context_set_block_mode(
112 raw_context,
113 // In some versions of libcoap, bindgen infers COAP_BLOCK_USE_LIBCOAP and
114 // COAP_BLOCK_SINGLE_BODY to be u32, while the function parameter is u8.
115 // Therefore, we use `try_into()` to convert to the right type, and panic if this is
116 // not possible (should never happen)
117 (COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY)
118 .try_into()
119 .expect("coap_context_set_block_mode() flags have invalid type for function"),
120 );
121 coap_register_response_handler(raw_context, Some(session_response_handler));
122 }
123 let inner = CoapLendableFfiRcCell::new(CoapContextInner {
124 raw_context,
125 endpoints: Vec::new(),
126 resources: Vec::new(),
127 server_sessions: Vec::new(),
128 event_handler: None,
129 #[cfg(feature = "dtls-psk")]
130 psk_context: None,
131 #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
132 pki_rpk_context: None,
133 });
134
135 // SAFETY: We checked that the raw context is not null, the provided function is valid and
136 // the app data pointer provided must be valid as we just created it using
137 // `create_raw_weak_box()`.
138 unsafe {
139 coap_set_app_data(raw_context, inner.create_raw_weak_box() as *mut c_void);
140 coap_register_event_handler(raw_context, Some(event_handler_callback));
141 }
142
143 Ok(CoapContext { inner })
144 }
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 pub(crate) unsafe fn from_raw(raw_context: *mut coap_context_t) -> CoapContext<'a> {
152 assert!(!raw_context.is_null());
153 let inner = CoapLendableFfiRcCell::clone_raw_weak_box(
154 coap_get_app_data(raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>
155 );
156
157 CoapContext { inner }
158 }
159
160 /// Handle an incoming event provided by libcoap.
161 pub(crate) fn handle_event(&self, mut session: CoapSession<'a>, event: coap_event_t) {
162 let inner_ref = &mut *self.inner.borrow_mut();
163 // Call event handler for event.
164 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 }
216 // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed.
217 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 match event {
222 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 _ => {},
232 }
233 }
234 }
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 pub fn set_psk_context(&mut self, psk_context: ServerPskContext<'a>) -> Result<(), ContextConfigurationError> {
245 let mut inner = self.inner.borrow_mut();
246 if inner.psk_context.is_some() {
247 return Err(ContextConfigurationError::CryptoContextAlreadySet);
248 }
249 inner.psk_context = Some(psk_context);
250 // SAFETY: raw context is valid, we ensure that an already set encryption context will not
251 // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
252 // dropped (ensuring the encryption context outlives the CoAP context).
253 unsafe {
254 inner
255 .psk_context
256 .as_ref()
257 .unwrap()
258 .apply_to_context(NonNull::new(inner.raw_context).unwrap())
259 }
260 }
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 pub fn set_pki_rpk_context(
271 &mut self,
272 pki_context: impl Into<ServerPkiRpkCryptoContext<'a>>,
273 ) -> Result<(), ContextConfigurationError> {
274 let mut inner = self.inner.borrow_mut();
275 if inner.pki_rpk_context.is_some() {
276 return Err(ContextConfigurationError::CryptoContextAlreadySet);
277 }
278 inner.pki_rpk_context = Some(pki_context.into());
279 // SAFETY: raw context is valid, we ensure that an already set encryption context will not
280 // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
281 // dropped (ensuring the encryption context outlives the CoAP context).
282 unsafe {
283 inner
284 .pki_rpk_context
285 .as_ref()
286 .unwrap()
287 .apply_to_context(NonNull::new(inner.raw_context).unwrap())
288 }
289 }
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 pub fn set_pki_root_ca_paths(
312 &mut self,
313 ca_file: Option<impl AsRef<Path>>,
314 ca_dir: Option<impl AsRef<Path>>,
315 ) -> Result<(), ContextConfigurationError> {
316 let ca_file = ca_file.as_ref().map(|v| {
317 let v = v.as_ref();
318 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 CString::new(v.as_os_str().as_bytes()).unwrap()
321 });
322 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 });
328
329 self.set_pki_root_cas(ca_file, ca_dir)
330 }
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 pub fn set_pki_root_cas(
344 &mut self,
345 ca_file: Option<CString>,
346 ca_dir: Option<CString>,
347 ) -> Result<(), ContextConfigurationError> {
348 let inner = self.inner.borrow();
349
350 let result = unsafe {
351 coap_context_set_pki_root_cas(
352 inner.raw_context,
353 ca_file.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
354 ca_dir.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
355 )
356 };
357 if result == 1 {
358 Ok(())
359 } else {
360 Err(ContextConfigurationError::Unknown)
361 }
362 }
363}
364
365impl 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 pub fn shutdown(mut self, exit_wait_timeout: Option<Duration>) -> Result<(), IoProcessError> {
371 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 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 Ok(())
379 }
380
381 /// Store reference to the endpoint
382 fn add_endpoint(&mut self, addr: SocketAddr, proto: coap_proto_t) -> Result<(), EndpointCreationError> {
383 let endpoint = CoapEndpoint::new_endpoint(self, addr, proto)?;
384
385 let mut inner_ref = self.inner.borrow_mut();
386 inner_ref.endpoints.push(endpoint);
387 Ok(())
388 }
389
390 /// Creates a new UDP endpoint that is bound to the given address.
391 pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
392 self.add_endpoint(addr, coap_proto_t_COAP_PROTO_UDP)
393 }
394
395 /// Creates a new TCP endpoint that is bound to the given address.
396 #[cfg(feature = "tcp")]
397 pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
398 self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TCP)
399 }
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 pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
407 self.add_endpoint(addr, coap_proto_t_COAP_PROTO_DTLS)
408 }
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 pub fn add_resource<D: Any + ?Sized + Debug>(&mut self, res: CoapResource<D>) {
419 let mut inner_ref = self.inner.borrow_mut();
420 inner_ref.resources.push(Box::new(res));
421 // SAFETY: raw context is valid, raw resource is also guaranteed to be valid as long as
422 // contract of CoapResource is upheld.
423 unsafe {
424 coap_add_resource(
425 inner_ref.raw_context,
426 inner_ref.resources.last_mut().unwrap().raw_resource(),
427 );
428 };
429 }
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 pub fn do_io(&mut self, timeout: Option<Duration>) -> Result<Duration, IoProcessError> {
437 let mut inner_ref = self.inner.borrow_mut();
438 // Round up the duration if it is not a clean number of seconds.
439 let timeout = if let Some(timeout) = timeout {
440 let mut temp_timeout = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX);
441 if timeout.subsec_micros() > 0 || timeout.subsec_nanos() > 0 {
442 temp_timeout = temp_timeout.saturating_add(1);
443 }
444 temp_timeout
445 } else {
446 // If no timeout is set, wait indefinitely.
447 COAP_IO_WAIT
448 };
449 let raw_ctx_ptr = inner_ref.raw_context;
450 // Lend the current mutable reference to potential callers of CoapContext functions on the
451 // other side of the FFI barrier.
452 let lend_handle = self.inner.lend_ref_mut(&mut inner_ref);
453 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
454 // deleted until the CoapContextInner is dropped.
455 // Other raw structs used by libcoap are encapsulated in a way that they cannot be in use
456 // while in this function (considering that they are all !Send).
457 let spent_time = unsafe { coap_io_process(raw_ctx_ptr, timeout) };
458 // Demand the return of the lent handle, ensuring that the mutable reference is no longer
459 // used anywhere.
460 lend_handle.unlend();
461 // Check for errors.
462 if spent_time < 0 {
463 return Err(IoProcessError::Unknown);
464 }
465 // Return with duration of call.
466 Ok(Duration::from_millis(spent_time.unsigned_abs() as u64))
467 }
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 pub(crate) unsafe fn as_mut_raw_context(&mut self) -> &mut coap_context_t {
649 // SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
650 // freed by anything outside of here (assuming the contract of this function is kept), and
651 // the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
652 // is).
653 &mut *self.inner.borrow_mut().raw_context
654 }
655
656 // TODO coap_session_get_by_peer
657}
658
659impl Drop for CoapContextInner<'_> {
660 fn drop(&mut self) {
661 // Disable event handler before dropping, as we would otherwise need to lend our reference
662 // and because calling event handlers is probably undesired when we are already dropping
663 // the context.
664 // SAFETY: Validity of our raw context is always given for the lifetime of CoapContextInner
665 // unless coap_free_context() is called during a violation of the [as_mut_raw_context()] and
666 // [as_mut_context()] contracts (we check validity of the pointer on construction).
667 // Passing a NULL handler/None to coap_register_event_handler() is allowed as per the
668 // documentation.
669 unsafe {
670 coap_register_event_handler(self.raw_context, None);
671 }
672 for session in std::mem::take(&mut self.server_sessions).into_iter() {
673 session.drop_exclusively();
674 }
675 // Clear endpoints because coap_free_context() would free their underlying raw structs.
676 self.endpoints.clear();
677 // Extract reference to CoapContextInner from raw context and drop it.
678 // SAFETY: Value is set upon construction of the inner context and never deleted.
679 unsafe {
680 std::mem::drop(CoapLendableFfiWeakCell::<CoapContextInner>::from_raw_box(
681 coap_get_app_data(self.raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>,
682 ))
683 }
684 // Attempt to regain sole ownership over all resources.
685 // As long as [CoapResource::into_inner] isn't used and we haven't given out owned
686 // CoapResource instances whose raw resource is attached to the raw context, this should
687 // never fail.
688 std::mem::take(&mut self.resources)
689 .into_iter()
690 .for_each(UntypedCoapResource::drop_inner_exclusive);
691 // SAFETY: We have already dropped all endpoints and contexts which could be freed alongside
692 // the actual context, and our raw context reference is valid (as long as the contracts of
693 // [as_mut_raw_context()] and [as_mut_context()] are fulfilled).
694 unsafe {
695 coap_free_context(self.raw_context);
696 }
697 }
698}