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
use libcoap_sys::{
17
    coap_new_client_session, coap_proto_t_COAP_PROTO_DTLS, coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP,
18
    coap_register_event_handler, coap_session_get_app_data, coap_session_get_context, coap_session_get_type,
19
    coap_session_init_token, coap_session_release, coap_session_set_app_data, coap_session_t,
20
    coap_session_type_t_COAP_SESSION_TYPE_CLIENT, coap_session_type_t_COAP_SESSION_TYPE_HELLO,
21
    coap_session_type_t_COAP_SESSION_TYPE_NONE, coap_session_type_t_COAP_SESSION_TYPE_SERVER, COAP_TOKEN_DEFAULT_MAX,
22
};
23

            
24
use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider};
25
#[cfg(feature = "dtls")]
26
use crate::crypto::ClientCryptoContext;
27
use crate::{
28
    context::CoapContext,
29
    error::SessionCreationError,
30
    event::event_handler_callback,
31
    mem::{CoapFfiRcCell, DropInnerExclusively},
32
    prng::coap_prng_try_fill,
33
    types::CoapAddress,
34
};
35

            
36
#[derive(Debug)]
37
struct CoapClientSessionInner<'a> {
38
    inner: CoapSessionInner<'a>,
39
    #[cfg(feature = "dtls")]
40
    // This field is actually referred to be libcoap, so it isn't actually unused.
41
    #[allow(unused)]
42
    crypto_ctx: Option<ClientCryptoContext<'a>>,
43
}
44

            
45
impl<'a> CoapClientSessionInner<'a> {
46
    /// Initializes a new [`CoapClientSessionInner`] for an unencrypted session from its raw counterpart
47
    /// with the provided initial information.
48
    ///
49
    /// Also initializes the message token to a random value to prevent off-path response spoofing
50
    /// (see [RFC 7252, section 5.3.1](https://datatracker.ietf.org/doc/html/rfc7252#section-5.3.1)).
51
    ///
52
    /// # Safety
53
    /// The provided pointer for `raw_session` must be valid and point to the newly constructed raw
54
    /// session.
55
70
    unsafe fn new(raw_session: *mut coap_session_t) -> CoapFfiRcCell<CoapClientSessionInner<'a>> {
56
70
        // For insecure protocols, generate a random initial token to prevent off-path response
57
70
        // spoofing, see https://datatracker.ietf.org/doc/html/rfc7252#section-5.3.1
58
70
        let mut token = [0; COAP_TOKEN_DEFAULT_MAX as usize];
59
70
        coap_prng_try_fill(&mut token).expect("unable to generate random initial token");
60
70
        coap_session_init_token(raw_session, token.len(), token.as_ptr());
61
70

            
62
70
        let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
63
70
            inner: CoapSessionInner::new(raw_session),
64
70
            #[cfg(feature = "dtls")]
65
70
            crypto_ctx: None,
66
70
        });
67
70

            
68
70
        // SAFETY: raw session is valid, inner session pointer must be valid as it was just created
69
70
        // from one of Rust's smart pointers.
70
70
        coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
71
70

            
72
70
        inner_session
73
70
    }
74

            
75
    /// Initializes a new [`CoapClientSessionInner`] for an encrypted session from its raw counterpart
76
    /// with the provided initial information.
77
    ///
78
    /// # Safety
79
    /// The provided pointer for `raw_session` must be valid and point to the newly constructed raw
80
    /// session.
81
    #[cfg(feature = "dtls")]
82
163
    unsafe fn new_with_crypto_ctx(
83
163
        raw_session: *mut coap_session_t,
84
163
        crypto_ctx: ClientCryptoContext<'a>,
85
163
    ) -> CoapFfiRcCell<CoapClientSessionInner<'a>> {
86
163
        let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
87
163
            inner: CoapSessionInner::new(raw_session),
88
163
            crypto_ctx: Some(crypto_ctx),
89
163
        });
90
163

            
91
163
        // SAFETY: raw session is valid, inner session pointer must be valid as it was just created
92
163
        // from one of Rust's smart pointers.
93
163
        coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
94
163

            
95
163
        inner_session
96
163
    }
97
}
98

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

            
105
impl CoapClientSession<'_> {
106
    /// Create a new DTLS encrypted session with the given peer `addr` using the given `crypto_ctx`.
107
    ///
108
    /// # Errors
109
    /// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
110
    /// because it was not possible to bind to a port).
111
    #[cfg(feature = "dtls")]
112
17
    pub fn connect_dtls<'a>(
