libcoap_rs/session/client.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 * session/client.rs - Types relating to client-side CoAP sessions.
9 */
10
11use std::{
12 cell::{Ref, RefMut},
13 net::SocketAddr,
14};
15
16#[cfg(feature = "oscore")]
17use libcoap_sys::coap_new_client_session_oscore;
18use libcoap_sys::{
19 coap_new_client_session, coap_proto_t_COAP_PROTO_DTLS, coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP,
20 coap_register_event_handler, coap_session_get_app_data, coap_session_get_context, coap_session_get_type,
21 coap_session_init_token, coap_session_release, coap_session_set_app_data, coap_session_t,
22 coap_session_type_t_COAP_SESSION_TYPE_CLIENT, coap_session_type_t_COAP_SESSION_TYPE_HELLO,
23 coap_session_type_t_COAP_SESSION_TYPE_NONE, coap_session_type_t_COAP_SESSION_TYPE_SERVER, COAP_TOKEN_DEFAULT_MAX,
24};
25
26use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider};
27#[cfg(feature = "dtls")]
28use crate::crypto::ClientCryptoContext;
29#[cfg(feature = "oscore")]
30use crate::oscore::OscoreConf;
31use crate::{
32 context::CoapContext,
33 error::SessionCreationError,
34 event::event_handler_callback,
35 mem::{CoapFfiRcCell, DropInnerExclusively},
36 prng::coap_prng_try_fill,
37 types::CoapAddress,
38};
39
40#[derive(Debug)]
41struct CoapClientSessionInner<'a> {
42 inner: CoapSessionInner<'a>,
43 #[cfg(feature = "dtls")]
44 // This field is actually referred to be libcoap, so it isn't actually unused.
45 #[allow(unused)]
46 crypto_ctx: Option<ClientCryptoContext<'a>>,
47}
48
49impl<'a> CoapClientSessionInner<'a> {
50 /// Initializes a new [`CoapClientSessionInner`] for an unencrypted session from its raw counterpart
51 /// with the provided initial information.
52 ///
53 /// Also initializes the message token to a random value to prevent off-path response spoofing
54 /// (see [RFC 7252, section 5.3.1](https://datatracker.ietf.org/doc/html/rfc7252#section-5.3.1)).
55 ///
56 /// # Safety
57 /// The provided pointer for `raw_session` must be valid and point to the newly constructed raw
58 /// session.
59 unsafe fn new(raw_session: *mut coap_session_t) -> CoapFfiRcCell<CoapClientSessionInner<'a>> {
60 // For insecure protocols, generate a random initial token to prevent off-path response
61 // spoofing, see https://datatracker.ietf.org/doc/html/rfc7252#section-5.3.1
62 let mut token = [0; COAP_TOKEN_DEFAULT_MAX as usize];
63 coap_prng_try_fill(&mut token).expect("unable to generate random initial token");
64 coap_session_init_token(raw_session, token.len(), token.as_ptr());
65
66 let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
67 inner: CoapSessionInner::new(raw_session),
68 #[cfg(feature = "dtls")]
69 crypto_ctx: None,
70 });
71
72 // SAFETY: raw session is valid, inner session pointer must be valid as it was just created
73 // from one of Rust's smart pointers.
74 coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
75
76 inner_session
77 }
78
79 /// Initializes a new [`CoapClientSessionInner`] for an encrypted session from its raw counterpart
80 /// with the provided initial information.
81 ///
82 /// # Safety
83 /// The provided pointer for `raw_session` must be valid and point to the newly constructed raw
84 /// session.
85 #[cfg(feature = "dtls")]
86 unsafe fn new_with_crypto_ctx(
87 raw_session: *mut coap_session_t,
88 crypto_ctx: ClientCryptoContext<'a>,
89 ) -> CoapFfiRcCell<CoapClientSessionInner<'a>> {
90 let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
91 inner: CoapSessionInner::new(raw_session),
92 crypto_ctx: Some(crypto_ctx),
93 });
94
95 // SAFETY: raw session is valid, inner session pointer must be valid as it was just created
96 // from one of Rust's smart pointers.
97 coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
98
99 inner_session
100 }
101}
102
103/// Representation of a client-side CoAP session.
104#[derive(Debug, Clone)]
105pub struct CoapClientSession<'a> {
106 inner: CoapFfiRcCell<CoapClientSessionInner<'a>>,
107}
108
109impl CoapClientSession<'_> {
110 /// Create a new DTLS encrypted session with the given peer `addr` using the given `crypto_ctx`.
111 ///
112 /// # Errors
113 /// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
114 /// because it was not possible to bind to a port).
115 #[cfg(feature = "dtls")]
116 pub fn connect_dtls<'a>(
117 ctx: &mut CoapContext<'a>,
118 addr: SocketAddr,
119 crypto_ctx: impl Into<ClientCryptoContext<'a>>,
120 ) -> Result<CoapClientSession<'a>, SessionCreationError> {
121 let crypto_ctx = crypto_ctx.into();
122 // SAFETY: The returned raw session lives for as long as the constructed
123 // CoapClientSessionInner does, which is limited to the lifetime of crypto_ctx.
124 // When the CoapClientSessionInner instance is dropped, the session is dropped before the
125 // crypto context is.
126 let raw_session = unsafe {
127 match &crypto_ctx {
128 #[cfg(feature = "dtls-psk")]
129 ClientCryptoContext::Psk(psk_ctx) => {
130 psk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
131 },
132 #[cfg(feature = "dtls-pki")]
133 ClientCryptoContext::Pki(pki_ctx) => {
134 pki_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
135 },
136 #[cfg(feature = "dtls-rpk")]
137 ClientCryptoContext::Rpk(rpk_ctx) => {
138 rpk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
139 },
140 }
141 };
142
143 // SAFETY: raw_session was just checked to be valid pointer.
144 Ok(CoapClientSession {
145 inner: unsafe { CoapClientSessionInner::new_with_crypto_ctx(raw_session.as_ptr(), crypto_ctx) },
146 })
147 }
148
149 /// Create a new unencrypted session with the given peer over UDP.
150 ///
151 /// # Errors
152 /// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
153 /// because it was not possible to bind to a port).
154 pub fn connect_udp<'a>(
155 ctx: &mut CoapContext<'a>,
156 addr: SocketAddr,
157 ) -> Result<CoapClientSession<'a>, SessionCreationError> {
158 // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
159 let session = unsafe {
160 coap_new_client_session(
161 ctx.as_mut_raw_context(),
162 std::ptr::null(),
163 CoapAddress::from(addr).as_raw_address(),
164 coap_proto_t_COAP_PROTO_UDP,
165 )
166 };
167 if session.is_null() {
168 return Err(SessionCreationError::Unknown);
169 }
170 // SAFETY: Session was just checked for validity.
171 Ok(CoapClientSession {
172 inner: unsafe { CoapClientSessionInner::new(session) },
173 })
174 }
175
176 /// Create a new unencrypted session with the given peer over TCP.
177 ///
178 /// # Errors
179 /// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
180 /// because it was not possible to bind to a port).
181 pub fn connect_tcp<'a>(
182 ctx: &mut CoapContext<'a>,
183 addr: SocketAddr,
184 ) -> Result<CoapClientSession<'a>, SessionCreationError> {
185 // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
186 let session = unsafe {
187 coap_new_client_session(
188 ctx.as_mut_raw_context(),
189 std::ptr::null(),
190 CoapAddress::from(addr).as_raw_address(),
191 coap_proto_t_COAP_PROTO_TCP,
192 )
193 };
194 if session.is_null() {
195 return Err(SessionCreationError::Unknown);
196 }
197 // SAFETY: Session was just checked for validity.
198 Ok(CoapClientSession {
199 inner: unsafe { CoapClientSessionInner::new(session) },
200 })
201 }
202
203 /// Create an encrypted session with the given peer over UDP using OSCORE.
204 ///
205 /// # Errors
206 /// Will return a [SessionCreationError] if libcoap was unable to create a session
207 /// (most likely because it was not possible to bind to a port).
208 #[cfg(feature = "oscore")]
209 pub fn connect_oscore<'a>(
210 ctx: &mut CoapContext<'a>,
211 addr: SocketAddr,
212 oscore_conf: OscoreConf,
213 ) -> Result<CoapClientSession<'a>, SessionCreationError> {
214 // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
215 // OscoreConf raw_conf should be valid, else we return an error.
216 //
217 // coap_new_client_session_oscore should free the raw_conf:
218 // [libcoap docs](https://libcoap.net/doc/reference/4.3.5/group__oscore.html#ga65ac1a57ebc037b4d14538c8e21c28a7)
219 let session = unsafe {
220 coap_new_client_session_oscore(
221 ctx.as_mut_raw_context(),
222 std::ptr::null(),
223 CoapAddress::from(addr).as_raw_address(),
224 coap_proto_t_COAP_PROTO_UDP,
225 oscore_conf.into_raw_conf().0,
226 )
227 };
228
229 if session.is_null() {
230 return Err(SessionCreationError::Unknown);
231 }
232 // SAFETY: Session was just checked for validity.
233 Ok(CoapClientSession {
234 inner: unsafe { CoapClientSessionInner::new(session) },
235 })
236 }
237
238 /// Restores a [CoapClientSession] from its raw counterpart.
239 ///
240 /// Note that it is not possible to statically infer the lifetime of the created session from
241 /// the raw pointer, i.e., the session will be created with an arbitrary lifetime.
242 /// Therefore, callers of this function should ensure that the created session instance does not
243 /// outlive the context it is bound to.
244 /// Failing to do so will result in a panic/abort in the context destructor as it is unable to
245 /// claim exclusive ownership of the client session.
246 ///
247 /// # Panics
248 ///
249 /// Panics if the given pointer is a null pointer or the raw session is not a client-side
250 /// session with app data.
251 ///
252 /// # Safety
253 /// The provided pointer must be valid, the provided session's app data must be a valid argument
254 /// to [`CoapFfiRawCell<CoapClientSessionInner>::clone_raw_rc`].
255 pub(crate) unsafe fn from_raw<'a>(raw_session: *mut coap_session_t) -> CoapClientSession<'a> {
256 assert!(!raw_session.is_null(), "provided raw session was null");
257 let raw_session_type = coap_session_get_type(raw_session);
258 // Variant names are named by bindgen, we have no influence on this.
259 // Ref: https://github.com/rust-lang/rust/issues/39371
260 #[allow(non_upper_case_globals)]
261 match raw_session_type {
262 coap_session_type_t_COAP_SESSION_TYPE_NONE => panic!("provided session has no type"),
263 coap_session_type_t_COAP_SESSION_TYPE_CLIENT => {
264 let raw_app_data_ptr = coap_session_get_app_data(raw_session);
265 assert!(!raw_app_data_ptr.is_null(), "provided raw session has no app data");
266 let inner = CoapFfiRcCell::clone_raw_rc(raw_app_data_ptr);
267 CoapClientSession { inner }
268 },
269 coap_session_type_t_COAP_SESSION_TYPE_SERVER | coap_session_type_t_COAP_SESSION_TYPE_HELLO => {
270 panic!("attempted to create CoapClientSession from raw server session")
271 },
272 _ => unreachable!("unknown session type"),
273 }
274 }
275}
276
277impl DropInnerExclusively for CoapClientSession<'_> {
278 fn drop_exclusively(self) {
279 self.inner.drop_exclusively();
280 }
281}
282
283impl Drop for CoapClientSessionInner<'_> {
284 fn drop(&mut self) {
285 // SAFETY:
286 // - raw_session is always valid as long as we are not dropped yet (as this is the only
287 // function that calls coap_session_release on client-side sessions).
288 // - Application data validity is asserted.
289 // - For event handling access, see later comment.
290 unsafe {
291 let app_data = coap_session_get_app_data(self.inner.raw_session);
292 assert!(!app_data.is_null());
293 // Recreate weak pointer instance so that it can be dropped (which in turn reduces the
294 // weak reference count, avoiding memory leaks).
295 CoapFfiRcCell::<CoapClientSessionInner>::raw_ptr_to_weak(app_data);
296 // We need to temporarily disable event handling so that our own event handler does not
297 // access this already partially invalid session (and recursively also calls this Drop
298 // implementation), causing a SIGABRT.
299 // This is fine, because:
300 // - While this destructor is called, nothing is concurrently accessing the raw context
301 // (as libcoap is single-threaded and all types are !Send)
302 // - The only way this could be problematic would be if libcoap assumed sessions to be
303 // unchanging during a call to coap_io_process. However, this would be considered a
304 // bug in libcoap (as the documentation does not explicitly forbid this AFAIK).
305 let raw_context = coap_session_get_context(self.inner.raw_session);
306 assert!(!raw_context.is_null());
307 coap_register_event_handler(raw_context, None);
308 // Let libcoap do its cleanup of the raw session and free the associated memory.
309 coap_session_release(self.inner.raw_session);
310 // Restore event handler.
311 coap_register_event_handler(raw_context, Some(event_handler_callback));
312 }
313 }
314}
315
316impl<'a> CoapSessionInnerProvider<'a> for CoapClientSession<'a> {
317 fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>> {
318 Ref::map(self.inner.borrow(), |v| &v.inner)
319 }
320
321 fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>> {
322 RefMut::map(self.inner.borrow_mut(), |v| &mut v.inner)
323 }
324}
325
326impl<'a, T: CoapSessionCommon<'a>> PartialEq<T> for CoapClientSession<'_> {
327 fn eq(&self, other: &T) -> bool {
328 // SAFETY: Pointers are only compared, never accessed.
329 self.if_index() == other.if_index()
330 && unsafe { self.raw_session() == other.raw_session() }
331 && self.addr_local() == other.addr_local()
332 && self.addr_remote() == other.addr_remote()
333 }
334}
335
336impl Eq for CoapClientSession<'_> {}