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}