113
17
        ctx: &mut CoapContext<'a>,
114
17
        addr: SocketAddr,
115
17
        crypto_ctx: impl Into<ClientCryptoContext<'a>>,
116
17
    ) -> Result<CoapClientSession<'a>, SessionCreationError> {
117
17
        let crypto_ctx = crypto_ctx.into();
118
        // SAFETY: The returned raw session lives for as long as the constructed
119
        // CoapClientSessionInner does, which is limited to the lifetime of crypto_ctx.
120
        // When the CoapClientSessionInner instance is dropped, the session is dropped before the
121
        // crypto context is.
122
17
        let raw_session = unsafe {
123
17
            match &crypto_ctx {
124
                #[cfg(feature = "dtls-psk")]
125
4
                ClientCryptoContext::Psk(psk_ctx) => {
126
4
                    psk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
127
                },
128
                #[cfg(feature = "dtls-pki")]
129
11
                ClientCryptoContext::Pki(pki_ctx) => {
130
11
                    pki_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
131
                },
132
                #[cfg(feature = "dtls-rpk")]
133
2
                ClientCryptoContext::Rpk(rpk_ctx) => {
134
2
                    rpk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
135
                },
136
            }
137
        };
138

            
139
        // SAFETY: raw_session was just checked to be valid pointer.
140
17
        Ok(CoapClientSession {
141
17
            inner: unsafe { CoapClientSessionInner::new_with_crypto_ctx(raw_session.as_ptr(), crypto_ctx) },
142
17
        })
143
17
    }
144

            
145
    /// Create a new unencrypted session with the given peer over UDP.
146
    ///
147
    /// # Errors
148
    /// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
149
    /// because it was not possible to bind to a port).
150
35
    pub fn connect_udp<'a>(
151
35
        ctx: &mut CoapContext<'a>,
152
35
        addr: SocketAddr,
153
35
    ) -> Result<CoapClientSession<'a>, SessionCreationError> {
154
35
        // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
155
35
        let session = unsafe {
156
35
            coap_new_client_session(
157
35
                ctx.as_mut_raw_context(),
158
35
                std::ptr::null(),
159
35
                CoapAddress::from(addr).as_raw_address(),
160
35
                coap_proto_t_COAP_PROTO_UDP,
161
35
            )
162
35
        };
163
35
        if session.is_null() {
164
            return Err(SessionCreationError::Unknown);
165
35
        }
166
35
        // SAFETY: Session was just checked for validity.
167
35
        Ok(CoapClientSession {
168
35
            inner: unsafe { CoapClientSessionInner::new(session) },
169
35
        })
170
35
    }
171

            
172
    /// Create a new unencrypted session with the given peer over TCP.
173
    ///
174
    /// # Errors
175
    /// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
176
    /// because it was not possible to bind to a port).
177
35
    pub fn connect_tcp<'a>(
178
35
        ctx: &mut CoapContext<'a>,
179
35
        addr: SocketAddr,
180
35
    ) -> Result<CoapClientSession<'a>, SessionCreationError> {
181
35
        // SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
182
35
        let session = unsafe {
183
35
            coap_new_client_session(
184
35
                ctx.as_mut_raw_context(),
185
35
                std::ptr::null(),
186
35
                CoapAddress::from(addr).as_raw_address(),
187
35
                coap_proto_t_COAP_PROTO_TCP,
188
35
            )
189
35
        };
190
35
        if session.is_null() {
191
            return Err(SessionCreationError::Unknown);
192
35
        }
193
35
        // SAFETY: Session was just checked for validity.
194
35
        Ok(CoapClientSession {
195
35
            inner: unsafe { CoapClientSessionInner::new(session) },
196
35
        })
197
35
    }
198

            
199
    /// Restores a [CoapClientSession] from its raw counterpart.
200
    ///
201
    /// Note that it is not possible to statically infer the lifetime of the created session from
202
    /// the raw pointer, i.e., the session will be created with an arbitrary lifetime.
203
    /// Therefore, callers of this function should ensure that the created session instance does not
204
    /// outlive the context it is bound to.
205
    /// Failing to do so will result in a panic/abort in the context destructor as it is unable to
206
    /// claim exclusive ownership of the client session.
207
    ///
208
    /// # Panics
209
    ///
210
    /// Panics if the given pointer is a null pointer or the raw session is not a client-side
211
    /// session with app data.
212
    ///
213
    /// # Safety
214
    /// The provided pointer must be valid, the provided session's app data must be a valid argument
