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