libcoap_rs/session/client.rs
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
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright © The libcoap-rs Contributors, all rights reserved.
* This file is part of the libcoap-rs project, see the README file for
* general information on this project and the NOTICE.md and LICENSE files
* for information regarding copyright ownership and terms of use.
*
* session/client.rs - Types relating to client-side CoAP sessions.
*/
use std::{
cell::{Ref, RefMut},
net::SocketAddr,
};
use libcoap_sys::{
coap_new_client_session, coap_proto_t_COAP_PROTO_DTLS, coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP,
coap_register_event_handler, coap_session_get_app_data, coap_session_get_context, coap_session_get_type,
coap_session_init_token, coap_session_release, coap_session_set_app_data, coap_session_t,
coap_session_type_t_COAP_SESSION_TYPE_CLIENT, coap_session_type_t_COAP_SESSION_TYPE_HELLO,
coap_session_type_t_COAP_SESSION_TYPE_NONE, coap_session_type_t_COAP_SESSION_TYPE_SERVER, COAP_TOKEN_DEFAULT_MAX,
};
use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider};
#[cfg(feature = "dtls")]
use crate::crypto::ClientCryptoContext;
use crate::{
context::CoapContext,
error::SessionCreationError,
event::event_handler_callback,
mem::{CoapFfiRcCell, DropInnerExclusively},
prng::coap_prng_try_fill,
types::CoapAddress,
};
#[derive(Debug)]
struct CoapClientSessionInner<'a> {
inner: CoapSessionInner<'a>,
#[cfg(feature = "dtls")]
// This field is actually referred to be libcoap, so it isn't actually unused.
#[allow(unused)]
crypto_ctx: Option<ClientCryptoContext<'a>>,
}
impl<'a> CoapClientSessionInner<'a> {
/// Initializes a new [`CoapClientSessionInner`] for an unencrypted session from its raw counterpart
/// with the provided initial information.
///
/// Also initializes the message token to a random value to prevent off-path response spoofing
/// (see [RFC 7252, section 5.3.1](https://datatracker.ietf.org/doc/html/rfc7252#section-5.3.1)).
///
/// # Safety
/// The provided pointer for `raw_session` must be valid and point to the newly constructed raw
/// session.
unsafe fn new(raw_session: *mut coap_session_t) -> CoapFfiRcCell<CoapClientSessionInner<'a>> {
// For insecure protocols, generate a random initial token to prevent off-path response
// spoofing, see https://datatracker.ietf.org/doc/html/rfc7252#section-5.3.1
let mut token = [0; COAP_TOKEN_DEFAULT_MAX as usize];
coap_prng_try_fill(&mut token).expect("unable to generate random initial token");
coap_session_init_token(raw_session, token.len(), token.as_ptr());
let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
inner: CoapSessionInner::new(raw_session),
#[cfg(feature = "dtls")]
crypto_ctx: None,
});
// SAFETY: raw session is valid, inner session pointer must be valid as it was just created
// from one of Rust's smart pointers.
coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
inner_session
}
/// Initializes a new [`CoapClientSessionInner`] for an encrypted session 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.
#[cfg(feature = "dtls")]
unsafe fn new_with_crypto_ctx(
raw_session: *mut coap_session_t,
crypto_ctx: ClientCryptoContext<'a>,
) -> CoapFfiRcCell<CoapClientSessionInner<'a>> {
let inner_session = CoapFfiRcCell::new(CoapClientSessionInner {
inner: CoapSessionInner::new(raw_session),
crypto_ctx: Some(crypto_ctx),
});
// SAFETY: raw session is valid, inner session pointer must be valid as it was just created
// from one of Rust's smart pointers.
coap_session_set_app_data(raw_session, inner_session.create_raw_weak());
inner_session
}
}
/// 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 `addr` using the given `crypto_ctx`.
///
/// # 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>(
ctx: &mut CoapContext<'a>,
addr: SocketAddr,
crypto_ctx: impl Into<ClientCryptoContext<'a>>,
) -> Result<CoapClientSession<'a>, SessionCreationError> {
let crypto_ctx = crypto_ctx.into();
// SAFETY: The returned raw session lives for as long as the constructed
// CoapClientSessionInner does, which is limited to the lifetime of crypto_ctx.
// When the CoapClientSessionInner instance is dropped, the session is dropped before the
// crypto context is.
let raw_session = unsafe {
match &crypto_ctx {
#[cfg(feature = "dtls-psk")]
ClientCryptoContext::Psk(psk_ctx) => {
psk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
},
#[cfg(feature = "dtls-pki")]
ClientCryptoContext::Pki(pki_ctx) => {
pki_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
},
#[cfg(feature = "dtls-rpk")]
ClientCryptoContext::Rpk(rpk_ctx) => {
rpk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)?
},
}
};
// SAFETY: raw_session was just checked to be valid pointer.
Ok(CoapClientSession {
inner: unsafe { CoapClientSessionInner::new_with_crypto_ctx(raw_session.as_ptr(), crypto_ctx) },
})
}
/// 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.
Ok(CoapClientSession {
inner: unsafe { CoapClientSessionInner::new(session) },
})
}
/// Create a new unencrypted session with the given peer over TCP.
///
/// # 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_tcp<'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_TCP,
)
};
if session.is_null() {
return Err(SessionCreationError::Unknown);
}
// SAFETY: Session was just checked for validity.
Ok(CoapClientSession {
inner: unsafe { CoapClientSessionInner::new(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, the provided session's app data must be a valid argument
/// to [`CoapFfiRawCell<CoapClientSessionInner>::clone_raw_rc`].
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);
// Variant names are named by bindgen, we have no influence on this.
// Ref: https://github.com/rust-lang/rust/issues/39371
#[allow(non_upper_case_globals)]
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"),
}
}
}
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<'_> {}