libcoap_rs/
types.rs

1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * resource.rs - Types for converting between libcoap and Rust data structures.
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//! Types required for conversion between libcoap C library abstractions and Rust types.
11
12use std::fmt::{Display, Formatter};
13use std::{
14    convert::Infallible,
15    fmt::Debug,
16    mem::MaybeUninit,
17    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
18    os::raw::c_int,
19    slice::Iter,
20    str::FromStr,
21    vec::Drain,
22};
23
24use libc::{c_ushort, in6_addr, in_addr, sa_family_t, sockaddr_in, sockaddr_in6, socklen_t, AF_INET, AF_INET6};
25use num_derive::FromPrimitive;
26use num_traits::FromPrimitive;
27use url::{Host, Url};
28
29use libcoap_sys::{
30    coap_address_t, coap_mid_t, coap_proto_t,
31    coap_proto_t::{COAP_PROTO_DTLS, COAP_PROTO_NONE, COAP_PROTO_TCP, COAP_PROTO_TLS, COAP_PROTO_UDP},
32    coap_uri_scheme_t,
33    coap_uri_scheme_t::{
34        COAP_URI_SCHEME_COAP, COAP_URI_SCHEME_COAPS, COAP_URI_SCHEME_COAPS_TCP, COAP_URI_SCHEME_COAP_TCP,
35        COAP_URI_SCHEME_HTTP, COAP_URI_SCHEME_HTTPS,
36    },
37    COAP_URI_SCHEME_SECURE_MASK,
38};
39
40use crate::error::UriParsingError;
41
42/// Interface index used internally by libcoap to refer to an endpoint.
43pub type IfIndex = c_int;
44/// Value for maximum retransmits.
45pub type MaxRetransmit = c_ushort;
46/// Identifier for a CoAP message.
47pub type CoapMessageId = coap_mid_t;
48
49/// Internal wrapper for the raw coap_address_t type, mainly used for conversion between types.
50pub(crate) struct CoapAddress(coap_address_t);
51
52impl CoapAddress {
53    /// Returns a reference to the underlying raw [coap_address_t].
54    pub(crate) fn as_raw_address(&self) -> &coap_address_t {
55        &self.0
56    }
57
58    /// Returns a mutable reference to the underlying [coap_address_t].
59    ///
60    /// Because there are some invariants that must be kept with regards to the underlying
61    /// [coap_address_t], this function is unsafe.
62    /// If you want to get the coap_address_t safely, use [into_raw_address()](CoapAddress::into_raw_address()).
63    ///
64    /// # Safety
65    /// The underlying [coap_address_t] must always refer to a valid instance of sockaddr_in or
66    /// sockaddr_in6, and [coap_address_t::size] must always be the correct size of the sockaddr
67    /// in the [coap_address_t::addr] field.
68    // Kept for consistency
69    #[allow(dead_code)]
70    pub(crate) unsafe fn as_mut_raw_address(&mut self) -> &mut coap_address_t {
71        &mut self.0
72    }
73
74    /// Converts this address into the corresponding raw [coap_address_t](libcoap_sys::coap_address_t)
75    // Kept for consistency
76    #[allow(dead_code)]
77    pub(crate) fn into_raw_address(self) -> coap_address_t {
78        self.0
79    }
80}
81
82impl ToSocketAddrs for CoapAddress {
83    type Iter = std::option::IntoIter<SocketAddr>;
84
85    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
86        // SAFETY: That the underlying value of addr is a valid sockaddr is an invariant, the only
87        // way the value could be invalid is if as_mut_coap_address_t() (an unsafe function) is used
88        // incorrectly.
89        let socketaddr = match unsafe { self.0.addr.sa.as_ref().sa_family } as i32 {
90            AF_INET => {
91                // SAFETY: Validity of addr is an invariant, and we checked that the type of the
92                // underlying sockaddr is actually sockaddr_in.
93                let raw_addr = unsafe { self.0.addr.sin.as_ref() };
94                SocketAddrV4::new(
95                    Ipv4Addr::from(raw_addr.sin_addr.s_addr.to_ne_bytes()),
96                    u16::from_be(raw_addr.sin_port),
97                )
98                .into()
99            },
100            AF_INET6 => {
101                // SAFETY: Validity of addr is an invariant, and we checked that the type of the
102                // underlying sockaddr is actually sockaddr_in6.
103                let raw_addr = unsafe { self.0.addr.sin6.as_ref() };
104                SocketAddrV6::new(
105                    Ipv6Addr::from(raw_addr.sin6_addr.s6_addr),
106                    u16::from_be(raw_addr.sin6_port),
107                    raw_addr.sin6_flowinfo,
108                    raw_addr.sin6_scope_id,
109                )
110                .into()
111            },
112            // This should not happen as long as the invariants are kept.
113            _ => panic!("sa_family_t of underlying coap_address_t is invalid!"),
114        };
115        Ok(Some(socketaddr).into_iter())
116    }
117}
118
119impl From<SocketAddr> for CoapAddress {
120    fn from(addr: SocketAddr) -> Self {
121        match addr {
122            SocketAddr::V4(addr) => {
123                // addr is a bindgen-type union wrapper, so we can't assign to it directly and have
124                // to use a pointer instead.
125                // SAFETY: addr is not read before it is assigned properly, assignment cannot fail.
126                unsafe {
127                    let mut coap_addr = coap_address_t {
128                        size: std::mem::size_of::<sockaddr_in>() as socklen_t,
129                        addr: std::mem::zeroed(),
130                    };
131                    *coap_addr.addr.sin.as_mut() = sockaddr_in {
132                        sin_family: AF_INET as sa_family_t,
133                        sin_port: addr.port().to_be(),
134                        sin_addr: in_addr {
135                            s_addr: u32::from_ne_bytes(addr.ip().octets()),
136                        },
137                        sin_zero: Default::default(),
138                    };
139                    CoapAddress(coap_addr)
140                }
141            },
142            SocketAddr::V6(addr) => {
143                // addr is a bindgen-type union wrapper, so we can't assign to it directly and have
144                // to use a pointer instead.
145                // SAFETY: addr is not read before it is assigned properly, assignment cannot fail.
146                unsafe {
147                    let mut coap_addr = coap_address_t {
148                        size: std::mem::size_of::<sockaddr_in6>() as socklen_t,
149                        addr: std::mem::zeroed(),
150                    };
151                    *coap_addr.addr.sin6.as_mut() = sockaddr_in6 {
152                        sin6_family: AF_INET6 as sa_family_t,
153                        sin6_port: addr.port().to_be(),
154                        sin6_addr: in6_addr {
155                            s6_addr: addr.ip().octets(),
156                        },
157                        sin6_flowinfo: addr.flowinfo(),
158                        sin6_scope_id: addr.scope_id(),
159                    };
160                    CoapAddress(coap_addr)
161                }
162            },
163        }
164    }
165}
166
167#[doc(hidden)]
168impl From<coap_address_t> for CoapAddress {
169    fn from(raw_addr: coap_address_t) -> Self {
170        CoapAddress(raw_addr)
171    }
172}
173
174#[doc(hidden)]
175impl From<&coap_address_t> for CoapAddress {
176    fn from(raw_addr: &coap_address_t) -> Self {
177        let mut new_addr = MaybeUninit::zeroed();
178        unsafe {
179            std::ptr::copy_nonoverlapping(raw_addr, new_addr.as_mut_ptr(), 1);
180            CoapAddress(new_addr.assume_init())
181        }
182    }
183}
184
185/// Representation for a URI scheme that can be used in CoAP (proxy) requests.
186#[repr(u32)]
187#[derive(Copy, Clone, FromPrimitive, Debug, PartialEq, Eq, Hash)]
188pub enum CoapUriScheme {
189    Coap = COAP_URI_SCHEME_COAP as u32,
190    Coaps = COAP_URI_SCHEME_COAPS as u32,
191    CoapTcp = COAP_URI_SCHEME_COAP_TCP as u32,
192    CoapsTcp = COAP_URI_SCHEME_COAPS_TCP as u32,
193    Http = COAP_URI_SCHEME_HTTP as u32,
194    Https = COAP_URI_SCHEME_HTTPS as u32,
195}
196
197impl CoapUriScheme {
198    pub fn is_secure(self) -> bool {
199        COAP_URI_SCHEME_SECURE_MASK & (self as u32) > 0
200    }
201
202    pub fn from_raw_scheme(scheme: coap_uri_scheme_t) -> CoapUriScheme {
203        num_traits::FromPrimitive::from_u32(scheme as u32).expect("unknown scheme")
204    }
205}
206
207impl FromStr for CoapUriScheme {
208    type Err = UriParsingError;
209
210    fn from_str(s: &str) -> Result<Self, Self::Err> {
211        match s {
212            "coap" => Ok(CoapUriScheme::Coap),
213            "coaps" => Ok(CoapUriScheme::Coaps),
214            "coap+tcp" => Ok(CoapUriScheme::CoapTcp),
215            "coaps+tcp" => Ok(CoapUriScheme::CoapsTcp),
216            "http" => Ok(CoapUriScheme::Http),
217            "https" => Ok(CoapUriScheme::Https),
218            _ => Err(UriParsingError::NotACoapScheme),
219        }
220    }
221}
222
223impl Display for CoapUriScheme {
224    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
225        f.write_str(match self {
226            CoapUriScheme::Coap => "coap",
227            CoapUriScheme::Coaps => "coaps",
228            CoapUriScheme::CoapTcp => "coap+tcp",
229            CoapUriScheme::CoapsTcp => "coaps+tcp",
230            CoapUriScheme::Http => "http",
231            CoapUriScheme::Https => "https",
232        })
233    }
234}
235
236impl From<coap_uri_scheme_t> for CoapUriScheme {
237    fn from(scheme: coap_uri_scheme_t) -> Self {
238        CoapUriScheme::from_raw_scheme(scheme)
239    }
240}
241
242/// Representation of the host part of a CoAP request.
243#[derive(Clone, Debug, PartialEq, Eq, Hash)]
244pub enum CoapUriHost {
245    IpLiteral(IpAddr),
246    Name(String),
247}
248
249impl<T: ToString> From<url::Host<T>> for CoapUriHost {
250    fn from(host: Host<T>) -> Self {
251        match host {
252            Host::Domain(d) => CoapUriHost::Name(d.to_string()),
253            Host::Ipv4(addr) => CoapUriHost::IpLiteral(IpAddr::V4(addr)),
254            Host::Ipv6(addr) => CoapUriHost::IpLiteral(IpAddr::V6(addr)),
255        }
256    }
257}
258
259impl FromStr for CoapUriHost {
260    type Err = Infallible;
261
262    fn from_str(s: &str) -> Result<Self, Self::Err> {
263        Ok(IpAddr::from_str(s).map_or_else(|_| CoapUriHost::Name(s.to_string()), CoapUriHost::IpLiteral))
264    }
265}
266
267impl Display for CoapUriHost {
268    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
269        f.write_str(
270            match self {
271                CoapUriHost::IpLiteral(addr) => addr.to_string(),
272                CoapUriHost::Name(host) => host.clone(),
273            }
274            .as_str(),
275        )
276    }
277}
278
279/// Representation of a URI for CoAP requests or responses.
280#[derive(Clone, Debug, PartialEq, Eq, Hash)]
281pub struct CoapUri {
282    scheme: Option<CoapUriScheme>,
283    host: Option<CoapUriHost>,
284    port: Option<u16>,
285    path: Option<Vec<String>>,
286    query: Option<Vec<String>>,
287}
288
289impl CoapUri {
290    /// Creates a new CoapUri from the given components.
291    pub fn new(
292        scheme: Option<CoapUriScheme>,
293        host: Option<CoapUriHost>,
294        port: Option<u16>,
295        path: Option<Vec<String>>,
296        query: Option<Vec<String>>,
297    ) -> CoapUri {
298        CoapUri {
299            scheme,
300            host,
301            port,
302            path,
303            query,
304        }
305    }
306
307    /// Attempts to convert a [Url] into a [CoapUri].
308    ///
309    /// # Errors
310    /// May fail if the provided Url has an invalid scheme.
311    pub fn try_from_url(url: Url) -> Result<CoapUri, UriParsingError> {
312        let path: Vec<String> = url
313            .path()
314            .split('/')
315            .map(String::from)
316            .filter(|v| !v.is_empty())
317            .collect();
318        let path = if path.is_empty() { None } else { Some(path) };
319
320        let query: Vec<String> = url.query_pairs().map(|(k, v)| format!("{}={}", k, v)).collect();
321        let query = if query.is_empty() { None } else { Some(query) };
322        Ok(CoapUri {
323            scheme: Some(CoapUriScheme::from_str(url.scheme())?),
324            host: url.host().map(|h| h.into()),
325            port: url.port(),
326            path,
327            query,
328        })
329    }
330
331    /// Returns the scheme part of this URI.
332    pub fn scheme(&self) -> Option<&CoapUriScheme> {
333        self.scheme.as_ref()
334    }
335
336    /// Returns the host part of this URI.
337    pub fn host(&self) -> Option<&CoapUriHost> {
338        self.host.as_ref()
339    }
340
341    /// Returns the port of this URI (if provided).
342    pub fn port(&self) -> Option<u16> {
343        self.port
344    }
345
346    /// Drains the parts of the URI path of this CoapUri into an iterator.
347    pub(crate) fn drain_path_iter(&mut self) -> Option<Drain<String>> {
348        self.path.as_mut().map(|p| p.drain(..))
349    }
350
351    /// Returns an iterator over the path components of this URI.
352    pub fn path_iter(&self) -> Option<Iter<'_, String>> {
353        self.path.as_ref().map(|p| p.iter())
354    }
355
356    /// Drains the parts of the URI query of this CoapUri into an iterator.
357    pub fn drain_query_iter(&mut self) -> Option<Drain<String>> {
358        self.query.as_mut().map(|p| p.drain(..))
359    }
360
361    /// Returns an iterator over the query components of this URI.
362    pub fn query_iter(&self) -> Option<Iter<String>> {
363        self.query.as_ref().map(|p| p.iter())
364    }
365}
366
367impl TryFrom<Url> for CoapUri {
368    type Error = UriParsingError;
369
370    fn try_from(value: Url) -> Result<Self, Self::Error> {
371        CoapUri::try_from_url(value)
372    }
373}
374
375impl Display for CoapUri {
376    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
377        f.write_fmt(format_args!(
378            "{}{}{}{}{}",
379            self.scheme.map_or_else(String::new, |v| format!("{}://", v)),
380            self.host.as_ref().map_or_else(String::new, |v| v.to_string()),
381            self.port.map_or_else(String::new, |v| format!(":{}", v)),
382            self.path
383                .as_ref()
384                .map_or_else(String::new, |v| format!("/{}", v.join("/"))),
385            self.query
386                .as_ref()
387                .map_or_else(String::new, |v| format!("?{}", v.join("&"))),
388        ))
389    }
390}
391
392/// Transport protocols that can be used with libcoap.
393#[repr(u32)]
394#[non_exhaustive]
395#[derive(Copy, Clone, FromPrimitive, PartialEq, Eq, Hash)]
396pub enum CoapProtocol {
397    None = COAP_PROTO_NONE as u32,
398    Udp = COAP_PROTO_UDP as u32,
399    Dtls = COAP_PROTO_DTLS as u32,
400    Tcp = COAP_PROTO_TCP as u32,
401    Tls = COAP_PROTO_TLS as u32,
402}
403
404#[doc(hidden)]
405impl From<coap_proto_t> for CoapProtocol {
406    fn from(raw_proto: coap_proto_t) -> Self {
407        <CoapProtocol as FromPrimitive>::from_u32(raw_proto as u32).expect("unknown protocol")
408    }
409}
410
411impl Display for CoapProtocol {
412    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
413        f.write_str(match self {
414            CoapProtocol::None => "none",
415            CoapProtocol::Udp => "udp",
416            CoapProtocol::Dtls => "dtls",
417            CoapProtocol::Tcp => "tcp",
418            CoapProtocol::Tls => "tls",
419        })
420    }
421}
422
423fn convert_to_fixed_size_slice(n: usize, val: &[u8]) -> Box<[u8]> {
424    if val.len() > n {
425        panic!("supplied slice too short");
426    }
427    let mut buffer: Vec<u8> = vec![0; n];
428    let (_, target_buffer) = buffer.split_at_mut(n - val.len());
429    target_buffer.copy_from_slice(val);
430    buffer.truncate(n);
431    buffer.into_boxed_slice()
432}
433
434// TODO the following functions should probably return a result and use generics.
435pub(crate) fn decode_var_len_u32(val: &[u8]) -> u32 {
436    u32::from_be_bytes(
437        convert_to_fixed_size_slice(4, val)[..4]
438            .try_into()
439            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
440    )
441}
442
443pub(crate) fn encode_var_len_u32(val: u32) -> Box<[u8]> {
444    // I really hope that rust accounts for endianness here.
445    let bytes_to_discard = val.leading_zeros() / 8;
446    let mut ret_val = Vec::from(val.to_be_bytes());
447    ret_val.drain(..bytes_to_discard as usize);
448    ret_val.into_boxed_slice()
449}
450
451// Kept for consistency
452#[allow(unused)]
453pub(crate) fn decode_var_len_u64(val: &[u8]) -> u64 {
454    u64::from_be_bytes(
455        convert_to_fixed_size_slice(8, val)[..8]
456            .try_into()
457            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
458    )
459}
460
461// Kept for consistency
462#[allow(unused)]
463pub(crate) fn encode_var_len_u64(val: u64) -> Box<[u8]> {
464    // I really hope that rust accounts for endianness here.
465    let bytes_to_discard = val.leading_zeros() / 8;
466    let mut ret_val = Vec::from(val.to_be_bytes());
467    ret_val.drain(..bytes_to_discard as usize);
468    ret_val.into_boxed_slice()
469}
470
471pub(crate) fn decode_var_len_u16(val: &[u8]) -> u16 {
472    u16::from_be_bytes(
473        convert_to_fixed_size_slice(2, val)[..2]
474            .try_into()
475            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
476    )
477}
478
479pub(crate) fn encode_var_len_u16(val: u16) -> Box<[u8]> {
480    // I really hope that rust accounts for endianness here.
481    let bytes_to_discard = val.leading_zeros() / 8;
482    let mut ret_val = Vec::from(val.to_be_bytes());
483    ret_val.drain(..bytes_to_discard as usize);
484    ret_val.into_boxed_slice()
485}
486
487// Kept for consistency
488#[allow(unused)]
489pub(crate) fn decode_var_len_u8(val: &[u8]) -> u16 {
490    u16::from_be_bytes(
491        convert_to_fixed_size_slice(1, val)[..1]
492            .try_into()
493            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
494    )
495}
496
497pub(crate) fn encode_var_len_u8(val: u8) -> Box<[u8]> {
498    Vec::from([val]).into_boxed_slice()
499}