1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * session/client.rs - Types relating to client-side CoAP sessions.
4
 * This file is part of the libcoap-rs crate, see the README and LICENSE files for
5
 * more information and terms of use.
6
 * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
7
 * See the README as well as the LICENSE file for more information.
8
 */
9

            
10
use std::cell::{Ref, RefMut};
11
use std::net::SocketAddr;
12

            
13
use libcoap_sys::{
14
    coap_new_client_session, coap_proto_t, coap_register_event_handler, coap_session_get_app_data,
15
    coap_session_get_context, coap_session_get_type, coap_session_init_token, coap_session_release,
16
    coap_session_set_app_data, coap_session_t, coap_session_type_t, COAP_TOKEN_DEFAULT_MAX,
17
};
18

            
19
use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider};
20
use crate::event::event_handler_callback;
21
use crate::mem::{CoapFfiRcCell, DropInnerExclusively};
22
use crate::prng::coap_prng_try_fill;
23
use crate::{context::CoapContext, error::SessionCreationError, types::CoapAddress};
24

            
25
#[cfg(dtls)]
26
use crate::crypto::ClientCryptoContext;
27

            
28
#[derive(Debug)]
29
struct CoapClientSessionInner<'a> {
30
    inner: CoapSessionInner<'a>,
31
    #[cfg(dtls)]
32
    // This field is actually referred to be libcoap, so it isn't actually unused.
33
    #[allow(unused)]
34
    crypto_ctx: Option<ClientCryptoContext<'a>>,
35
}
36

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

            
54
70
        let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
55
70
            inner: CoapSessionInner::new(raw_session),
56
70
            #[cfg(dtls)]
57
70
            crypto_ctx: None,
58
70
        });
59
70

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

            
64
70
        inner_session
65
70
    }
66

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

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

            
87
163
        inner_session
88
163
    }
89
}
90

            
91
/// Representation of a client-side CoAP session.
92
#[derive(Debug, Clone)]
93
pub struct CoapClientSession<'a> {
94
    inner: CoapFfiRcCell<CoapClientSessionInner<'a>>,
95
}
96

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

            
131
        // SAFETY: raw_session was just checked to be valid pointer.
132
17
        Ok(CoapClientSession {
133
17
            inner: unsafe { CoapClientSessionInner::new_with_crypto_ctx(raw_session.as_ptr(), crypto_ctx) },
134
17
        })
135
17
    }
136

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

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

            
191
    /// Restores a [CoapClientSession] from its raw counterpart.
192
    ///
193
    /// Note that it is not possible to statically infer the lifetime of the created session from
194
    /// the raw pointer, i.e., the session will be created with an arbitrary lifetime.
195
    /// Therefore, callers of this function should ensure that the created session instance does not
196
    /// outlive the context it is bound to.
197
    /// Failing to do so will result in a panic/abort in the context destructor as it is unable to
198
    /// claim exclusive ownership of the client session.
199
    ///
200
    /// # Panics
201
    ///
202
    /// Panics if the given pointer is a null pointer or the raw session is not a client-side
203
    /// session with app data.
204
    ///
205
    /// # Safety
206
    /// The provided pointer must be valid, the provided session's app data must be a valid argument
207
    /// to [`CoapFfiRawCell<CoapClientSessionInner>::clone_raw_rc`].
208
396
    pub(crate) unsafe fn from_raw<'a>(raw_session: *mut coap_session_t) -> CoapClientSession<'a> {
209
396
        assert!(!raw_session.is_null(), "provided raw session was null");
210
396
        let raw_session_type = coap_session_get_type(raw_session);
211
396
        match raw_session_type {
212
            coap_session_type_t::COAP_SESSION_TYPE_NONE => panic!("provided session has no type"),
213
            coap_session_type_t::COAP_SESSION_TYPE_CLIENT => {
214
396
                let raw_app_data_ptr = coap_session_get_app_data(raw_session);
215
396
                assert!(!raw_app_data_ptr.is_null(), "provided raw session has no app data");
216
396
                let inner = CoapFfiRcCell::clone_raw_rc(raw_app_data_ptr);
217
396
                CoapClientSession { inner }
218
            },
219
            coap_session_type_t::COAP_SESSION_TYPE_SERVER | coap_session_type_t::COAP_SESSION_TYPE_HELLO => {
220
                panic!("attempted to create CoapClientSession from raw server session")
221
            },
222
            _ => unreachable!("unknown session type"),
223
        }
224
396
    }
225
}
226

            
227
impl DropInnerExclusively for CoapClientSession<'_> {
228
    fn drop_exclusively(self) {
229
        self.inner.drop_exclusively();
230
    }
231
}
232

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

            
266
impl<'a> CoapSessionInnerProvider<'a> for CoapClientSession<'a> {
267
699
    fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>> {
268
699
        Ref::map(self.inner.borrow(), |v| &v.inner)
269
699
    }
270
2177
    fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>> {
271
2177
        RefMut::map(self.inner.borrow_mut(), |v| &mut v.inner)
272
2177
    }
273
}
274

            
275
impl<'a, T: CoapSessionCommon<'a>> PartialEq<T> for CoapClientSession<'_> {
276
    fn eq(&self, other: &T) -> bool {
277
        // SAFETY: Pointers are only compared, never accessed.
278
        self.if_index() == other.if_index()
279
            && unsafe { self.raw_session() == other.raw_session() }
280
            && self.addr_local() == other.addr_local()
281
            && self.addr_remote() == other.addr_remote()
282
    }
283
}
284

            
285
impl Eq for CoapClientSession<'_> {}