libcoap_rs/
types.rs

1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * Copyright © The libcoap-rs Contributors, all rights reserved.
4 * This file is part of the libcoap-rs project, see the README file for
5 * general information on this project and the NOTICE.md and LICENSE files
6 * for information regarding copyright ownership and terms of use.
7 *
8 * resource.rs - Types for converting between libcoap and Rust data structures.
9 */
10
11//! Types required for conversion between libcoap C library abstractions and Rust types.
12
13use core::ffi::c_ushort;
14use std::{
15    ffi::{CStr, CString},
16    fmt::{Debug, Display, Formatter},
17    marker::PhantomPinned,
18    mem::MaybeUninit,
19    net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
20    os::raw::c_int,
21    pin::Pin,
22    str::FromStr,
23};
24
25use libcoap_sys::{
26    c_stdlib::{in6_addr, in_addr, sa_family_t, sockaddr_in, sockaddr_in6, socklen_t, AF_INET, AF_INET6},
27    coap_address_t, coap_delete_optlist, coap_mid_t, coap_proto_t, coap_proto_t_COAP_PROTO_DTLS,
28    coap_proto_t_COAP_PROTO_NONE, coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_TLS,
29    coap_proto_t_COAP_PROTO_UDP, coap_split_proxy_uri, coap_split_uri, coap_str_const_t, coap_string_equal,
30    coap_uri_into_optlist, coap_uri_scheme_t, coap_uri_scheme_t_COAP_URI_SCHEME_COAP,
31    coap_uri_scheme_t_COAP_URI_SCHEME_COAPS, coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_TCP,
32    coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_WS, coap_uri_scheme_t_COAP_URI_SCHEME_COAP_TCP,
33    coap_uri_scheme_t_COAP_URI_SCHEME_COAP_WS, coap_uri_scheme_t_COAP_URI_SCHEME_HTTP,
34    coap_uri_scheme_t_COAP_URI_SCHEME_HTTPS, coap_uri_t, COAP_URI_SCHEME_SECURE_MASK,
35};
36use num_derive::FromPrimitive;
37use num_traits::FromPrimitive;
38#[cfg(feature = "url")]
39use url::Url;
40
41use crate::{context::ensure_coap_started, error::UriParsingError, message::CoapOption, protocol::UriPort};
42
43/// Interface index used internally by libcoap to refer to an endpoint.
44pub type IfIndex = c_int;
45/// Value for maximum retransmits.
46pub type MaxRetransmit = c_ushort;
47/// Identifier for a CoAP message.
48pub type CoapMessageId = coap_mid_t;
49
50/// Internal wrapper for the raw coap_address_t type, mainly used for conversion between types.
51pub(crate) struct CoapAddress(coap_address_t);
52
53impl CoapAddress {
54    /// Returns a reference to the underlying raw [coap_address_t].
55    pub(crate) fn as_raw_address(&self) -> &coap_address_t {
56        &self.0
57    }
58
59    /// Returns a mutable reference to the underlying [coap_address_t].
60    ///
61    /// Because there are some invariants that must be kept with regards to the underlying
62    /// [coap_address_t], this function is unsafe.
63    /// If you want to get the coap_address_t safely, use [into_raw_address()](CoapAddress::into_raw_address()).
64    ///
65    /// # Safety
66    /// The underlying [coap_address_t] must always refer to a valid instance of sockaddr_in or
67    /// sockaddr_in6, and [coap_address_t::size] must always be the correct size of the sockaddr
68    /// in the [coap_address_t::addr] field.
69    // Kept for consistency
70    #[allow(dead_code)]
71    pub(crate) unsafe fn as_mut_raw_address(&mut self) -> &mut coap_address_t {
72        &mut self.0
73    }
74
75    /// Converts this address into the corresponding raw [coap_address_t](libcoap_sys::coap_address_t)
76    // Kept for consistency
77    #[allow(dead_code)]
78    pub(crate) fn into_raw_address(self) -> coap_address_t {
79        self.0
80    }
81}
82
83impl ToSocketAddrs for CoapAddress {
84    type Iter = std::option::IntoIter<SocketAddr>;
85
86    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
87        // SAFETY: That the underlying value of addr is a valid sockaddr is an invariant, the only
88        // way the value could be invalid is if as_mut_coap_address_t() (an unsafe function) is used
89        // incorrectly.
90        let socketaddr = match unsafe { self.0.addr.sa.sa_family as _ } {
91            AF_INET => {
92                // SAFETY: Validity of addr is an invariant, and we checked that the type of the
93                // underlying sockaddr is actually sockaddr_in.
94                let raw_addr = unsafe { self.0.addr.sin };
95                SocketAddrV4::new(
96                    Ipv4Addr::from(raw_addr.sin_addr.s_addr.to_ne_bytes()),
97                    u16::from_be(raw_addr.sin_port),
98                )
99                .into()
100            },
101            AF_INET6 => {
102                // SAFETY: Validity of addr is an invariant, and we checked that the type of the
103                // underlying sockaddr is actually sockaddr_in6.
104                let raw_addr = unsafe { self.0.addr.sin6 };
105
106                // The esp_idf_sys definition of sockaddr_in6 differs slightly.
107                #[cfg(not(target_os = "espidf"))]
108                let raw_addr_bytes = raw_addr.sin6_addr.s6_addr;
109                #[cfg(target_os = "espidf")]
110                // SAFETY: Both representations are valid.
111                let raw_addr_bytes = unsafe { raw_addr.sin6_addr.un.u8_addr };
112
113                SocketAddrV6::new(
114                    Ipv6Addr::from(raw_addr_bytes),
115                    u16::from_be(raw_addr.sin6_port),
116                    raw_addr.sin6_flowinfo,
117                    raw_addr.sin6_scope_id,
118                )
119                .into()
120            },
121            // This should not happen as long as the invariants are kept.
122            _ => panic!("sa_family_t of underlying coap_address_t is invalid!"),
123        };
124        Ok(Some(socketaddr).into_iter())
125    }
126}
127
128impl From<SocketAddr> for CoapAddress {
129    fn from(addr: SocketAddr) -> Self {
130        match addr {
131            SocketAddr::V4(addr) => {
132                // addr is a bindgen-type union wrapper, so we can't assign to it directly and have
133                // to use a pointer instead.
134                // SAFETY: addr is not read before it is assigned properly, assignment cannot fail.
135                unsafe {
136                    let mut coap_addr = coap_address_t {
137                        size: std::mem::size_of::<sockaddr_in>() as socklen_t,
138                        addr: std::mem::zeroed(),
139                    };
140
141                    coap_addr.addr.sin = sockaddr_in {
142                        #[cfg(any(
143                            target_os = "freebsd",
144                            target_os = "dragonfly",
145                            target_os = "openbsd",
146                            target_os = "netbsd",
147                            target_os = "aix",
148                            target_os = "haiku",
149                            target_os = "hurd",
150                            target_os = "espidf",
151                        ))]
152                        sin_len: (std::mem::size_of::<sockaddr_in>() as u8),
153                        sin_family: AF_INET as sa_family_t,
154                        sin_port: addr.port().to_be(),
155                        sin_addr: in_addr {
156                            s_addr: u32::from_ne_bytes(addr.ip().octets()),
157                        },
158                        sin_zero: Default::default(),
159                    };
160                    CoapAddress(coap_addr)
161                }
162            },
163            SocketAddr::V6(addr) => {
164                // addr is a bindgen-type union wrapper, so we can't assign to it directly and have
165                // to use a pointer instead.
166                // SAFETY: addr is not read before it is assigned properly, assignment cannot fail.
167                unsafe {
168                    let mut coap_addr = coap_address_t {
169                        size: std::mem::size_of::<sockaddr_in6>() as socklen_t,
170                        addr: std::mem::zeroed(),
171                    };
172
173                    // Representation of sockaddr_in6 differs depending on the used OS, therefore
174                    // some fields are a bit different.
175                    coap_addr.addr.sin6 = sockaddr_in6 {
176                        #[cfg(any(
177                            target_os = "freebsd",
178                            target_os = "dragonfly",
179                            target_os = "openbsd",
180                            target_os = "netbsd",
181                            target_os = "aix",
182                            target_os = "haiku",
183                            target_os = "hurd",
184                            target_os = "espidf",
185                        ))]
186                        sin6_len: (std::mem::size_of::<sockaddr_in6>() as u8),
187                        sin6_family: AF_INET6 as sa_family_t,
188                        sin6_port: addr.port().to_be(),
189                        sin6_addr: in6_addr {
190                            #[cfg(not(target_os = "espidf"))]
191                            s6_addr: addr.ip().octets(),
192                            #[cfg(target_os = "espidf")]
193                            un: libcoap_sys::c_stdlib::in6_addr__bindgen_ty_1 {
194                                u8_addr: addr.ip().octets(),
195                            },
196                        },
197                        sin6_flowinfo: addr.flowinfo(),
198                        sin6_scope_id: addr.scope_id(),
199                    };
200
201                    CoapAddress(coap_addr)
202                }
203            },
204        }
205    }
206}
207
208#[doc(hidden)]
209impl From<coap_address_t> for CoapAddress {
210    fn from(raw_addr: coap_address_t) -> Self {
211        CoapAddress(raw_addr)
212    }
213}
214
215#[doc(hidden)]
216impl From<&coap_address_t> for CoapAddress {
217    fn from(raw_addr: &coap_address_t) -> Self {
218        let mut new_addr = MaybeUninit::zeroed();
219        unsafe {
220            std::ptr::copy_nonoverlapping(raw_addr, new_addr.as_mut_ptr(), 1);
221            CoapAddress(new_addr.assume_init())
222        }
223    }
224}
225
226/// Representation for a URI scheme that can be used in CoAP (proxy) requests.
227#[repr(u32)]
228#[derive(Copy, Clone, FromPrimitive, Debug, PartialEq, Eq, Hash)]
229pub enum CoapUriScheme {
230    Coap = coap_uri_scheme_t_COAP_URI_SCHEME_COAP as u32,
231    Coaps = coap_uri_scheme_t_COAP_URI_SCHEME_COAPS as u32,
232    CoapTcp = coap_uri_scheme_t_COAP_URI_SCHEME_COAP_TCP as u32,
233    CoapsTcp = coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_TCP as u32,
234    Http = coap_uri_scheme_t_COAP_URI_SCHEME_HTTP as u32,
235    Https = coap_uri_scheme_t_COAP_URI_SCHEME_HTTPS as u32,
236    CoapWs = coap_uri_scheme_t_COAP_URI_SCHEME_COAP_WS as u32,
237    CoapsWs = coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_WS as u32,
238}
239
240impl CoapUriScheme {
241    pub fn is_secure(self) -> bool {
242        COAP_URI_SCHEME_SECURE_MASK & (self as u32) > 0
243    }
244
245    pub fn from_raw_scheme(scheme: coap_uri_scheme_t) -> CoapUriScheme {
246        FromPrimitive::from_u32(scheme as u32).expect("unknown scheme")
247    }
248}
249
250impl FromStr for CoapUriScheme {
251    type Err = UriParsingError;
252
253    fn from_str(s: &str) -> Result<Self, Self::Err> {
254        match s {
255            "coap" => Ok(CoapUriScheme::Coap),
256            "coaps" => Ok(CoapUriScheme::Coaps),
257            "coap+tcp" => Ok(CoapUriScheme::CoapTcp),
258            "coaps+tcp" => Ok(CoapUriScheme::CoapsTcp),
259            "http" => Ok(CoapUriScheme::Http),
260            "https" => Ok(CoapUriScheme::Https),
261            "coap+ws" => Ok(CoapUriScheme::CoapWs),
262            "coaps+ws" => Ok(CoapUriScheme::CoapsWs),
263            _ => Err(UriParsingError::NotACoapScheme(s.to_string())),
264        }
265    }
266}
267
268impl Display for CoapUriScheme {
269    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
270        f.write_str(match self {
271            CoapUriScheme::Coap => "coap",
272            CoapUriScheme::Coaps => "coaps",
273            CoapUriScheme::CoapTcp => "coap+tcp",
274            CoapUriScheme::CoapsTcp => "coaps+tcp",
275            CoapUriScheme::Http => "http",
276            CoapUriScheme::Https => "https",
277            CoapUriScheme::CoapWs => "coap+ws",
278            CoapUriScheme::CoapsWs => "coaps+ws",
279        })
280    }
281}
282
283impl From<coap_uri_scheme_t> for CoapUriScheme {
284    fn from(scheme: coap_uri_scheme_t) -> Self {
285        CoapUriScheme::from_raw_scheme(scheme)
286    }
287}
288
289impl From<CoapProtocol> for CoapUriScheme {
290    fn from(value: CoapProtocol) -> Self {
291        match value {
292            CoapProtocol::None | CoapProtocol::Udp => CoapUriScheme::Coap,
293            CoapProtocol::Dtls => CoapUriScheme::Coaps,
294            CoapProtocol::Tcp => CoapUriScheme::CoapTcp,
295            CoapProtocol::Tls => CoapUriScheme::CoapsTcp,
296        }
297    }
298}
299
300/// Representation of a URI for CoAP requests, responses or proxy URIs.
301///
302/// See https://datatracker.ietf.org/doc/html/rfc7252#section-6 for a description of how a URI
303/// should look like.
304///
305/// # Examples
306/// The easiest way to instantiate a request or location CoAP URI is by parsing a string (either
307/// using the [FromStr] implementation or using [CoapUri::try_from_str]):
308/// ```
309/// use libcoap_rs::error::UriParsingError;
310/// use libcoap_rs::types::{CoapUri, CoapUriScheme};
311///
312/// let uri: CoapUri = "coap://example.com:4711/foo/bar?answer=42".parse()?;
313///
314/// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
315/// assert_eq!(uri.host(), Some("example.com".as_bytes()));
316/// assert_eq!(uri.port(), Some(4711));
317/// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
318/// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
319/// assert!(!uri.is_proxy());
320///
321/// # Result::<(), UriParsingError>::Ok(())
322/// ```
323///
324/// Alternatively, a [CoapUri] may be constructed from its parts using [CoapUri::new] or
325/// [CoapUri::new_relative] or from a [Url] (requires the `url` feature), refer to the method level
326/// documentation for more information.
327///
328/// If you want to create a proxy URI, refer to the method-level documentation [CoapUri::new_proxy],
329/// [CoapUri::try_from_str_proxy] or [CoapUri::try_from_url_proxy].
330///
331/// # Note on URI Length Limits
332///
333/// Due to [the specified limits](https://datatracker.ietf.org/doc/html/rfc7252#section-5.10)
334/// of CoAP option lengths, the URI path components, query components, and hostnames for a URI must not
335/// exceed 255 bytes each, i.e. a full path with more than 255 bytes is fine, but each individual
336/// path segment must be smaller than 255 bytes.
337///
338/// For proxy URIs, there is a length limit of 255 bytes for the scheme.
339/// As we use the Uri-* options for encoding proxy URIs instead of the Proxy-Uri option (as
340/// specified in [RFC 7252, section 5.10.2](https://datatracker.ietf.org/doc/html/rfc7252#section-5.10.2)),
341/// the above limits regarding path and query components also apply here.
342#[derive(Debug)]
343pub struct CoapUri {
344    is_proxy: bool,
345    raw_uri: coap_uri_t,
346    uri_str: Pin<Box<CoapUriInner>>,
347}
348
349#[derive(Debug)]
350struct CoapUriInner(CString, PhantomPinned);
351
352impl CoapUri {
353    /// Creates a new [CoapUri] for use as a request or location URI from its constituent parts.
354    ///
355    /// # Errors
356    /// May fail if the provided fields do not represent a valid relative URI or if the arguments
357    /// exceed maximum lengths (see the struct level documentation).
358    ///
359    /// # Examples
360    /// ```
361    /// use libcoap_rs::error::UriParsingError;
362    /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
363    ///
364    /// let uri: CoapUri = CoapUri::new(
365    ///     CoapUriScheme::Coap,
366    ///     "example.com".as_bytes(),
367    ///     4711,
368    ///     Some("/foo/bar".as_bytes()),
369    ///     Some("?answer=42".as_bytes())
370    /// )?;
371    ///
372    /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
373    /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
374    /// assert_eq!(uri.port(), Some(4711));
375    /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
376    /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
377    /// assert!(!uri.is_proxy());
378    ///
379    /// # Result::<(), UriParsingError>::Ok(())
380    /// ```
381    pub fn new(
382        scheme: CoapUriScheme,
383        host: &[u8],
384        port: u16,
385        path: Option<&[u8]>,
386        query: Option<&[u8]>,
387    ) -> Result<CoapUri, UriParsingError> {
388        let (uri_str, _, _, _) =
389            Self::construct_uri_string_from_parts(scheme, host, port, path.unwrap_or(&[b'/']), query.unwrap_or(&[]))?;
390        // SAFETY: coap_split_uri is one of the allowed functions.
391        unsafe { CoapUri::create_parsed_uri(uri_str, coap_split_uri, false) }
392    }
393
394    /// Creates a new [CoapUri] for use as a proxy URI from its constituent parts.
395    ///
396    /// # Errors
397    /// May fail if the provided fields do not represent a valid relative URI or if the arguments
398    /// exceed maximum lengths (see the struct level documentation).
399    /// # Examples
400    /// ```
401    /// use libcoap_rs::error::UriParsingError;
402    /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
403    ///
404    /// let uri: CoapUri = CoapUri::new_proxy(
405    ///     CoapUriScheme::Coap,
406    ///     "example.com".as_bytes(),
407    ///     4711,
408    ///     Some("/foo/bar".as_bytes()),
409    ///     Some("?answer=42".as_bytes())
410    /// )?;
411    ///
412    /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
413    /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
414    /// assert_eq!(uri.port(), Some(4711));
415    /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
416    /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
417    /// assert!(uri.is_proxy());
418    ///
419    /// # Result::<(), UriParsingError>::Ok(())
420    /// ```
421    pub fn new_proxy(
422        scheme: CoapUriScheme,
423        host: &[u8],
424        port: u16,
425        path: Option<&[u8]>,
426        query: Option<&[u8]>,
427    ) -> Result<CoapUri, UriParsingError> {
428        let (uri_str, _, _, _) =
429            Self::construct_uri_string_from_parts(scheme, host, port, path.unwrap_or(&[b'/']), query.unwrap_or(&[]))?;
430        // SAFETY: coap_split_proxy_uri is one of the allowed functions.
431        unsafe { CoapUri::create_parsed_uri(uri_str, coap_split_proxy_uri, true) }
432    }
433
434    /// Attempts to convert the provided `path` and `query` into a relative [CoapUri] suitable as a
435    /// request/location URI.
436    ///
437    /// # Errors
438    /// May fail if the provided `path` and `query` do not represent a valid relative URI or if the
439    /// arguments exceed maximum lengths (see the struct level documentation).
440    ///
441    /// # Examples
442    /// ```
443    /// use libcoap_rs::error::UriParsingError;
444    /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
445    ///
446    /// let uri: CoapUri = CoapUri::new_relative(
447    ///     Some("/foo/bar".as_bytes()),
448    ///     Some("?answer=42".as_bytes())
449    /// )?;
450    ///
451    /// assert_eq!(uri.scheme(), None);
452    /// assert_eq!(uri.host(), None);
453    /// assert_eq!(uri.port(), Some(5683));
454    /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
455    /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
456    /// assert!(!uri.is_proxy());
457    ///
458    /// # Result::<(), UriParsingError>::Ok(())
459    /// ```
460    pub fn new_relative(path: Option<&[u8]>, query: Option<&[u8]>) -> Result<CoapUri, UriParsingError> {
461        CoapUri::new(CoapUriScheme::Coap, &[], 0, path, query)
462    }
463
464    /// Attempts to convert the provided `uri_str` into a [CoapUri] suitable as a request/location
465    /// URI.
466    ///
467    /// # Errors
468    /// May fail if the provided `uri_str` is not a valid URI or if the URI components exceed
469    /// maximum lengths (see the struct level documentation).
470    ///
471    /// # Examples
472    /// ```
473    /// use libcoap_rs::error::UriParsingError;
474    /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
475    ///
476    /// let uri: CoapUri = CoapUri::try_from_str("coap://example.com:4711/foo/bar?answer=42")?;
477    ///
478    /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
479    /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
480    /// assert_eq!(uri.port(), Some(4711));
481    /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
482    /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
483    /// assert!(!uri.is_proxy());
484    ///
485    /// # Result::<(), UriParsingError>::Ok(())
486    /// ```
487    pub fn try_from_str(uri_str: &str) -> Result<CoapUri, UriParsingError> {
488        // SAFETY: coap_split_uri is one of the allowed functions.
489        unsafe { CoapUri::create_parsed_uri(CString::new(uri_str)?, coap_split_uri, false) }
490    }
491
492    /// Attempts to convert the provided `uri_str` into a [CoapUri] suitable as a proxy URI.
493    ///
494    /// # Errors
495    /// May fail if the provided `uri_str` is not a valid proxy URI or if the URI components exceed
496    /// maximum lengths (see the struct level documentation).
497    ///
498    /// # Examples
499    /// ```
500    /// use libcoap_rs::error::UriParsingError;
501    /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
502    ///
503    /// let uri: CoapUri = CoapUri::try_from_str_proxy("coap://example.com:4711/foo/bar?answer=42")?;
504    ///
505    /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
506    /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
507    /// assert_eq!(uri.port(), Some(4711));
508    /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
509    /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
510    /// assert!(uri.is_proxy());
511    ///
512    /// # Result::<(), UriParsingError>::Ok(())
513    /// ```
514    pub fn try_from_str_proxy(uri_str: &str) -> Result<CoapUri, UriParsingError> {
515        // SAFETY: coap_split_proxy_uri is one of the allowed functions.
516        unsafe { CoapUri::create_parsed_uri(CString::new(uri_str)?, coap_split_proxy_uri, true) }
517    }
518
519    /// Attempts to convert a [Url] into a [CoapUri].
520    ///
521    /// # Errors
522    /// May fail if the provided Url is not a valid URI supported by libcoap or if the URI
523    /// components exceed maximum lengths (see the struct level documentation).
524    #[cfg(feature = "url")]
525    pub fn try_from_url(url: &Url) -> Result<CoapUri, UriParsingError> {
526        Self::try_from_str(url.as_str())
527    }
528
529    /// Attempts to convert a [Url] into a proxy [CoapUri].
530    ///
531    /// # Errors
532    /// May fail if the provided Url is not a valid proxy URI supported by libcoap or if the URI
533    /// components exceed maximum lengths (see the struct level documentation).
534    #[cfg(feature = "url")]
535    pub fn try_from_url_proxy(url: &Url) -> Result<CoapUri, UriParsingError> {
536        Self::try_from_str_proxy(url.as_str())
537    }
538
539    /// Returns the scheme part of this URI.
540    pub fn scheme(&self) -> Option<CoapUriScheme> {
541        // URIs can either be absolute or relative. If they are relative, the scheme is also not
542        // set (but defaults to CoAP as the default enum value is 0).
543        self.host()?;
544        Some(CoapUriScheme::from_raw_scheme(self.raw_uri.scheme))
545    }
546
547    /// Returns the host part of this URI.
548    pub fn host(&self) -> Option<&[u8]> {
549        let raw_str = self.raw_uri.host;
550        if raw_str.length == 0 {
551            return None;
552        }
553        // SAFETY: After construction the fields of self.raw_uri always reference the corresponding
554        //         parts of the underlying string, which is pinned. Therefore, the pointer and
555        //         length are valid for the lifetime of this struct.
556        Some(unsafe { std::slice::from_raw_parts(raw_str.s, raw_str.length) })
557    }
558
559    /// Returns the port of this URI (if provided).
560    pub fn port(&self) -> Option<UriPort> {
561        match self.raw_uri.port {
562            0 => None,
563            v => Some(v),
564        }
565    }
566
567    /// Returns the URI path part of this URI.
568    pub fn path(&self) -> Option<&[u8]> {
569        let raw_str = self.raw_uri.path;
570        if raw_str.s.is_null() {
571            return None;
572        }
573        // SAFETY: After construction the fields of self.raw_uri always reference the corresponding
574        //         parts of the underlying string, which is pinned. Therefore, the pointer and
575        //         length are valid for the lifetime of this struct.
576        Some(unsafe { std::slice::from_raw_parts(raw_str.s, raw_str.length) })
577    }
578
579    /// Returns the host part of this URI.
580    pub fn query(&self) -> Option<&[u8]> {
581        let raw_str = self.raw_uri.query;
582        if raw_str.s.is_null() {
583            return None;
584        }
585        // SAFETY: After construction the fields of self.raw_uri always reference the corresponding
586        //         parts of the underlying string, which is pinned. Therefore, the pointer and
587        //         length are valid for the lifetime of this struct.
588        Some(unsafe { std::slice::from_raw_parts(raw_str.s, raw_str.length) })
589    }
590
591    /// Returns whether this URI is a proxy URI.
592    pub fn is_proxy(&self) -> bool {
593        self.is_proxy
594    }
595
596    /// Converts the given URI into a `Vec` of [CoapOption]s that can be added to a
597    /// [crate::message::CoapMessage].
598    pub fn into_options(self) -> Vec<CoapOption> {
599        // TODO this is a lot of copying around, however, fixing that would require an entire
600        //      rewrite of the option handling code, so it's better kept for a separate PR.
601
602        let mut optlist = std::ptr::null_mut();
603        // SAFETY: self.raw_uri is always valid after construction. The destination may be a null
604        //         pointer, optlist may be a null pointer at the start (it will be set to a valid
605        //         pointer by this call). Buf and create_port_host_opt are set according to the
606        //         libcoap documentation.
607        if unsafe { coap_uri_into_optlist(&self.raw_uri, std::ptr::null(), &mut optlist, 1) } < 0 {
608            // We have already parsed this URI. If converting it into options fails, something went
609            // terribly wrong.
610            panic!("could not convert valid coap URI into options");
611        }
612        let mut out_opts = Vec::new();
613        while !optlist.is_null() {
614            // SAFETY: coap_uri_into_options should have ensured that optlist is either null or a
615            //         valid coap option list. In the former case, we wouldn't be in this loop, in
616            //         the latter case calling from_optlist_entry is fine.
617            out_opts.push(unsafe {
618                CoapOption::from_optlist_entry(optlist.as_ref().expect("self-generated options should always be valid"))
619                    .expect("self-generated options should always be valid")
620            });
621            optlist = unsafe { *optlist }.next;
622        }
623        // SAFETY: optlist has been set by coap_uri_into_options, which has not returned an error.
624        unsafe {
625            coap_delete_optlist(optlist);
626        }
627        drop(self);
628        out_opts
629    }
630
631    /// Provides a reference to the raw [coap_uri_t] struct represented by this [CoapUri].
632    ///
633    /// Note that while obtaining this struct and reading the fields is safe (which is why this
634    /// method is safe), modifying the referenced URI parts by (unsafely) dereferencing and mutating
635    /// the `const` pointers inside is not.
636    pub fn as_raw_uri(&self) -> &coap_uri_t {
637        &self.raw_uri
638    }
639
640    /// Converts the given `raw_uri` to a new [CoapUri] instance.
641    ///
642    /// This method will create a copy of the provided URI, i.e. `raw_uri` will remain valid and not
643    /// be owned by the created [CoapUri] instance.
644    ///
645    /// # Safety
646    ///
647    /// The provided `raw_uri` must point to a valid instance of [coap_uri_t].
648    /// In particular, the provided pointers for the URI components must also be valid.
649    ///
650    /// # Panics
651    ///
652    /// Panics if the provided `raw_uri` is null or the provided URI contains a null byte.
653    pub unsafe fn from_raw_uri(raw_uri: *const coap_uri_t, is_proxy: bool) -> CoapUri {
654        // Loosely based on coap_clone_uri.
655        assert!(!raw_uri.is_null());
656        let host_slice = (*raw_uri)
657            .host
658            .s
659            .is_null()
660            .then_some(&[] as &[u8])
661            .unwrap_or_else(|| std::slice::from_raw_parts((*raw_uri).host.s, (*raw_uri).host.length));
662        let path_slice = (*raw_uri)
663            .path
664            .s
665            .is_null()
666            .then_some(&[] as &[u8])
667            .unwrap_or_else(|| std::slice::from_raw_parts((*raw_uri).path.s, (*raw_uri).path.length));
668        let query_slice = (*raw_uri)
669            .query
670            .s
671            .is_null()
672            .then_some(&[] as &[u8])
673            .unwrap_or_else(|| std::slice::from_raw_parts((*raw_uri).query.s, (*raw_uri).query.length));
674        // Clone the actual URI string.
675        let (uri_str_copy, host_pos, path_pos, query_pos) = Self::construct_uri_string_from_parts(
676            CoapUriScheme::from_raw_scheme((*raw_uri).scheme),
677            host_slice,
678            (*raw_uri).port,
679            path_slice,
680            query_slice,
681        )
682        .expect("provided raw URI is invalid");
683
684        let mut result = CoapUri::create_unparsed_uri(
685            CString::new(uri_str_copy).expect("provided raw_uri contains null bytes!"),
686            is_proxy,
687        );
688        result.raw_uri.port = (*raw_uri).port;
689        result.raw_uri.scheme = (*raw_uri).scheme;
690        // Now, _after_ the uri_str is pinned, we can set the new object's raw_uri string fields.
691        result.raw_uri.host = coap_str_const_t {
692            length: (*raw_uri).host.length,
693            s: result.uri_str.0.as_bytes_with_nul()[host_pos..host_pos + 1].as_ptr(),
694        };
695        result.raw_uri.path = coap_str_const_t {
696            length: (*raw_uri).path.length,
697            s: result.uri_str.0.as_bytes_with_nul()[path_pos..path_pos + 1].as_ptr(),
698        };
699        result.raw_uri.query = coap_str_const_t {
700            length: (*raw_uri).query.length,
701            s: result.uri_str.0.as_bytes_with_nul()[query_pos..query_pos + 1].as_ptr(),
702        };
703
704        result
705    }
706
707    /// Create an instance of [CoapUri] with the given `uri_str`, but don't parse the value, i.e.
708    /// the resulting `raw_uri` is not set correctly.
709    fn create_unparsed_uri(uri_str: CString, is_proxy: bool) -> Self {
710        let uri_str = Box::pin(CoapUriInner(uri_str, PhantomPinned));
711        CoapUri {
712            raw_uri: coap_uri_t {
713                host: coap_str_const_t {
714                    length: 0,
715                    s: std::ptr::null(),
716                },
717                port: 0,
718                path: coap_str_const_t {
719                    length: 0,
720                    s: std::ptr::null(),
721                },
722                query: coap_str_const_t {
723                    length: 0,
724                    s: std::ptr::null(),
725                },
726                scheme: coap_uri_scheme_t_COAP_URI_SCHEME_COAP,
727            },
728            uri_str,
729            is_proxy,
730        }
731    }
732
733    /// Create and parse a URI from a CString.
734    ///
735    /// # Safety
736    ///
737    /// parsing_fn must be either coap_split_uri or coap_split_proxy_uri.
738    unsafe fn create_parsed_uri(
739        uri_str: CString,
740        parsing_fn: unsafe extern "C" fn(*const u8, usize, *mut coap_uri_t) -> c_int,
741        is_proxy: bool,
742    ) -> Result<CoapUri, UriParsingError> {
743        ensure_coap_started();
744        let mut uri = Self::create_unparsed_uri(uri_str, is_proxy);
745
746        // SAFETY: The provided pointers to raw_uri and uri_str are valid.
747        // Because uri_str is pinned (and its type is not Unpin), the pointer locations are always
748        // valid while this object lives, therefore the resulting coap_uri_t remains valid for the
749        // entire lifetime of this object too.
750        if unsafe {
751            parsing_fn(
752                uri.uri_str.0.as_ptr() as *const u8,
753                CStr::from_ptr(uri.uri_str.0.as_ptr()).count_bytes(),
754                std::ptr::from_mut(&mut uri.raw_uri),
755            )
756        } < 0
757        {
758            return Err(UriParsingError::Unknown);
759        }
760        Ok(uri)
761    }
762
763    /// Constructs a CString representing the given URI parts in a form parsable by libcoap.
764    fn construct_uri_string_from_parts(
765        scheme: CoapUriScheme,
766        host: &[u8],
767        port: u16,
768        path: &[u8],
769        query: &[u8],
770    ) -> Result<(CString, usize, usize, usize), UriParsingError> {
771        // Reconstruct string for scheme.
772        let scheme = if !host.is_empty() {
773            format!("{}://", scheme)
774        } else {
775            String::new()
776        };
777        let port = if port != 0 { format!(":{}", port) } else { String::new() };
778        let parts = [scheme.as_bytes(), host, port.as_bytes(), path, query];
779        let uri_str_len = parts.iter().map(|v| v.len()).sum::<usize>();
780
781        let mut uri_str_copy = vec![0u8; uri_str_len];
782        let mut cur;
783        let mut rest = uri_str_copy.as_mut_slice();
784        for part in parts.iter() {
785            (cur, rest) = rest.split_at_mut(part.len());
786            cur.clone_from_slice(part)
787        }
788
789        // The host is index 1 in the parts list
790        let host_pos = parts[..1].iter().map(|v| v.len()).sum();
791        // The path is index 3 in the parts list
792        let path_pos = parts[..3].iter().map(|v| v.len()).sum();
793        // The query is index 4 in the parts list
794        let query_pos = parts[..4].iter().map(|v| v.len()).sum();
795
796        CString::new(uri_str_copy)
797            .map(|v| (v, host_pos, path_pos, query_pos))
798            .map_err(UriParsingError::from)
799    }
800}
801
802impl PartialEq for CoapUri {
803    fn eq(&self, other: &Self) -> bool {
804        self.raw_uri.port == other.raw_uri.port
805            && self.raw_uri.scheme == other.raw_uri.scheme
806            // SAFETY: After construction the fields of self.raw_uri always reference the
807            //         corresponding parts of the underlying string, which is pinned. Therefore, the
808            //         pointer and length are valid for the lifetime of this struct.
809            && unsafe {
810            coap_string_equal!(&self.raw_uri.host, &other.raw_uri.host)
811                && coap_string_equal!(&self.raw_uri.path, &other.raw_uri.path)
812                && coap_string_equal!(&self.raw_uri.query, &other.raw_uri.query)
813        }
814    }
815}
816
817impl Eq for CoapUri {}
818
819impl Clone for CoapUri {
820    fn clone(&self) -> Self {
821        // SAFETY: raw_uri is a valid pointer to a coap_uri_t (by construction of this type and
822        // contract of from_raw_uri)
823        unsafe { CoapUri::from_raw_uri(&self.raw_uri, self.is_proxy) }
824    }
825}
826
827#[cfg(feature = "url")]
828impl TryFrom<&Url> for CoapUri {
829    type Error = UriParsingError;
830
831    fn try_from(value: &Url) -> Result<Self, Self::Error> {
832        CoapUri::try_from_url(value)
833    }
834}
835
836impl FromStr for CoapUri {
837    type Err = UriParsingError;
838
839    fn from_str(s: &str) -> Result<Self, Self::Err> {
840        Self::try_from_str(s)
841    }
842}
843
844impl Display for CoapUri {
845    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
846        self.uri_str.fmt(f)
847    }
848}
849
850/// Transport protocols that can be used with libcoap.
851#[repr(u32)]
852#[non_exhaustive]
853#[derive(Copy, Clone, FromPrimitive, PartialEq, Eq, Hash)]
854pub enum CoapProtocol {
855    None = coap_proto_t_COAP_PROTO_NONE as u32,
856    Udp = coap_proto_t_COAP_PROTO_UDP as u32,
857    Dtls = coap_proto_t_COAP_PROTO_DTLS as u32,
858    Tcp = coap_proto_t_COAP_PROTO_TCP as u32,
859    Tls = coap_proto_t_COAP_PROTO_TLS as u32,
860}
861
862impl CoapProtocol {
863    pub fn is_secure(&self) -> bool {
864        match self {
865            CoapProtocol::None | CoapProtocol::Udp | CoapProtocol::Tcp => false,
866            CoapProtocol::Dtls | CoapProtocol::Tls => true,
867        }
868    }
869}
870
871#[doc(hidden)]
872impl From<coap_proto_t> for CoapProtocol {
873    fn from(raw_proto: coap_proto_t) -> Self {
874        <CoapProtocol as FromPrimitive>::from_u32(raw_proto as u32).expect("unknown protocol")
875    }
876}
877
878impl Display for CoapProtocol {
879    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
880        f.write_str(match self {
881            CoapProtocol::None => "none",
882            CoapProtocol::Udp => "udp",
883            CoapProtocol::Dtls => "dtls",
884            CoapProtocol::Tcp => "tcp",
885            CoapProtocol::Tls => "tls",
886        })
887    }
888}
889
890fn convert_to_fixed_size_slice(n: usize, val: &[u8]) -> Box<[u8]> {
891    if val.len() > n {
892        panic!("supplied slice too short");
893    }
894    let mut buffer: Vec<u8> = vec![0; n];
895    let (_, target_buffer) = buffer.split_at_mut(n - val.len());
896    target_buffer.copy_from_slice(val);
897    buffer.truncate(n);
898    buffer.into_boxed_slice()
899}
900
901// TODO the following functions should probably return a result and use generics.
902pub(crate) fn decode_var_len_u32(val: &[u8]) -> u32 {
903    u32::from_be_bytes(
904        convert_to_fixed_size_slice(4, val)[..4]
905            .try_into()
906            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
907    )
908}
909
910pub(crate) fn encode_var_len_u32(val: u32) -> Box<[u8]> {
911    // I really hope that rust accounts for endianness here.
912    let bytes_to_discard = val.leading_zeros() / 8;
913    let mut ret_val = Vec::from(val.to_be_bytes());
914    ret_val.drain(..bytes_to_discard as usize);
915    ret_val.into_boxed_slice()
916}
917
918// Kept for consistency
919#[allow(unused)]
920pub(crate) fn decode_var_len_u64(val: &[u8]) -> u64 {
921    u64::from_be_bytes(
922        convert_to_fixed_size_slice(8, val)[..8]
923            .try_into()
924            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
925    )
926}
927
928// Kept for consistency
929#[allow(unused)]
930pub(crate) fn encode_var_len_u64(val: u64) -> Box<[u8]> {
931    // I really hope that rust accounts for endianness here.
932    let bytes_to_discard = val.leading_zeros() / 8;
933    let mut ret_val = Vec::from(val.to_be_bytes());
934    ret_val.drain(..bytes_to_discard as usize);
935    ret_val.into_boxed_slice()
936}
937
938pub(crate) fn decode_var_len_u16(val: &[u8]) -> u16 {
939    u16::from_be_bytes(
940        convert_to_fixed_size_slice(2, val)[..2]
941            .try_into()
942            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
943    )
944}
945
946pub(crate) fn encode_var_len_u16(val: u16) -> Box<[u8]> {
947    // I really hope that rust accounts for endianness here.
948    let bytes_to_discard = val.leading_zeros() / 8;
949    let mut ret_val = Vec::from(val.to_be_bytes());
950    ret_val.drain(..bytes_to_discard as usize);
951    ret_val.into_boxed_slice()
952}
953
954pub(crate) fn decode_var_len_u8(val: &[u8]) -> u16 {
955    u16::from_be_bytes(
956        convert_to_fixed_size_slice(1, val)[..1]
957            .try_into()
958            .expect("could not convert from variable sized value to fixed size number as the lengths don't match"),
959    )
960}
961
962pub(crate) fn encode_var_len_u8(val: u8) -> Box<[u8]> {
963    Vec::from([val]).into_boxed_slice()
964}