215
    /// to [`CoapFfiRawCell<CoapClientSessionInner>::clone_raw_rc`].
216
396
    pub(crate) unsafe fn from_raw<'a>(raw_session: *mut coap_session_t) -> CoapClientSession<'a> {
217
396
        assert!(!raw_session.is_null(), "provided raw session was null");
218
396
        let raw_session_type = coap_session_get_type(raw_session);
219
396
        // Variant names are named by bindgen, we have no influence on this.
220
396
        // Ref: https://github.com/rust-lang/rust/issues/39371
221
396
        #[allow(non_upper_case_globals)]
222
396
        match raw_session_type {
223
            coap_session_type_t_COAP_SESSION_TYPE_NONE => panic!("provided session has no type"),
224
            coap_session_type_t_COAP_SESSION_TYPE_CLIENT => {
225
396
                let raw_app_data_ptr = coap_session_get_app_data(raw_session);
226
396
                assert!(!raw_app_data_ptr.is_null(), "provided raw session has no app data");
227
396
                let inner = CoapFfiRcCell::clone_raw_rc(raw_app_data_ptr);
228
396
                CoapClientSession { inner }
229
            },
230
            coap_session_type_t_COAP_SESSION_TYPE_SERVER | coap_session_type_t_COAP_SESSION_TYPE_HELLO => {
231
                panic!("attempted to create CoapClientSession from raw server session")
232
            },
233
            _ => unreachable!("unknown session type"),
234
        }
235
396
    }
236
}
237

            
238
impl DropInnerExclusively for CoapClientSession<'_> {
239
    fn drop_exclusively(self) {
240
        self.inner.drop_exclusively();
241
    }
242
}
243

            
244
impl Drop for CoapClientSessionInner<'_> {
245
233
    fn drop(&mut self) {
246
233
        // SAFETY:
247
233
        // - raw_session is always valid as long as we are not dropped yet (as this is the only
248
233
        //   function that calls coap_session_release on client-side sessions).
249
233
        // - Application data validity is asserted.
250
233
        // - For event handling access, see later comment.
251
233
        unsafe {
252
233
            let app_data = coap_session_get_app_data(self.inner.raw_session);
253
233
            assert!(!app_data.is_null());
254
            // Recreate weak pointer instance so that it can be dropped (which in turn reduces the
255
            // weak reference count, avoiding memory leaks).
256
233
            CoapFfiRcCell::<CoapClientSessionInner>::raw_ptr_to_weak(app_data);
257
233
            // We need to temporarily disable event handling so that our own event handler does not
258
233
            // access this already partially invalid session (and recursively also calls this Drop
259
233
            // implementation), causing a SIGABRT.
260
233
            // This is fine, because:
261
233
            // - While this destructor is called, nothing is concurrently accessing the raw context
262
233
            //   (as libcoap is single-threaded and all types are !Send)
263
233
            // - The only way this could be problematic would be if libcoap assumed sessions to be
264
233
            //   unchanging during a call to coap_io_process. However, this would be considered a
265
233
            //   bug in libcoap (as the documentation does not explicitly forbid this AFAIK).
266
233
            let raw_context = coap_session_get_context(self.inner.raw_session);
267
233
            assert!(!raw_context.is_null());
268
233
            coap_register_event_handler(raw_context, None);
269
233
            // Let libcoap do its cleanup of the raw session and free the associated memory.
270
233
            coap_session_release(self.inner.raw_session);
271
233
            // Restore event handler.
272
233
            coap_register_event_handler(raw_context, Some(event_handler_callback));
273
233
        }
274
233
    }
275
}
276

            
277
impl<'a> CoapSessionInnerProvider<'a> for CoapClientSession<'a> {
278
699
    fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>> {
279
699
        Ref::map(self.inner.borrow(), |v| &v.inner)
280
699
    }
281

            
282
2177
    fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>> {
283
2177
        RefMut::map(self.inner.borrow_mut(), |v| &mut v.inner)
284
2177
    }
285
}
286

            
287
impl<'a, T: CoapSessionCommon<'a>> PartialEq<T> for CoapClientSession<'_> {
288
    fn eq(&self, other: &T) -> bool {
289
        // SAFETY: Pointers are only compared, never accessed.
290
        self.if_index() == other.if_index()
291
            && unsafe { self.raw_session() == other.raw_session() }
292
            && self.addr_local() == other.addr_local()
293
            && self.addr_remote() == other.addr_remote()
294
    }
295
}
296

            
297
impl Eq for CoapClientSession<'_> {}