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 libcoap_sys::coap_context_set_pki_root_cas;
16use libcoap_sys::{
17 coap_add_resource, coap_can_exit, coap_context_get_csm_max_message_size, coap_context_get_csm_timeout,
18 coap_context_get_max_handshake_sessions, coap_context_get_max_idle_sessions, coap_context_get_session_timeout,
19 coap_context_set_block_mode, coap_context_set_csm_max_message_size, coap_context_set_csm_timeout,
20 coap_context_set_keepalive, coap_context_set_max_handshake_sessions, coap_context_set_max_idle_sessions,
21 coap_context_set_session_timeout, coap_context_t, coap_event_t, coap_event_t_COAP_EVENT_BAD_PACKET,
22 coap_event_t_COAP_EVENT_DTLS_CLOSED, coap_event_t_COAP_EVENT_DTLS_CONNECTED, coap_event_t_COAP_EVENT_DTLS_ERROR,
23 coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE, coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE,
24 coap_event_t_COAP_EVENT_MSG_RETRANSMITTED, coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR,
25 coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE, coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR,
26 coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED, coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD,
27 coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY, coap_event_t_COAP_EVENT_PARTIAL_BLOCK,
28 coap_event_t_COAP_EVENT_SERVER_SESSION_DEL, coap_event_t_COAP_EVENT_SERVER_SESSION_NEW,
29 coap_event_t_COAP_EVENT_SESSION_CLOSED, coap_event_t_COAP_EVENT_SESSION_CONNECTED,
30 coap_event_t_COAP_EVENT_SESSION_FAILED, coap_event_t_COAP_EVENT_TCP_CLOSED, coap_event_t_COAP_EVENT_TCP_CONNECTED,
31 coap_event_t_COAP_EVENT_TCP_FAILED, coap_event_t_COAP_EVENT_WS_CLOSED, coap_event_t_COAP_EVENT_WS_CONNECTED,
32 coap_event_t_COAP_EVENT_WS_PACKET_SIZE, coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL, coap_free_context,
33 coap_get_app_data, coap_io_process, coap_new_context, coap_proto_t, coap_proto_t_COAP_PROTO_DTLS,
34 coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP, coap_register_event_handler,
35 coap_register_response_handler, coap_set_app_data, coap_startup_with_feature_checks, COAP_BLOCK_SINGLE_BODY,
36 COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT,
37};
38use std::ffi::CStr;
39#[cfg(feature = "dtls-pki")]
40use std::ffi::CString;
41#[cfg(feature = "dtls")]
42use std::ptr::NonNull;
43use std::{any::Any, ffi::c_void, fmt::Debug, net::SocketAddr, ops::Sub, sync::Once, time::Duration};
44#[cfg(all(feature = "dtls-pki", unix))]
45use std::{os::unix::ffi::OsStrExt, path::Path};
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, MulticastGroupJoinError},
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//TODO: New feature?
61use libcoap_sys::coap_join_mcast_group_intf;
62
63static COAP_STARTUP_ONCE: Once = Once::new();
64
65#[inline(always)]
66pub(crate) fn ensure_coap_started() {
67 COAP_STARTUP_ONCE.call_once(coap_startup_with_feature_checks);
68}
69
70#[derive(Debug)]
71struct CoapContextInner<'a> {
72 /// Reference to the raw context this context wraps around.
73 raw_context: *mut coap_context_t,
74 /// A list of endpoints that this context is currently associated with.
75 endpoints: Vec<CoapEndpoint>,
76 /// A list of resources associated with this context.
77 resources: Vec<Box<dyn UntypedCoapResource>>,
78 /// A list of server-side sessions that are currently active.
79 server_sessions: Vec<CoapServerSession<'a>>,
80 /// The event handler responsible for library-user side handling of events.
81 event_handler: Option<Box<dyn CoapEventHandler>>,
82 /// PSK context for encrypted server-side sessions.
83 #[cfg(feature = "dtls-psk")]
84 psk_context: Option<ServerPskContext<'a>>,
85 /// PKI context for encrypted server-side sessions.
86 #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
87 pki_rpk_context: Option<ServerPkiRpkCryptoContext<'a>>,
88}
89
90/// A CoAP Context — container for general state and configuration information relating to CoAP
91///
92/// The equivalent to the [coap_context_t] type in libcoap.
93#[derive(Debug)]
94pub struct CoapContext<'a> {
95 inner: CoapLendableFfiRcCell<CoapContextInner<'a>>,
96}
97
98impl<'a> CoapContext<'a> {
99 /// Creates a new context.
100 ///
101 /// # Errors
102 /// Returns an error if the underlying libcoap library was unable to create a new context
103 /// (probably an allocation error?).
104 pub fn new() -> Result<CoapContext<'a>, ContextConfigurationError> {
105 ensure_coap_started();
106 // SAFETY: Providing null here is fine, the context will just not be bound to an endpoint
107 // yet.
108 let raw_context = unsafe { coap_new_context(std::ptr::null()) };
109 if raw_context.is_null() {
110 return Err(ContextConfigurationError::Unknown);
111 }
112 // SAFETY: We checked that raw_context is not null.
113 unsafe {
114 coap_context_set_block_mode(
115 raw_context,
116 // In some versions of libcoap, bindgen infers COAP_BLOCK_USE_LIBCOAP and
117 // COAP_BLOCK_SINGLE_BODY to be u32, while the function parameter is u8.
118 // Therefore, we use `try_into()` to convert to the right type, and panic if this is
119 // not possible (should never happen)
120 (COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY)
121 .try_into()
122 .expect("coap_context_set_block_mode() flags have invalid type for function"),
123 );
124 coap_register_response_handler(raw_context, Some(session_response_handler));
125 }
126 let inner = CoapLendableFfiRcCell::new(CoapContextInner {
127 raw_context,
128 endpoints: Vec::new(),
129 resources: Vec::new(),
130 server_sessions: Vec::new(),
131 event_handler: None,
132 #[cfg(feature = "dtls-psk")]
133 psk_context: None,
134 #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
135 pki_rpk_context: None,
136 });
137
138 // SAFETY: We checked that the raw context is not null, the provided function is valid and
139 // the app data pointer provided must be valid as we just created it using
140 // `create_raw_weak_box()`.
141 unsafe {
142 coap_set_app_data(raw_context, inner.create_raw_weak_box() as *mut c_void);
143 coap_register_event_handler(raw_context, Some(event_handler_callback));
144 }
145
146 Ok(CoapContext { inner })
147 }
148
149 /// Restores a CoapContext from its raw counterpart.
150 ///
151 /// # Safety
152 /// Provided pointer must point to as valid instance of a raw context whose application data
153 /// points to a `*mut CoapLendableFfiWeakCell<CoapContextInner>`.
154 pub(crate) unsafe fn from_raw(raw_context: *mut coap_context_t) -> CoapContext<'a> {
155 assert!(!raw_context.is_null());
156 let inner = CoapLendableFfiRcCell::clone_raw_weak_box(
157 coap_get_app_data(raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>
158 );
159
160 CoapContext { inner }
161 }
162
163 /// Handle an incoming event provided by libcoap.
164 pub(crate) fn handle_event(&self, mut session: CoapSession<'a>, event: coap_event_t) {
165 let inner_ref = &mut *self.inner.borrow_mut();
166 // Call event handler for event.
167 if let Some(handler) = &mut inner_ref.event_handler {
168 // Variant names are named by bindgen, we have no influence on this.
169 // Ref: https://github.com/rust-lang/rust/issues/39371
170 #[allow(non_upper_case_globals)]
171 match event {
172 coap_event_t_COAP_EVENT_DTLS_CLOSED => handler.handle_dtls_closed(&mut session),
173 coap_event_t_COAP_EVENT_DTLS_CONNECTED => handler.handle_dtls_connected(&mut session),
174 coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE => handler.handle_dtls_renegotiate(&mut session),
175 coap_event_t_COAP_EVENT_DTLS_ERROR => handler.handle_dtls_error(&mut session),
176 coap_event_t_COAP_EVENT_TCP_CONNECTED => handler.handle_tcp_connected(&mut session),
177 coap_event_t_COAP_EVENT_TCP_CLOSED => handler.handle_tcp_closed(&mut session),
178 coap_event_t_COAP_EVENT_TCP_FAILED => handler.handle_tcp_failed(&mut session),
179 coap_event_t_COAP_EVENT_SESSION_CONNECTED => handler.handle_session_connected(&mut session),
180 coap_event_t_COAP_EVENT_SESSION_CLOSED => handler.handle_session_closed(&mut session),
181 coap_event_t_COAP_EVENT_SESSION_FAILED => handler.handle_session_failed(&mut session),
182 coap_event_t_COAP_EVENT_PARTIAL_BLOCK => handler.handle_partial_block(&mut session),
183 coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => {
184 if let CoapSession::Server(server_session) = &mut session {
185 handler.handle_server_session_new(server_session)
186 } else {
187 panic!("server-side session event fired for non-server-side session");
188 }
189 },
190 coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => {
191 if let CoapSession::Server(server_session) = &mut session {
192 handler.handle_server_session_del(server_session)
193 } else {
194 panic!("server-side session event fired for non-server-side session");
195 }
196 },
197 coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL => handler.handle_xmit_block_fail(&mut session),
198 coap_event_t_COAP_EVENT_BAD_PACKET => handler.handle_bad_packet(&mut session),
199 coap_event_t_COAP_EVENT_MSG_RETRANSMITTED => handler.handle_msg_retransmitted(&mut session),
200 coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE => {
201 handler.handle_oscore_decryption_failure(&mut session)
202 },
203 coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED => handler.handle_oscore_not_enabled(&mut session),
204 coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD => {
205 handler.handle_oscore_no_protected_payload(&mut session)
206 },
207 coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY => handler.handle_oscore_no_security(&mut session),
208 coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR => handler.handle_oscore_internal_error(&mut session),
209 coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR => handler.handle_oscore_decode_error(&mut session),
210 coap_event_t_COAP_EVENT_WS_PACKET_SIZE => handler.handle_ws_packet_size(&mut session),
211 coap_event_t_COAP_EVENT_WS_CONNECTED => handler.handle_ws_connected(&mut session),
212 coap_event_t_COAP_EVENT_WS_CLOSED => handler.handle_ws_closed(&mut session),
213 coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE => handler.handle_keepalive_failure(&mut session),
214 _ => {
215 // TODO probably a log message is justified here.
216 },
217 }
218 }
219 // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed.
220 if let CoapSession::Server(serv_sess) = session {
221 // Variant names are named by bindgen, we have no influence on this.
222 // Ref: https://github.com/rust-lang/rust/issues/39371
223 #[allow(non_upper_case_globals)]
224 match event {
225 coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => inner_ref.server_sessions.push(serv_sess),
226 coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => {
227 std::mem::drop(inner_ref.server_sessions.remove(
228 inner_ref.server_sessions.iter().position(|v| v.eq(&serv_sess)).expect(
229 "attempted to remove session wrapper from context that was never associated with it",
230 ),
231 ));
232 serv_sess.drop_exclusively();
233 },
234 _ => {},
235 }
236 }
237 }
238
239 /// Sets the server-side cryptography information provider.
240 ///
241 /// # Errors
242 ///
243 /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
244 /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
245 /// has already been set previously.
246 #[cfg(feature = "dtls-psk")]
247 pub fn set_psk_context(&mut self, psk_context: ServerPskContext<'a>) -> Result<(), ContextConfigurationError> {
248 let mut inner = self.inner.borrow_mut();
249 if inner.psk_context.is_some() {
250 return Err(ContextConfigurationError::CryptoContextAlreadySet);
251 }
252 inner.psk_context = Some(psk_context);
253 // SAFETY: raw context is valid, we ensure that an already set encryption context will not
254 // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
255 // dropped (ensuring the encryption context outlives the CoAP context).
256 unsafe {
257 inner
258 .psk_context
259 .as_ref()
260 .unwrap()
261 .apply_to_context(NonNull::new(inner.raw_context).unwrap())
262 }
263 }
264
265 /// Sets the server-side cryptography information provider.
266 ///
267 /// # Errors
268 ///
269 /// Returns [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap library
270 /// function fails and [`ContextConfigurationError::CryptoContextAlreadySet`] if the PSK context
271 /// has already been set previously.
272 #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
273 pub fn set_pki_rpk_context(
274 &mut self,
275 pki_context: impl Into<ServerPkiRpkCryptoContext<'a>>,
276 ) -> Result<(), ContextConfigurationError> {
277 let mut inner = self.inner.borrow_mut();
278 if inner.pki_rpk_context.is_some() {
279 return Err(ContextConfigurationError::CryptoContextAlreadySet);
280 }
281 inner.pki_rpk_context = Some(pki_context.into());
282 // SAFETY: raw context is valid, we ensure that an already set encryption context will not
283 // be overwritten, and the raw coap_context_t is cleaned up before the encryption context is
284 // dropped (ensuring the encryption context outlives the CoAP context).
285 unsafe {
286 inner
287 .pki_rpk_context
288 .as_ref()
289 .unwrap()
290 .apply_to_context(NonNull::new(inner.raw_context).unwrap())
291 }
292 }
293
294 /// Convenience wrapper around [`set_pki_root_cas`](CoapContext::set_pki_root_cas) that can be
295 /// provided with any type that implements `AsRef<Path>`.
296 ///
297 /// `ca_file` should be the full path of a PEM-encoded file containing all root CAs to be used
298 /// or `None`, `ca_dir` should be a directory path containing PEM-encoded CA certificates to
299 /// be used or `None`.
300 ///
301 /// As not all implementations of [`OsString`] (which is the basis of [`Path`]) provide
302 /// conversions to non-zero byte sequences, this function is only available on Unix.
303 /// On other operating systems, perform manual conversion of paths into [`CString`] and call
304 /// [`set_pki_root_cas`](CoapContext::set_pki_root_cas) directly instead.
305 ///
306 /// See the Rust standard library documentation on [FFI conversions](https://doc.rust-lang.org/std/ffi/index.html#conversions])
307 /// and the [`OsString` type](https://doc.rust-lang.org/std/ffi/struct.OsString.html) for more
308 /// information.
309 ///
310 /// # Errors
311 /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
312 /// function fails (indicating an error in either libcoap or the underlying TLS library).
313 #[cfg(all(feature = "dtls-pki", unix))]
314 pub fn set_pki_root_ca_paths(
315 &mut self,
316 ca_file: Option<impl AsRef<Path>>,
317 ca_dir: Option<impl AsRef<Path>>,
318 ) -> Result<(), ContextConfigurationError> {
319 let ca_file = ca_file.as_ref().map(|v| {
320 let v = v.as_ref();
321 assert!(v.is_file(), "attempted to set non-file as CA file for libcoap");
322 // Unix paths never contain null bytes, so we can unwrap here.
323 CString::new(v.as_os_str().as_bytes()).unwrap()
324 });
325 let ca_dir = ca_dir.as_ref().map(|v| {
326 let v = v.as_ref();
327 assert!(v.is_dir(), "attempted to set non-directory as CA directory for libcoap");
328 // Unix paths never contain null bytes, so we can unwrap here.
329 CString::new(v.as_os_str().as_bytes()).unwrap()
330 });
331
332 self.set_pki_root_cas(ca_file, ca_dir)
333 }
334
335 /// Sets the path to a CA certificate file and/or a directory of CA certificate files that
336 /// should be used as this context's default root CA information.
337 ///
338 /// `ca_file` should be the full path of a PEM-encoded file containing all root CAs to be used
339 /// or `None`, `ca_dir` should be a directory path containing PEM-encoded CA certificates to
340 /// be used or `None`.
341 ///
342 /// # Errors
343 /// Will return [`ContextConfigurationError::Unknown`] if the call to the underlying libcoap
344 /// function fails (indicating an error in either libcoap or the underlying TLS library).
345 #[cfg(feature = "dtls-pki")]
346 pub fn set_pki_root_cas(
347 &mut self,
348 ca_file: Option<CString>,
349 ca_dir: Option<CString>,
350 ) -> Result<(), ContextConfigurationError> {
351 let inner = self.inner.borrow();
352
353 let result = unsafe {
354 coap_context_set_pki_root_cas(
355 inner.raw_context,
356 ca_file.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
357 ca_dir.as_ref().map(|v| v.as_ptr()).unwrap_or(std::ptr::null()),
358 )
359 };
360 if result == 1 {
361 Ok(())
362 } else {
363 Err(ContextConfigurationError::Unknown)
364 }
365 }
366}
367
368impl CoapContext<'_> {
369 /// Performs a controlled shutdown of the CoAP context.
370 ///
371 /// This will perform all still outstanding IO operations until [coap_can_exit()] confirms that
372 /// the context has no more outstanding IO and can be dropped without interrupting sessions.
373 pub fn shutdown(mut self, exit_wait_timeout: Option<Duration>) -> Result<(), IoProcessError> {
374 let mut remaining_time = exit_wait_timeout;
375 // Send remaining packets until we can cleanly shutdown.
376 // SAFETY: Provided context is always valid as an invariant of this struct.
377 while unsafe { coap_can_exit(self.inner.borrow_mut().raw_context) } == 0 {
378 let spent_time = self.do_io(remaining_time)?;
379 remaining_time = remaining_time.map(|v| v.sub(spent_time));
380 }
381 Ok(())
382 }
383
384 /// Store reference to the endpoint
385 fn add_endpoint(&mut self, addr: SocketAddr, proto: coap_proto_t) -> Result<(), EndpointCreationError> {
386 let endpoint = CoapEndpoint::new_endpoint(self, addr, proto)?;
387
388 let mut inner_ref = self.inner.borrow_mut();
389 inner_ref.endpoints.push(endpoint);
390 Ok(())
391 }
392
393 /// Creates a new UDP endpoint that is bound to the given address.
394 pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
395 self.add_endpoint(addr, coap_proto_t_COAP_PROTO_UDP)
396 }
397
398 /// Creates a new TCP endpoint that is bound to the given address.
399 #[cfg(feature = "tcp")]
400 pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
401 self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TCP)
402 }
403
404 /// Creates a new DTLS endpoint that is bound to the given address.
405 ///
406 /// Note that in order to actually connect to DTLS clients, you need to set a crypto provider
407 /// using [CoapContext::set_psk_context] and/or [CoapContext::set_pki_rpk_context].
408 #[cfg(feature = "dtls")]
409 pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
410 self.add_endpoint(addr, coap_proto_t_COAP_PROTO_DTLS)
411 }
412
413 /// Joins a multicast group.
414 ///
415 /// `groupname` is usually a multicast IP address, while `ifname` is the used interface.
416 /// If `ifname` is set to `None`, the first appropriate interface will be chosen by the operating system.
417 pub fn join_mcast_group_intf(
418 &mut self,
419 groupname: impl AsRef<CStr>,
420 ifname: Option<&CStr>,
421 ) -> Result<(), MulticastGroupJoinError> {
422 let inner_ref = self.inner.borrow();
423 let mcast_groupname = groupname.as_ref().as_ptr();
424 let mcast_ifname = ifname.map(|v| v.as_ptr()).unwrap_or(std::ptr::null());
425 // SAFETY: `inner_ref.raw_context` is always valid by construction of this type, group and interface name are pointers to valid C strings.
426 unsafe {
427 let ret = coap_join_mcast_group_intf(inner_ref.raw_context, mcast_groupname, mcast_ifname);
428 if ret != 0 {
429 return Err(MulticastGroupJoinError::Unknown);
430 }
431 };
432 Ok(())
433 }
434
435 // /// TODO
436 // #[cfg(all(feature = "tcp", dtls))]
437 // pub fn add_endpoint_tls(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> {
438 // todo!()
439 // // TODO: self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TLS)
440 // }
441
442 /// Adds the given resource to the resource pool of this context.
443 pub fn add_resource<D: Any + ?Sized + Debug>(&mut self, res: CoapResource<D>) {
444 let mut inner_ref = self.inner.borrow_mut();
445 inner_ref.resources.push(Box::new(res));
446 // SAFETY: raw context is valid, raw resource is also guaranteed to be valid as long as
447 // contract of CoapResource is upheld.
448 unsafe {
449 coap_add_resource(
450 inner_ref.raw_context,
451 inner_ref.resources.last_mut().unwrap().raw_resource(),
452 );
453 };
454 }
455
456 /// Performs currently outstanding IO operations, waiting for a maximum duration of `timeout`.
457 ///
458 /// This is the function where most of the IO operations made using this library are actually
459 /// executed. It is recommended to call this function in a loop for as long as the CoAP context
460 /// is used.
461 pub fn do_io(&mut self, timeout: Option<Duration>) -> Result<Duration, IoProcessError> {
462 let mut inner_ref = self.inner.borrow_mut();
463 // Round up the duration if it is not a clean number of seconds.
464 let timeout = if let Some(timeout) = timeout {
465 let mut temp_timeout = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX);
466 if timeout.subsec_micros() > 0 || timeout.subsec_nanos() > 0 {
467 temp_timeout = temp_timeout.saturating_add(1);
468 }
469 temp_timeout
470 } else {
471 // If no timeout is set, wait indefinitely.
472 COAP_IO_WAIT
473 };
474 let raw_ctx_ptr = inner_ref.raw_context;
475 // Lend the current mutable reference to potential callers of CoapContext functions on the
476 // other side of the FFI barrier.
477 let lend_handle = self.inner.lend_ref_mut(&mut inner_ref);
478 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
479 // deleted until the CoapContextInner is dropped.
480 // Other raw structs used by libcoap are encapsulated in a way that they cannot be in use
481 // while in this function (considering that they are all !Send).
482 let spent_time = unsafe { coap_io_process(raw_ctx_ptr, timeout) };
483 // Demand the return of the lent handle, ensuring that the mutable reference is no longer
484 // used anywhere.
485 lend_handle.unlend();
486 // Check for errors.
487 if spent_time < 0 {
488 return Err(IoProcessError::Unknown);
489 }
490 // Return with duration of call.
491 Ok(Duration::from_millis(spent_time.unsigned_abs() as u64))
492 }
493
494 /// Return the duration that idle server-side sessions are kept alive if they are not referenced
495 /// or used anywhere else.
496 pub fn session_timeout(&self) -> Duration {
497 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
498 // deleted until the CoapContextInner is dropped.
499 let timeout = unsafe { coap_context_get_session_timeout(self.inner.borrow().raw_context) };
500 Duration::from_secs(timeout as u64)
501 }
502
503 /// Set the duration that idle server-side sessions are kept alive if they are not referenced or
504 /// used anywhere else.
505 ///
506 /// # Panics
507 /// Panics if the provided duration is too large to be provided to libcoap (larger than a
508 /// [libc::c_uint]).
509 pub fn set_session_timeout(&self, timeout: Duration) {
510 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
511 // deleted until the CoapContextInner is dropped.
512 unsafe {
513 coap_context_set_session_timeout(
514 self.inner.borrow_mut().raw_context,
515 timeout
516 .as_secs()
517 .try_into()
518 .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
519 )
520 }
521 }
522
523 /// Returns the maximum number of server-side sessions that can concurrently be in a handshake
524 /// state.
525 ///
526 /// If this number is exceeded, no new handshakes will be accepted.
527 pub fn max_handshake_sessions(&self) -> c_uint {
528 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
529 // deleted until the CoapContextInner is dropped.
530 unsafe { coap_context_get_max_handshake_sessions(self.inner.borrow().raw_context) }
531 }
532
533 /// Sets the maximum number of server-side sessions that can concurrently be in a handshake
534 /// state.
535 ///
536 /// If this number is exceeded, no new handshakes will be accepted.
537
538 pub fn set_max_handshake_sessions(&self, max_handshake_sessions: c_uint) {
539 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
540 // deleted until the CoapContextInner is dropped.
541 unsafe { coap_context_set_max_handshake_sessions(self.inner.borrow().raw_context, max_handshake_sessions) };
542 }
543
544 /// Returns the maximum number of idle server-side sessions for this context.
545 ///
546 /// If this number is exceeded, the oldest unreferenced session will be freed.
547 pub fn max_idle_sessions(&self) -> c_uint {
548 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
549 // deleted until the CoapContextInner is dropped.
550 unsafe { coap_context_get_max_idle_sessions(self.inner.borrow().raw_context) }
551 }
552
553 /// Sets the maximum number of idle server-side sessions for this context.
554 ///
555 /// If this number is exceeded, the oldest unreferenced session will be freed.
556 pub fn set_max_idle_sessions(&self, max_idle_sessions: c_uint) {
557 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
558 // deleted until the CoapContextInner is dropped.
559 unsafe { coap_context_set_max_idle_sessions(self.inner.borrow().raw_context, max_idle_sessions) };
560 }
561
562 /// Returns the maximum size for Capabilities and Settings Messages
563 ///
564 /// CSMs are used in CoAP over TCP as specified in
565 /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
566 pub fn csm_max_message_size(&self) -> u32 {
567 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
568 // deleted until the CoapContextInner is dropped.
569 unsafe { coap_context_get_csm_max_message_size(self.inner.borrow().raw_context) }
570 }
571
572 /// Sets the maximum size for Capabilities and Settings Messages
573 ///
574 /// CSMs are used in CoAP over TCP as specified in
575 /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
576 pub fn set_csm_max_message_size(&self, csm_max_message_size: u32) {
577 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
578 // deleted until the CoapContextInner is dropped.
579 unsafe { coap_context_set_csm_max_message_size(self.inner.borrow().raw_context, csm_max_message_size) };
580 }
581
582 /// Returns the timeout for Capabilities and Settings Messages
583 ///
584 /// CSMs are used in CoAP over TCP as specified in
585 /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
586 pub fn csm_timeout(&self) -> Duration {
587 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
588 // deleted until the CoapContextInner is dropped.
589 let timeout = unsafe { coap_context_get_csm_timeout(self.inner.borrow().raw_context) };
590 Duration::from_secs(timeout as u64)
591 }
592
593 /// Sets the timeout for Capabilities and Settings Messages
594 ///
595 /// CSMs are used in CoAP over TCP as specified in
596 /// [RFC 8323, Section 5.3](https://datatracker.ietf.org/doc/html/rfc8323#section-5.3).
597 ///
598 /// # Panics
599 /// Panics if the provided timeout is too large for libcoap (> [u32::MAX]).
600 pub fn set_csm_timeout(&self, csm_timeout: Duration) {
601 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
602 // deleted until the CoapContextInner is dropped.
603 unsafe {
604 coap_context_set_csm_timeout(
605 self.inner.borrow().raw_context,
606 csm_timeout
607 .as_secs()
608 .try_into()
609 .expect("provided session timeout is too large for libcoap (> u32::MAX)"),
610 )
611 };
612 }
613
614 /// Sets the number of seconds to wait before sending a CoAP keepalive message for idle
615 /// sessions.
616 ///
617 /// If the provided value is None, CoAP-level keepalive messages will be disabled.
618 ///
619 /// # Panics
620 /// Panics if the provided duration is too large to be provided to libcoap (larger than a
621 /// [libc::c_uint]).
622 pub fn set_keepalive(&self, timeout: Option<Duration>) {
623 // SAFETY: Properly initialized CoapContext always has a valid raw_context that is not
624 // deleted until the CoapContextInner is dropped.
625 unsafe {
626 coap_context_set_keepalive(
627 self.inner.borrow().raw_context,
628 timeout.map_or(0, |v| {
629 v.as_secs()
630 .try_into()
631 .expect("provided keepalive time is too large for libcoap (> c_uint)")
632 }),
633 )
634 };
635 }
636
637 /// Returns a reference to the raw context contained in this struct.
638 ///
639 /// # Safety
640 /// In general, you should not do anything that would interfere with the safe functions of this
641 /// struct.
642 /// Most notably, this includes the following:
643 /// - Associating raw resources with the context and not removing them before the context is
644 /// dropped (may cause segfaults on drop).
645 /// - Associating raw sessions that have a reference count != 0 when the CoapContext is dropped
646 /// (will cause an abort on drop)
647 /// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
648 /// cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
649 /// use anything related to the context again, but why would you do that?)
650 // Kept here for consistency, even though it is unused.
651 #[allow(unused)]
652 pub(crate) unsafe fn as_raw_context(&self) -> &coap_context_t {
653 // SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
654 // freed by anything outside of here (assuming the contract of this function is kept), and
655 // the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
656 // is).
657 &*self.inner.borrow().raw_context
658 }
659
660 /// Returns a mutable reference to the raw context contained in this struct.
661 ///
662 /// # Safety
663 /// In general, you should not do anything that would interfere with the safe functions of this
664 /// struct.
665 /// Most notably, this includes the following:
666 /// - Associating raw resources with the context and not removing them before the context is
667 /// dropped (may cause segfaults on drop).
668 /// - Associating raw sessions that have a reference count != 0 when the CoapContext is dropped
669 /// (will cause an abort on drop)
670 /// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
671 /// cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
672 /// use anything related to the context again, but why would you do that?)
673 pub(crate) unsafe fn as_mut_raw_context(&mut self) -> &mut coap_context_t {
674 // SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
675 // freed by anything outside of here (assuming the contract of this function is kept), and
676 // the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
677 // is).
678 &mut *self.inner.borrow_mut().raw_context
679 }
680
681 // TODO coap_session_get_by_peer
682}
683
684impl Drop for CoapContextInner<'_> {
685 fn drop(&mut self) {
686 // Disable event handler before dropping, as we would otherwise need to lend our reference
687 // and because calling event handlers is probably undesired when we are already dropping
688 // the context.
689 // SAFETY: Validity of our raw context is always given for the lifetime of CoapContextInner
690 // unless coap_free_context() is called during a violation of the [as_mut_raw_context()] and
691 // [as_mut_context()] contracts (we check validity of the pointer on construction).
692 // Passing a NULL handler/None to coap_register_event_handler() is allowed as per the
693 // documentation.
694 unsafe {
695 coap_register_event_handler(self.raw_context, None);
696 }
697 for session in std::mem::take(&mut self.server_sessions).into_iter() {
698 session.drop_exclusively();
699 }
700 // Clear endpoints because coap_free_context() would free their underlying raw structs.
701 self.endpoints.clear();
702 // Extract reference to CoapContextInner from raw context and drop it.
703 // SAFETY: Value is set upon construction of the inner context and never deleted.
704 unsafe {
705 std::mem::drop(CoapLendableFfiWeakCell::<CoapContextInner>::from_raw_box(
706 coap_get_app_data(self.raw_context) as *mut CoapLendableFfiWeakCell<CoapContextInner>,
707 ))
708 }
709 // Attempt to regain sole ownership over all resources.
710 // As long as [CoapResource::into_inner] isn't used and we haven't given out owned
711 // CoapResource instances whose raw resource is attached to the raw context, this should
712 // never fail.
713 std::mem::take(&mut self.resources)
714 .into_iter()
715 .for_each(UntypedCoapResource::drop_inner_exclusive);
716 // SAFETY: We have already dropped all endpoints and contexts which could be freed alongside
717 // the actual context, and our raw context reference is valid (as long as the contracts of
718 // [as_mut_raw_context()] and [as_mut_context()] are fulfilled).
719 unsafe {
720 coap_free_context(self.raw_context);
721 }
722 }
723}