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

            
11
use std::{
12
    cell::{Ref, RefMut},
13
    net::SocketAddr,
14
};
15

            
16
#[cfg(feature = "oscore")]
17
use libcoap_sys::coap_new_client_session_oscore;
18
use 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

            
26
use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider};
27
#[cfg(feature = "dtls")]
28
use crate::crypto::ClientCryptoContext;
29
#[cfg(feature = "oscore")]
30
use crate::oscore::OscoreConf;
31
use 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)]
41
struct 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

            
49
impl<'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
70
    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
70
        let mut token = [0; COAP_TOKEN_DEFAULT_MAX as usize];
63
70
        coap_prng_try_fill(&mut token).expect("unable to generate random initial token");
64
70
        coap_session_init_token(raw_session, token.len(), token.as_ptr());
65

            
66
70
        let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
67
70
            inner: CoapSessionInner::new(raw_session),
68
70
            #[cfg(feature = "dtls")]
69
70
            crypto_ctx: None,
70
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
70
        coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
75

            
76
70
        inner_session
77
70
    }
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
163
    unsafe fn new_with_crypto_ctx(
87
163
        raw_session: *mut coap_session_t,
88
163
        crypto_ctx: ClientCryptoContext<'a>,
89
163
    ) -> CoapFfiRcCell<CoapClientSessionInner<'a>> {
90
163
        let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
91
163
            inner: CoapSessionInner::new(raw_session),
92
163
            crypto_ctx: Some(crypto_ctx),
93
163
        });
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
163
        coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
98

            
99
163
        inner_session
100
163
    }
101
}
102

            
103
/// Representation of a client-side CoAP session.
104
#[derive(Debug, Clone)]
105
pub struct CoapClientSession<'a> {
106
    inner: CoapFfiRcCell<CoapClientSessionInner<'a>>,
107
}
108

            
109
impl 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
17
    pub fn connect_dtls<'a>(
117
17
        ctx: &mut CoapContext<'a>,
118
17
        addr: SocketAddr,
119
17
        crypto_ctx: impl Into<ClientCryptoContext<'a>>,
120
17
    ) -> Result<CoapClientSession<'a>, SessionCreationError> {
121
17
        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
17
        let raw_session = unsafe {
127
17
            match &crypto_ctx {
128
                #[cfg(feature = "dtls-psk")]
129
4
                ClientCryptoContext::Psk(psk_ctx) => {
130
4
                    psk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
131
                },
132
                #[cfg(feature = "dtls-pki")]
133
11
                ClientCryptoContext::Pki(pki_ctx) => {
134
11
                    pki_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
135
                },
136
                #[cfg(feature = "dtls-rpk")]
137
2
                ClientCryptoContext::Rpk(rpk_ctx) => {
138
2
                    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
17
        Ok(CoapClientSession {
145
17
            inner: unsafe { CoapClientSessionInner::new_with_crypto_ctx(raw_session.as_ptr(), crypto_ctx) },
146
17
        })
147
17
    }
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
35
    pub fn connect_udp<'a>(
155
35
        ctx: &mut CoapContext<'a>,
156
35
        addr: SocketAddr,
157
35
    ) -> Result<CoapClientSession<'a>, SessionCreationError> {
158
        // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
159
35
        let session = unsafe {
160
35
            coap_new_client_session(
161
35
                ctx.as_mut_raw_context(),
162
35
                std::ptr::null(),
163
35
                CoapAddress::from(addr).as_raw_address(),
164
                coap_proto_t_COAP_PROTO_UDP,
165
            )
166
        };
167
35
        if session.is_null() {
168
            return Err(SessionCreationError::Unknown);
169
35
        }
170
        // SAFETY: Session was just checked for validity.
171
35
        Ok(CoapClientSession {
172
35
            inner: unsafe { CoapClientSessionInner::new(session) },
173
35
        })
174
35
    }
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
35
    pub fn connect_tcp<'a>(
182
35
        ctx: &mut CoapContext<'a>,
183
35
        addr: SocketAddr,
184
35
    ) -> Result<CoapClientSession<'a>, SessionCreationError> {
185
        // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
186
35
        let session = unsafe {
187
35
            coap_new_client_session(
188
35
                ctx.as_mut_raw_context(),
189
35
                std::ptr::null(),
190
35
                CoapAddress::from(addr).as_raw_address(),
191
                coap_proto_t_COAP_PROTO_TCP,
192
            )
193
        };
194
35
        if session.is_null() {
195
            return Err(SessionCreationError::Unknown);
196
35
        }
197
        // SAFETY: Session was just checked for validity.
198
35
        Ok(CoapClientSession {
199
35
            inner: unsafe { CoapClientSessionInner::new(session) },
200
35
        })
201
35
    }
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
396
    pub(crate) unsafe fn from_raw<'a>(raw_session: *mut coap_session_t) -> CoapClientSession<'a> {
256
396
        assert!(!raw_session.is_null(), "provided raw session was null");
257
396
        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
396
        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
396
                let raw_app_data_ptr = coap_session_get_app_data(raw_session);
265
396
                assert!(!raw_app_data_ptr.is_null(), "provided raw session has no app data");
266
396
                let inner = CoapFfiRcCell::clone_raw_rc(raw_app_data_ptr);
267
396
                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
396
    }
275
}
276

            
277
impl DropInnerExclusively for CoapClientSession<'_> {
278
    fn drop_exclusively(self) {
279
        self.inner.drop_exclusively();
280
    }
281
}
282

            
283
impl Drop for CoapClientSessionInner<'_> {
284
233
    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
233
            let app_data = coap_session_get_app_data(self.inner.raw_session);
292
233
            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
233
            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
233
            let raw_context = coap_session_get_context(self.inner.raw_session);
306
233
            assert!(!raw_context.is_null());
307
233
            coap_register_event_handler(raw_context, None);
308
            // Let libcoap do its cleanup of the raw session and free the associated memory.
309
233
            coap_session_release(self.inner.raw_session);
310
            // Restore event handler.
311
233
            coap_register_event_handler(raw_context, Some(event_handler_callback));
312
        }
313
233
    }
314
}
315

            
316
impl<'a> CoapSessionInnerProvider<'a> for CoapClientSession<'a> {
317
699
    fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>> {
318
699
        Ref::map(self.inner.borrow(), |v| &v.inner)
319
699
    }
320

            
321
2177
    fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>> {
322
2177
        RefMut::map(self.inner.borrow_mut(), |v| &mut v.inner)
323
2177
    }
324
}
325

            
326
impl<'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

            
336
impl Eq for CoapClientSession<'_> {}