1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
// SPDX-License-Identifier: BSD-2-Clause
/*
* session/client.rs - Types relating to client-side CoAP sessions.
* This file is part of the libcoap-rs crate, see the README and LICENSE files for
* more information and terms of use.
* Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
* See the README as well as the LICENSE file for more information.
*/
use std::cell::{Ref, RefMut};
use std::net::SocketAddr;
use libcoap_sys::{
coap_bin_const_t, coap_dtls_cpsk_info_t, coap_dtls_cpsk_t, coap_new_client_session, coap_new_client_session_psk2,
coap_proto_t, coap_register_event_handler, coap_session_get_app_data, coap_session_get_context,
coap_session_get_type, coap_session_release, coap_session_set_app_data, coap_session_t, coap_session_type_t,
COAP_DTLS_SPSK_SETUP_VERSION,
};
#[cfg(feature = "dtls")]
use crate::crypto::{
dtls_ih_callback, CoapClientCryptoProvider, CoapCryptoProviderResponse, CoapCryptoPskIdentity, CoapCryptoPskInfo,
};
use crate::event::event_handler_callback;
use crate::mem::{CoapFfiRcCell, DropInnerExclusively};
use crate::{context::CoapContext, error::SessionCreationError, types::CoapAddress};
use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider};
#[derive(Debug)]
struct CoapClientSessionInner<'a> {
inner: CoapSessionInner<'a>,
#[cfg(feature = "dtls")]
crypto_provider: Option<Box<dyn CoapClientCryptoProvider>>,
#[cfg(feature = "dtls")]
crypto_current_data: Option<CoapCryptoPskInfo>,
// coap_dtls_cpsk_info_t created upon calling dtls_client_ih_callback().
// The caller of the callback will make a defensive copy, so this one only has
// to be valid for a very short time and can always be overridden.
#[cfg(feature = "dtls")]
crypto_last_info_ref: coap_dtls_cpsk_info_t,
}
/// Representation of a client-side CoAP session.
#[derive(Debug, Clone)]
pub struct CoapClientSession<'a> {
inner: CoapFfiRcCell<CoapClientSessionInner<'a>>,
}
impl CoapClientSession<'_> {
/// Create a new DTLS encrypted session with the given peer.
///
/// To supply cryptographic information (like PSK hints or key data), you have to provide a
/// struct implementing [CoapClientCryptoProvider].
///
/// # Errors
/// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
/// because it was not possible to bind to a port).
#[cfg(feature = "dtls")]
pub fn connect_dtls<'a, P: 'static + CoapClientCryptoProvider>(
ctx: &mut CoapContext<'a>,
addr: SocketAddr,
mut crypto_provider: P,
) -> Result<CoapClientSession<'a>, SessionCreationError> {
// Get default identity.
let id = crypto_provider.provide_default_info();
let client_setup_data = Box::into_raw(Box::new(coap_dtls_cpsk_t {
version: COAP_DTLS_SPSK_SETUP_VERSION as u8,
reserved: [0; 7],
validate_ih_call_back: {
// Unsupported by MbedTLS
#[cfg(not(feature = "dtls_mbedtls"))]
{
Some(dtls_ih_callback)
}
#[cfg(feature = "dtls_mbedtls")]
{
None
}
},
ih_call_back_arg: std::ptr::null_mut(),
client_sni: std::ptr::null_mut(),
psk_info: coap_dtls_cpsk_info_t {
identity: coap_bin_const_t {
length: id.identity.len(),
s: id.identity.as_ptr(),
},
key: coap_bin_const_t {
length: id.key.len(),
s: id.key.as_ptr(),
},
},
}));
// SAFETY: self.raw_context is guaranteed to be valid, local_if can be null, constructed
// coap_dtls_cpsk_t is of valid format and has no out-of-bounds issues.
let raw_session = unsafe {
coap_new_client_session_psk2(
ctx.as_mut_raw_context(),
std::ptr::null(),
CoapAddress::from(addr).as_raw_address(),
coap_proto_t::COAP_PROTO_DTLS,
client_setup_data,
)
};
if raw_session.is_null() {
return Err(SessionCreationError::Unknown);
}
// SAFETY: raw_session was just checked, crypto_current_data is data provided to
// coap_new_client_session_psk2().
Ok(unsafe { CoapClientSession::new(raw_session, Some(id), Some(Box::new(crypto_provider))) })
}
/// Create a new unencrypted session with the given peer over UDP.
///
/// # Errors
/// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
/// because it was not possible to bind to a port).
pub fn connect_udp<'a>(
ctx: &mut CoapContext<'a>,
addr: SocketAddr,
) -> Result<CoapClientSession<'a>, SessionCreationError> {
// SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
let session = unsafe {
coap_new_client_session(
ctx.as_mut_raw_context(),
std::ptr::null(),
CoapAddress::from(addr).as_raw_address(),
coap_proto_t::COAP_PROTO_UDP,
)
};
if session.is_null() {
return Err(SessionCreationError::Unknown);
}
// SAFETY: Session was just checked for validity, no crypto info was provided to
// coap_new_client_session().
Ok(unsafe {
CoapClientSession::new(
session as *mut coap_session_t,
#[cfg(feature = "dtls")]
None,
#[cfg(feature = "dtls")]
None,
)
})
}
/// Initializes a new CoapClientSession from its raw counterpart with the provided initial
/// information.
///
/// # Safety
/// The provided pointer for `raw_session` must be valid and point to the newly constructed raw
/// session.
///
/// The provided value for `crypto_current_data` must be the one whose memory pointers were used
/// when calling `coap_new_client_session_*` (if any was provided).
unsafe fn new<'a>(
raw_session: *mut coap_session_t,
#[cfg(feature = "dtls")] crypto_current_data: Option<CoapCryptoPskInfo>,
#[cfg(feature = "dtls")] crypto_provider: Option<Box<dyn CoapClientCryptoProvider>>,
) -> CoapClientSession<'a> {
let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
inner: CoapSessionInner::new(raw_session),
#[cfg(feature = "dtls")]
crypto_provider,
#[cfg(feature = "dtls")]
crypto_current_data,
#[cfg(feature = "dtls")]
crypto_last_info_ref: coap_dtls_cpsk_info_t {
identity: coap_bin_const_t {
length: 0,
s: std::ptr::null(),
},
key: coap_bin_const_t {
length: 0,
s: std::ptr::null(),
},
},
});
let client_session = CoapClientSession {
inner: inner_session.clone(),
};
// SAFETY: raw session is valid, inner session pointer must be valid as it was just created
// from one of Rusts smart pointers.
coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
client_session
}
/// Restores a [CoapClientSession] from its raw counterpart.
///
/// Note that it is not possible to statically infer the lifetime of the created session from
/// the raw pointer, i.e., the session will be created with an arbitrary lifetime.
/// Therefore, callers of this function should ensure that the created session instance does not
/// outlive the context it is bound to.
/// Failing to do so will result in a panic/abort in the context destructor as it is unable to
/// claim exclusive ownership of the client session.
///
/// # Panics
/// Panics if the given pointer is a null pointer or the raw session is not a client-side
/// session with app data.
///
/// # Safety
/// The provided pointer must be valid.
pub(crate) unsafe fn from_raw<'a>(raw_session: *mut coap_session_t) -> CoapClientSession<'a> {
assert!(!raw_session.is_null(), "provided raw session was null");
let raw_session_type = coap_session_get_type(raw_session);
match raw_session_type {
coap_session_type_t::COAP_SESSION_TYPE_NONE => panic!("provided session has no type"),
coap_session_type_t::COAP_SESSION_TYPE_CLIENT => {
let raw_app_data_ptr = coap_session_get_app_data(raw_session);
assert!(!raw_app_data_ptr.is_null(), "provided raw session has no app data");
let inner = CoapFfiRcCell::clone_raw_rc(raw_app_data_ptr);
CoapClientSession { inner }
},
coap_session_type_t::COAP_SESSION_TYPE_SERVER | coap_session_type_t::COAP_SESSION_TYPE_HELLO => {
panic!("attempted to create CoapClientSession from raw server session")
},
_ => unreachable!("unknown session type"),
}
}
/// Sets the provider for cryptographic information for this session.
#[cfg(feature = "dtls")]
pub fn set_crypto_provider(&mut self, crypto_provider: Option<Box<dyn CoapClientCryptoProvider>>) {
self.inner.borrow_mut().crypto_provider = crypto_provider;
}
#[cfg(feature = "dtls")]
pub(crate) fn provide_raw_key_for_hint(
&mut self,
hint: &CoapCryptoPskIdentity,
) -> Option<*const coap_dtls_cpsk_info_t> {
let inner_ref = &mut *self.inner.borrow_mut();
match inner_ref.crypto_provider.as_mut().map(|v| v.provide_key_for_hint(hint)) {
Some(CoapCryptoProviderResponse::UseNew(new_data)) => {
inner_ref.crypto_current_data = Some(CoapCryptoPskInfo {
identity: Box::from(hint),
key: new_data,
});
inner_ref
.crypto_current_data
.as_ref()
.unwrap()
.apply_to_cpsk_info(&mut inner_ref.crypto_last_info_ref);
Some(&inner_ref.crypto_last_info_ref as *const coap_dtls_cpsk_info_t)
},
Some(CoapCryptoProviderResponse::UseCurrent) => {
if inner_ref.crypto_current_data.is_some() {
inner_ref
.crypto_current_data
.as_ref()
.unwrap()
.apply_to_cpsk_info(&mut inner_ref.crypto_last_info_ref);
Some(&inner_ref.crypto_last_info_ref as *const coap_dtls_cpsk_info_t)
} else {
None
}
},
None | Some(CoapCryptoProviderResponse::Unacceptable) => None,
}
}
}
impl DropInnerExclusively for CoapClientSession<'_> {
fn drop_exclusively(self) {
self.inner.drop_exclusively();
}
}
impl Drop for CoapClientSessionInner<'_> {
fn drop(&mut self) {
// SAFETY:
// - raw_session is always valid as long as we are not dropped yet (as this is the only
// function that calls coap_session_release on client-side sessions).
// - Application data validity is asserted.
// - For event handling access, see later comment.
unsafe {
let app_data = coap_session_get_app_data(self.inner.raw_session);
assert!(!app_data.is_null());
// Recreate weak pointer instance so that it can be dropped (which in turn reduces the
// weak reference count, avoiding memory leaks).
CoapFfiRcCell::<CoapClientSessionInner>::raw_ptr_to_weak(app_data);
// We need to temporarily disable event handling so that our own event handler does not
// access this already partially invalid session (and recursively also calls this Drop
// implementation), causing a SIGABRT.
// This is fine, because:
// - While this destructor is called, nothing is concurrently accessing the raw context
// (as libcoap is single-threaded and all types are !Send)
// - The only way this could be problematic would be if libcoap assumed sessions to be
// unchanging during a call to coap_io_process. However, this would be considered a
// bug in libcoap (as the documentation does not explicitly forbid this AFAIK).
let raw_context = coap_session_get_context(self.inner.raw_session);
assert!(!raw_context.is_null());
coap_register_event_handler(raw_context, None);
// Let libcoap do its cleanup of the raw session and free the associated memory.
coap_session_release(self.inner.raw_session);
// Restore event handler.
coap_register_event_handler(raw_context, Some(event_handler_callback));
}
}
}
impl<'a> CoapSessionInnerProvider<'a> for CoapClientSession<'a> {
fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>> {
Ref::map(self.inner.borrow(), |v| &v.inner)
}
fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>> {
RefMut::map(self.inner.borrow_mut(), |v| &mut v.inner)
}
}
impl<'a, T: CoapSessionCommon<'a>> PartialEq<T> for CoapClientSession<'_> {
fn eq(&self, other: &T) -> bool {
// SAFETY: Pointers are only compared, never accessed.
self.if_index() == other.if_index()
&& unsafe { self.raw_session() == other.raw_session() }
&& self.addr_local() == other.addr_local()
&& self.addr_remote() == other.addr_remote()
}
}
impl Eq for CoapClientSession<'_> {}