libcoap_rs/session/
mod.rs

1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * session/mod.rs - Types relating to generic 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
10use std::borrow::BorrowMut;
11use std::cell::{Ref, RefMut};
12use std::{
13    any::Any,
14    collections::{HashMap, VecDeque},
15    marker::PhantomData,
16    net::{SocketAddr, ToSocketAddrs},
17    rc::Rc,
18};
19
20use rand::Rng;
21
22use libcoap_sys::{
23    coap_context_t, coap_fixed_point_t, coap_mid_t, coap_new_message_id, coap_pdu_get_token, coap_pdu_t,
24    coap_response_t, coap_send, coap_session_get_ack_random_factor, coap_session_get_ack_timeout,
25    coap_session_get_addr_local, coap_session_get_addr_remote, coap_session_get_ifindex,
26    coap_session_get_max_retransmit, coap_session_get_proto, coap_session_get_psk_hint, coap_session_get_psk_identity,
27    coap_session_get_psk_key, coap_session_get_state, coap_session_get_type, coap_session_init_token,
28    coap_session_max_pdu_size, coap_session_new_token, coap_session_send_ping, coap_session_set_ack_random_factor,
29    coap_session_set_ack_timeout, coap_session_set_max_retransmit, coap_session_set_mtu, coap_session_state_t,
30    coap_session_t, coap_session_type_t,
31};
32
33#[cfg(feature = "dtls")]
34use crate::crypto::{CoapCryptoPskData, CoapCryptoPskIdentity};
35
36use crate::message::request::CoapRequest;
37use crate::message::response::CoapResponse;
38use crate::{
39    error::{MessageConversionError, SessionGetAppDataError},
40    message::{CoapMessage, CoapMessageCommon},
41    protocol::CoapToken,
42    types::{CoapAddress, CoapMessageId, CoapProtocol, IfIndex, MaxRetransmit},
43};
44
45pub use self::client::CoapClientSession;
46
47pub(self) use self::sealed::{CoapSessionCommonInternal, CoapSessionInnerProvider};
48
49pub use self::server::CoapServerSession;
50
51pub mod client;
52
53pub mod server;
54
55/// Representation of the states that a session can be in.
56#[repr(u32)]
57pub enum CoapSessionState {
58    None = coap_session_state_t::COAP_SESSION_STATE_NONE as u32,
59    Connecting = coap_session_state_t::COAP_SESSION_STATE_CONNECTING as u32,
60    Handshake = coap_session_state_t::COAP_SESSION_STATE_HANDSHAKE as u32,
61    Csm = coap_session_state_t::COAP_SESSION_STATE_CSM as u32,
62    Established = coap_session_state_t::COAP_SESSION_STATE_ESTABLISHED as u32,
63}
64
65impl From<coap_session_state_t> for CoapSessionState {
66    fn from(raw_state: coap_session_state_t) -> Self {
67        match raw_state {
68            coap_session_state_t::COAP_SESSION_STATE_NONE => CoapSessionState::None,
69            coap_session_state_t::COAP_SESSION_STATE_CONNECTING => CoapSessionState::Connecting,
70            coap_session_state_t::COAP_SESSION_STATE_HANDSHAKE => CoapSessionState::Handshake,
71            coap_session_state_t::COAP_SESSION_STATE_CSM => CoapSessionState::Csm,
72            coap_session_state_t::COAP_SESSION_STATE_ESTABLISHED => CoapSessionState::Established,
73            _ => unreachable!("unknown session state added"),
74        }
75    }
76}
77
78mod sealed {
79    use super::*;
80
81    /// Internal Trait for types that can provide a Ref(Mut) to CoapSessionInner instances.
82    /// Implemented by the different session types ([CoapClientSession] and [CoapServerSession])
83    pub trait CoapSessionInnerProvider<'a> {
84        /// Provide a Ref to the instance of [CoapSessionInner] contained in this type.
85        fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>>;
86
87        /// Provide a RefMut to the instance of [CoapSessionInner] contained in this type.
88        fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>>;
89    }
90
91    /// Functions common between all sessions that should not be public.
92    ///
93    /// This trait does not have any mandatory functions and will be automatically implemented for
94    /// all types that implement [CoapSessionInnerProvider].
95    pub trait CoapSessionCommonInternal<'a>: CoapSessionInnerProvider<'a> {
96        fn add_response(&self, pdu: CoapResponse) {
97            let token = pdu.token();
98            if let Some(token) = token {
99                if self.inner_ref().received_responses.contains_key(token) {
100                    self.inner_mut()
101                        .received_responses
102                        .get_mut(token)
103                        .unwrap()
104                        .push_back(pdu);
105                }
106            }
107        }
108    }
109
110    impl<'a, T: CoapSessionInnerProvider<'a>> CoapSessionCommonInternal<'a> for T {}
111}
112
113/// Trait for functions that are common between client and server sessions.
114pub trait CoapSessionCommon<'a>: CoapSessionCommonInternal<'a> {
115    /// Returns the application specific data stored alongside this session.
116    fn app_data<T: Any>(&self) -> Result<Option<Rc<T>>, SessionGetAppDataError> {
117        self.inner_ref()
118            .app_data
119            .as_ref()
120            .map(|v| v.clone().downcast().map_err(|_v| SessionGetAppDataError::WrongType))
121            .transpose()
122    }
123
124    /// Sets the application-specific data stored alongside this session.
125    fn set_app_data<T: 'static + Any>(&self, value: Option<T>) {
126        let mut inner = self.inner_mut();
127        let new_box: Option<Rc<dyn Any>> = value.map(|v| Rc::new(v) as Rc<dyn Any>);
128        inner.app_data = new_box;
129    }
130
131    /// Clears the application-specific data stored alongside this session.
132    fn clear_app_data(&self) {
133        let mut inner = self.inner_mut();
134        inner.app_data = None;
135    }
136
137    /// Returns the Ack-Random-Factor used by libcoap.
138    ///
139    /// The returned value is a tuple consisting of an integer and a fractional part, where the
140    /// fractional part is a value from 0-999 and represents the first three digits after the comma.
141    fn ack_random_factor(&self) -> (u16, u16) {
142        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
143        let random_factor = unsafe { coap_session_get_ack_random_factor(self.inner_ref().raw_session) };
144        (random_factor.integer_part, random_factor.fractional_part)
145    }
146
147    /// Sets the Ack-Random-Factor used by libcoap.
148    fn set_ack_random_factor(&self, integer_part: u16, fractional_part: u16) {
149        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
150        unsafe {
151            coap_session_set_ack_random_factor(
152                self.inner_mut().raw_session,
153                coap_fixed_point_t {
154                    integer_part,
155                    fractional_part,
156                },
157            )
158        };
159    }
160
161    /// Returns the current value of the Acknowledgement Timeout for this session (in seconds).
162    ///
163    /// The returned value is a tuple consisting of an integer and a fractional part, where the
164    /// fractional part is a value from 0-999 and represents the first three digits after the comma.
165    fn ack_timeout(&self) -> (u16, u16) {
166        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
167        let random_factor = unsafe { coap_session_get_ack_timeout(self.inner_ref().raw_session) };
168        (random_factor.integer_part, random_factor.fractional_part)
169    }
170
171    /// Sets the value of the Acknowledgement Timeout for this session.
172    fn set_ack_timeout(&self, integer_part: u16, fractional_part: u16) {
173        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
174        unsafe {
175            coap_session_set_ack_timeout(
176                self.inner_ref().raw_session,
177                coap_fixed_point_t {
178                    integer_part,
179                    fractional_part,
180                },
181            )
182        };
183    }
184
185    /// Returns the local address for this session.
186    fn addr_local(&self) -> SocketAddr {
187        CoapAddress::from(unsafe {
188            // This is infallible as long as the raw session is valid (which it always should be
189            // as long as the invariants are kept).
190            coap_session_get_addr_local(self.inner_ref().raw_session)
191                .as_ref()
192                .unwrap()
193        })
194        .to_socket_addrs()
195        .unwrap()
196        .next()
197        .unwrap()
198    }
199
200    /// Returns the remote address for this session.
201    fn addr_remote(&self) -> SocketAddr {
202        CoapAddress::from(unsafe {
203            // This is infallible as long as the raw session is valid (which it always should be
204            // as long as the invariants are kept).
205            coap_session_get_addr_remote(self.inner_ref().raw_session)
206                .as_ref()
207                .unwrap()
208        })
209        .to_socket_addrs()
210        .unwrap()
211        .next()
212        .unwrap()
213    }
214
215    /// Returns the interface index for this session.
216    fn if_index(&self) -> IfIndex {
217        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
218        unsafe { coap_session_get_ifindex(self.inner_ref().raw_session) }
219    }
220
221    /// Returns the maximum number of retransmissions for this session.
222    fn max_retransmit(&self) -> MaxRetransmit {
223        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
224        unsafe { coap_session_get_max_retransmit(self.inner_ref().raw_session) }
225    }
226
227    /// Sets the maximum number of retransmissions for this session.
228    fn set_max_retransmit(&mut self, value: MaxRetransmit) {
229        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
230        unsafe { coap_session_set_max_retransmit(self.inner_ref().raw_session, value) }
231    }
232
233    /// Returns the underlying transport protocol used for this session.
234    fn proto(&self) -> CoapProtocol {
235        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
236        unsafe { coap_session_get_proto(self.inner_ref().raw_session) }.into()
237    }
238
239    /// Returns the current PSK hint for this session.
240    #[cfg(feature = "dtls")]
241    fn psk_hint(&self) -> Option<Box<CoapCryptoPskIdentity>> {
242        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
243        unsafe {
244            coap_session_get_psk_hint(self.inner_ref().raw_session)
245                .as_ref()
246                .map(|raw_hint| Box::from(std::slice::from_raw_parts(raw_hint.s, raw_hint.length)))
247        }
248    }
249
250    /// Returns the current PSK identity for this session.
251    #[cfg(feature = "dtls")]
252    fn psk_identity(&self) -> Option<Box<CoapCryptoPskIdentity>> {
253        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
254        unsafe {
255            coap_session_get_psk_identity(self.inner_ref().raw_session)
256                .as_ref()
257                .map(|raw_hint| Box::from(std::slice::from_raw_parts(raw_hint.s, raw_hint.length)))
258        }
259    }
260
261    /// Returns the current PSK key for this session.
262    #[cfg(feature = "dtls")]
263    fn psk_key(&self) -> Option<Box<CoapCryptoPskData>> {
264        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
265        unsafe {
266            coap_session_get_psk_key(self.inner_ref().raw_session)
267                .as_ref()
268                .map(|raw_hint| Box::from(std::slice::from_raw_parts(raw_hint.s, raw_hint.length)))
269        }
270    }
271
272    /// Returns the current state of this session.
273    fn state(&self) -> CoapSessionState {
274        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
275        unsafe { coap_session_get_state(self.inner_ref().raw_session).into() }
276    }
277
278    /// Initializes the token value used by libcoap.
279    ///
280    /// Note that this function does not do anything if you are not setting the token manually using
281    /// [new_token()](CoapSessionCommon::new_token), because the wrapper will use a random number
282    /// generator to set the tokens instead.
283    fn init_token(&self, token: &[u8; 8]) {
284        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
285        unsafe { coap_session_init_token(self.inner_mut().raw_session, token.len(), token.as_ptr()) }
286    }
287
288    /// Returns the maximum size of a PDU for this session.
289    fn max_pdu_size(&self) -> usize {
290        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
291        unsafe { coap_session_max_pdu_size(self.inner_ref().raw_session) }
292    }
293
294    /// Sets the maximum size of a PDU for this session.
295    fn set_mtu(&self, mtu: u32) {
296        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
297        unsafe { coap_session_set_mtu(self.inner_mut().raw_session, mtu) }
298    }
299
300    /// Returns the next message ID that should be used for this session.
301    fn next_message_id(&self) -> CoapMessageId {
302        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
303        unsafe { coap_new_message_id(self.inner_mut().raw_session) as CoapMessageId }
304    }
305
306    /// Returns the next token that should be used for requests.
307    fn new_token(&mut self, token: &mut [u8; 8]) -> usize {
308        let mut length = 8;
309        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
310        unsafe { coap_session_new_token(self.inner_mut().raw_session, &mut length, token.as_mut_ptr()) }
311        length
312    }
313
314    /// Send a ping message to the remote peer.
315    fn send_ping(&mut self) -> CoapMessageId {
316        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner
317        unsafe { coap_session_send_ping(self.inner_mut().raw_session) }
318    }
319
320    /// Send the given message-like object to the peer.
321    ///
322    /// # Errors
323    /// Returns a [MessageConversionError] if the supplied object cannot be converted to a message.
324    fn send<P: Into<CoapMessage>>(&self, pdu: P) -> Result<CoapMessageId, MessageConversionError> {
325        let raw_pdu = pdu.into().into_raw_pdu(self)?;
326        // SAFETY: Provided session pointer being valid is an invariant of CoapSessionInner,
327        // raw pdu should be valid as we got it from `into_raw_pdu()`.
328        let mid = unsafe { coap_send(self.inner_mut().raw_session, raw_pdu) };
329        Ok(mid)
330    }
331
332    /// Sends the given CoapRequest, returning a CoapRequestHandle that can be used to poll the
333    /// request for completion.
334    ///
335    /// # Errors
336    /// Returns a [MessageConversionError] if the given Request could not be converted into a raw
337    /// message.
338    fn send_request(&self, mut req: CoapRequest) -> Result<CoapRequestHandle, MessageConversionError> {
339        if req.token().is_none() {
340            let mut token_tmp: Vec<u8> = vec![0; 8];
341            rand::thread_rng().fill(&mut token_tmp[0..8]);
342            req.set_token(Some(token_tmp))
343        }
344        let token: Box<[u8]> = Box::from(req.token().unwrap());
345        if req.mid().is_none() {
346            req.set_mid(Some(self.next_message_id()))
347        }
348        self.inner_mut()
349            .received_responses
350            .insert(token.clone(), VecDeque::new());
351        self.send(req.into_message()).map(|v| CoapRequestHandle::new(v, token))
352    }
353
354    /// Polls whether the request for the given handle already has pending responses.
355    ///
356    /// Returns an iterator over all responses associated with the request.
357    ///
358    /// # Panics
359    /// Panics if the provided handle does not refer to a valid token, i.e., because it belongs to
360    /// a different session.
361    fn poll_handle(&self, handle: &CoapRequestHandle) -> std::collections::vec_deque::IntoIter<CoapResponse> {
362        self.inner_mut()
363            .received_responses
364            .insert(handle.token.clone(), VecDeque::new())
365            .expect("Attempted to poll handle that does not refer to a valid token")
366            .into_iter()
367    }
368
369    /// Returns whether this session waits for the provided token.
370    fn is_waiting_for_token(&self, token: &CoapToken) -> bool {
371        self.inner_ref().received_responses.contains_key(token)
372    }
373
374    /// Stops listening for responses to this request handle.
375    ///
376    /// Any future responses to the request associated with this handle will be responded to with an
377    /// RST message.
378    fn remove_handle(&self, handle: CoapRequestHandle) {
379        self.inner_mut().received_responses.remove(&handle.token);
380    }
381
382    /// Returns a mutable reference to the underlying raw session.
383    ///
384    /// # Safety
385    /// Do not do anything that would interfere with the functionality of this wrapper.
386    /// Most importantly, *do not* free the session yourself.
387    unsafe fn raw_session_mut(&self) -> *mut coap_session_t {
388        self.inner_mut().raw_session
389    }
390
391    /// Returns a reference to the underlying raw session.
392    ///
393    /// # Safety
394    /// Do not do anything that would interfere with the functionality of this wrapper.
395    /// Most importantly, *do not* free the session yourself.
396    unsafe fn raw_session(&self) -> *const coap_session_t {
397        self.inner_ref().raw_session
398    }
399}
400
401impl<'a, T: CoapSessionCommonInternal<'a>> CoapSessionCommon<'a> for T {}
402
403#[derive(Debug)]
404/// The representation of a CoAP session.
405///
406/// This enum only provides functionality that is common between clients and servers (by
407/// implementing [CoapSessionCommon]). If you require functionality specific to client- or
408/// server-side sessions, match this enum on the required session type.
409pub enum CoapSession<'a> {
410    Client(CoapClientSession<'a>),
411
412    Server(CoapServerSession<'a>),
413}
414
415impl<'a> CoapSessionInnerProvider<'a> for CoapSession<'a> {
416    fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>> {
417        match self {
418            CoapSession::Client(sess) => sess.inner_ref(),
419
420            CoapSession::Server(sess) => sess.inner_ref(),
421        }
422    }
423
424    fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>> {
425        match self {
426            CoapSession::Client(sess) => sess.inner_mut(),
427
428            CoapSession::Server(sess) => sess.inner_mut(),
429        }
430    }
431}
432
433impl<'a> CoapSession<'a> {
434    /// Restores a CoapSession from its raw counterpart.
435    ///
436    /// Note that it is not possible to statically infer the lifetime of the created session from
437    /// the raw pointer, i.e., the session will be created with an arbitrary lifetime.
438    /// Therefore, callers of this function should ensure that the created session instance does not
439    /// outlive the context it is bound to.
440    /// Failing to do so will result in a panic/abort in the context destructor as it is unable to
441    /// claim exclusive ownership of the session.
442    ///
443    /// # Panics
444    /// Panics if the given pointer is a null pointer.
445    ///
446    /// # Safety
447    /// The provided pointer must be valid.
448    pub(crate) unsafe fn from_raw(raw_session: *mut coap_session_t) -> CoapSession<'a> {
449        assert!(!raw_session.is_null(), "provided raw session was null");
450        let raw_session_type = coap_session_get_type(raw_session);
451        match raw_session_type {
452            coap_session_type_t::COAP_SESSION_TYPE_NONE => panic!("provided session has no type"),
453
454            coap_session_type_t::COAP_SESSION_TYPE_CLIENT => CoapClientSession::from_raw(raw_session).into(),
455
456            coap_session_type_t::COAP_SESSION_TYPE_SERVER | coap_session_type_t::COAP_SESSION_TYPE_HELLO => {
457                CoapServerSession::from_raw(raw_session).into()
458            },
459            _ => unreachable!("unknown session type"),
460        }
461    }
462}
463
464impl<'a> From<CoapClientSession<'a>> for CoapSession<'a> {
465    fn from(session: CoapClientSession<'a>) -> Self {
466        CoapSession::Client(session)
467    }
468}
469
470impl<'a> From<CoapServerSession<'a>> for CoapSession<'a> {
471    fn from(session: CoapServerSession<'a>) -> Self {
472        CoapSession::Server(session)
473    }
474}
475
476impl PartialEq for CoapSession<'_> {
477    fn eq(&self, other: &Self) -> bool {
478        match self {
479            CoapSession::Client(cli_sess) => cli_sess.eq(other),
480
481            CoapSession::Server(srv_sess) => srv_sess.eq(other),
482        }
483    }
484}
485
486impl Eq for CoapSession<'_> {}
487
488/// Inner part of the representation of a CoapSession.
489///
490/// For internal use only, this is only public because of some limitations in Rusts type system
491/// (as we would leak a private type).
492#[derive(Debug)]
493#[doc(hidden)]
494pub struct CoapSessionInner<'a> {
495    raw_session: *mut coap_session_t,
496    app_data: Option<Rc<dyn Any>>,
497    received_responses: HashMap<CoapToken, VecDeque<CoapResponse>>,
498    _context_lifetime_marker: PhantomData<&'a coap_context_t>,
499}
500
501impl CoapSessionInner<'_> {
502    /// Initializes a new session from its raw counterpart.
503    ///
504    /// Callers of this function should probably also insert themselves into the `app_data` pointer
505    /// of the raw session to allow later retrieval (as is done for `CoapClientSession` and
506    /// `CoapServerSession`.
507    ///
508    /// # Safety
509    /// Provided pointer must point to a valid raw_session.
510    ///
511    /// Note that the chosen lifetime for the inner session is arbitraty, so you should ensure that
512    /// the CoapSessionInner instance does not outlive the underlying session.
513    /// To do so, you probably want to increase the reference counter of the session by one and
514    /// never provide values of this session with a lifetime that exceeds the one of the
515    /// [CoapContext] this session is bound to.
516    pub(crate) unsafe fn new<'a>(raw_session: *mut coap_session_t) -> CoapSessionInner<'a> {
517        CoapSessionInner {
518            raw_session,
519            app_data: None,
520            received_responses: HashMap::new(),
521            _context_lifetime_marker: Default::default(),
522        }
523    }
524}
525
526/// A handle returned by CoAP sessions upon sending a request.
527///
528/// Can be used in calls to [CoapSessionCommon::poll_handle()] to check for responses to the sent
529/// request.
530#[derive(Debug, Clone, PartialEq, Eq, Hash)]
531pub struct CoapRequestHandle {
532    _mid: CoapMessageId,
533    token: CoapToken,
534}
535
536impl CoapRequestHandle {
537    fn new<T: Into<Box<[u8]>>>(mid: CoapMessageId, token: T) -> CoapRequestHandle {
538        CoapRequestHandle {
539            _mid: mid,
540            token: token.into(),
541        }
542    }
543}
544
545// This is fine, we don't read the C-type struct, we return it.
546#[allow(improper_ctypes_definitions)]
547pub(crate) unsafe extern "C" fn session_response_handler(
548    session: *mut coap_session_t,
549    _sent: *const coap_pdu_t,
550    received: *const coap_pdu_t,
551    _id: coap_mid_t,
552) -> coap_response_t {
553    let mut session = CoapSession::from_raw(session);
554    let client = session.borrow_mut();
555    // First check if the token is actually one we are currently waiting for.
556    let raw_token = coap_pdu_get_token(received);
557    let token: CoapToken = CoapToken::from(std::slice::from_raw_parts(raw_token.s, raw_token.length));
558    if !client.is_waiting_for_token(&token) {
559        return coap_response_t::COAP_RESPONSE_FAIL;
560    }
561    if let Ok(message) = CoapMessage::from_raw_pdu(received).and_then(CoapResponse::from_message) {
562        client.add_response(message);
563        coap_response_t::COAP_RESPONSE_OK
564    } else {
565        coap_response_t::COAP_RESPONSE_FAIL
566    }
567}