diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7cdf4ac5..5a2f431b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,23 +24,23 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
- - uses: dtolnay/rust-toolchain@master
+ - uses: dtolnay/rust-toolchain@stable
with:
components: rust-src
- toolchain: nightly
+ toolchain: stable
- if: matrix.dtls_backend == 'gnutls'
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libgnutls28-dev libgnutls30
version: 1.0
- if: matrix.crate == 'libcoap-rs' && matrix.dtls_backend != 'gnutls'
- run: cargo test -p ${{ matrix.crate }} --no-default-features --features dtls,tcp,vendored --features dtls_${{ matrix.dtls_backend }} --features dtls_${{ matrix.dtls_backend }}_vendored --no-fail-fast -- -Z unstable-options --report-time --ensure-time
+ run: cargo test -p ${{ matrix.crate }} --no-default-features --features dtls,tcp,vendored --features dtls_${{ matrix.dtls_backend }} --features dtls_${{ matrix.dtls_backend }}_vendored --no-fail-fast
- if: matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'gnutls'
- run: cargo test -p ${{ matrix.crate }} --no-default-features --features dtls,tcp,vendored --features dtls_${{ matrix.dtls_backend }} --no-fail-fast -- -Z unstable-options --report-time --ensure-time
+ run: cargo test -p ${{ matrix.crate }} --no-default-features --features dtls,tcp,vendored --features dtls_${{ matrix.dtls_backend }} --no-fail-fast
- if: matrix.crate == 'libcoap-sys' && matrix.dtls_backend != 'gnutls'
- run: cargo test -p ${{ matrix.crate }} --features dtls,dtls_backend_${{ matrix.dtls_backend }},dtls_backend_${{ matrix.dtls_backend }}_vendored --no-fail-fast -- -Z unstable-options --report-time --ensure-time
+ run: cargo test -p ${{ matrix.crate }} --features dtls,dtls_backend_${{ matrix.dtls_backend }},dtls_backend_${{ matrix.dtls_backend }}_vendored --no-fail-fast
- if: matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'gnutls'
- run: cargo test -p ${{ matrix.crate }} --features dtls,dtls_backend_${{ matrix.dtls_backend }} --no-fail-fast -- -Z unstable-options --report-time --ensure-time
+ run: cargo test -p ${{ matrix.crate }} --features dtls,dtls_backend_${{ matrix.dtls_backend }} --no-fail-fast
lint:
runs-on: ubuntu-latest
diff --git a/.idea/dtsSettings.xml b/.idea/dtsSettings.xml
new file mode 100644
index 00000000..c0b5c345
--- /dev/null
+++ b/.idea/dtsSettings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Test.xml b/.idea/runConfigurations/Test.xml
index 7f713101..ec677be7 100644
--- a/.idea/runConfigurations/Test.xml
+++ b/.idea/runConfigurations/Test.xml
@@ -2,10 +2,7 @@
-
-
-
-
+
diff --git a/libcoap-sys/build.rs b/libcoap-sys/build.rs
index 186c4615..fdfb0efa 100644
--- a/libcoap-sys/build.rs
+++ b/libcoap-sys/build.rs
@@ -94,6 +94,7 @@ fn get_builder_espidf() -> bindgen::Builder {
let esp_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH is not set");
// Determine compiler path
+ // SAFETY: Always safe to call in a single-threaded environment (see docs of env::set_var).
unsafe { env::set_var("PATH", embuild_env) };
let cmake_info = embuild::cmake::Query::new(
&Path::new(&esp_idf_buildroot).join("build"),
diff --git a/libcoap-sys/src/lib.rs b/libcoap-sys/src/lib.rs
index 4fc650a5..f2ce0f9f 100644
--- a/libcoap-sys/src/lib.rs
+++ b/libcoap-sys/src/lib.rs
@@ -86,16 +86,22 @@
#![allow(deref_nullptr)]
#![allow(non_snake_case)]
-use libc::{fd_set, sockaddr, sockaddr_in, sockaddr_in6, socklen_t, time_t, sa_family_t};
-#[cfg(not(target_os = "espidf"))]
-use libc::{epoll_event};
+use std::ffi::c_void;
-// use dtls backend libraries in cases where they set our linker flags, otherwise cargo will
+#[allow(unused_imports)]
+use libc::{fd_set, memcmp, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, socklen_t, time_t};
+#[allow(unused_imports)]
+#[cfg(not(target_os = "espidf"))]
+use libc::epoll_event;
+// use dtls backend libraries in cases where they set our linker flags, otherwise cargo will
// optimize them out.
+#[allow(unused_imports)]
#[cfg(feature = "dtls_backend_mbedtls_vendored")]
use mbedtls_sys as _;
+#[allow(unused_imports)]
#[cfg(feature = "dtls_backend_openssl")]
use openssl_sys as _;
+#[allow(unused_imports)]
#[cfg(feature = "dtls_backend_tinydtls")]
use tinydtls_sys as _;
@@ -106,13 +112,45 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[inline]
#[cfg(inlined_coap_send_rst)]
-pub unsafe fn coap_send_rst(
- session: *mut coap_session_t,
- request: *const coap_pdu_t,
-) -> coap_mid_t {
+pub unsafe fn coap_send_rst(session: *mut coap_session_t, request: *const coap_pdu_t) -> coap_mid_t {
coap_send_message_type(session, request, crate::coap_pdu_type_t::COAP_MESSAGE_RST)
}
+/// Compares instances of coap_str_const_t and/or coap_string_t.
+///
+/// This macro is a reimplementation of the macro defined in coap_str.h, see
+/// .
+#[macro_export]
+macro_rules! coap_string_equal {
+ ( $string1:expr, $string2:expr ) => {{
+ use libcoap_sys::coap_string_equal_internal;
+ let s1 = $string1;
+ let s2 = $string2;
+ coap_string_equal_internal((*s1).s, (*s1).length, (*s2).s, (*s2).length)
+ }};
+}
+
+/// Internal only function for CoAP string comparisons.
+///
+/// *DO NOT USE THIS FUNCTION DIRECTLY.* It is only public because it is used by the
+/// [coap_string_equal] macro, which is the function you probably wanted to call instead.
+///
+/// # Safety
+///
+/// This function should not be called directly, use [coap_string_equal] instead.
+pub unsafe fn coap_string_equal_internal(
+ str1_ptr: *const u8,
+ str1_len: usize,
+ str2_ptr: *const u8,
+ str2_len: usize,
+) -> bool {
+ str1_len == str2_len
+ && (str1_len == 0
+ || !str1_ptr.is_null()
+ && !str2_ptr.is_null()
+ && memcmp(str1_ptr as *const c_void, str2_ptr as *const c_void, str1_len) == 0)
+}
+
#[cfg(all(test, not(target_os = "espidf")))]
mod tests {
use std::{
@@ -122,9 +160,8 @@ mod tests {
sync::{Arc, Barrier},
};
- use libc::{in6_addr, in_addr, sa_family_t, size_t, AF_INET, AF_INET6};
+ use libc::{AF_INET, AF_INET6, in6_addr, in_addr, sa_family_t, size_t};
- use super::*;
use crate::{
coap_pdu_code_t::{COAP_REQUEST_CODE_GET, COAP_RESPONSE_CODE_CONTENT},
coap_proto_t::COAP_PROTO_UDP,
@@ -132,6 +169,8 @@ mod tests {
coap_response_t::COAP_RESPONSE_OK,
};
+ use super::*;
+
const COAP_TEST_RESOURCE_URI: &str = "test";
const COAP_TEST_RESOURCE_RESPONSE: &str = "Hello World!";
@@ -286,8 +325,6 @@ mod tests {
// This also seems to free all resources.
unsafe {
coap_free_context(context);
- std::mem::drop(context);
- std::mem::drop(test_resource);
}
}
@@ -371,8 +408,6 @@ mod tests {
// This also seems to free all resources.
unsafe {
coap_free_context(context);
- std::mem::drop(context);
- std::mem::drop(client_session)
}
server_thread_handle.join().expect("Error waiting for server thread");
}
diff --git a/libcoap/Cargo.toml b/libcoap/Cargo.toml
index 91bda2d3..8647602b 100644
--- a/libcoap/Cargo.toml
+++ b/libcoap/Cargo.toml
@@ -18,7 +18,7 @@ keywords = ["coap", "libcoap"]
resolver = "2"
[features]
-default = ["dtls", "tcp", "dtls_openssl"]
+default = ["dtls", "tcp", "dtls_openssl", "vendored"]
dtls = ["libcoap-sys/dtls"]
dtls_tinydtls = ["libcoap-sys/dtls_backend_tinydtls"]
dtls_tinydtls_vendored = ["dtls_tinydtls", "libcoap-sys/dtls_backend_tinydtls_vendored"]
@@ -34,11 +34,11 @@ vendored = ["libcoap-sys/vendored"]
[dependencies]
libcoap-sys = { version = "^0.2.2", path = "../libcoap-sys", default-features = false, features = ["client", "server"] }
libc = { version = "^0.2.95" }
-num-derive = { version = "^0.3.3" }
+num-derive = { version = "^0.3.3" }
num-traits = { version = "^0.2.14" }
-url = { version = "^2.2" }
+url = { version = "^2.2", optional = true }
rand = { version = "^0.8.4" }
thiserror = "^1.0"
[package.metadata.docs.rs]
-features = ["dtls", "dtls_openssl", "vendored"]
+features = ["dtls", "dtls_openssl", "vendored", "url"]
diff --git a/libcoap/src/crypto.rs b/libcoap/src/crypto.rs
index 579f0750..8cec5dd9 100644
--- a/libcoap/src/crypto.rs
+++ b/libcoap/src/crypto.rs
@@ -19,8 +19,8 @@ use libcoap_sys::{
coap_bin_const_t, coap_context_t, coap_dtls_cpsk_info_t, coap_dtls_spsk_info_t, coap_session_t, coap_str_const_t,
};
-use crate::session::CoapServerSession;
use crate::{context::CoapContext, session::CoapClientSession};
+use crate::session::CoapServerSession;
/// Representation of cryptographic information used by a server.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -132,6 +132,7 @@ pub trait CoapServerCryptoProvider: Debug {
// TODO DTLS PKI/RPK
+// DTLS Identity Hint callback is unsupported on mbedtls
#[cfg(feature = "dtls")]
pub(crate) unsafe extern "C" fn dtls_ih_callback(
hint: *mut coap_str_const_t,
@@ -142,7 +143,7 @@ pub(crate) unsafe extern "C" fn dtls_ih_callback(
let provided_identity = std::slice::from_raw_parts((*hint).s, (*hint).length);
session
.provide_raw_key_for_hint(provided_identity)
- .map(|v| v as *const coap_dtls_cpsk_info_t)
+ .map(|v| v)
.unwrap_or(std::ptr::null())
}
@@ -170,7 +171,7 @@ pub(crate) unsafe extern "C" fn dtls_server_sni_callback(
if let Ok(sni_value) = sni_value {
context
.provide_raw_hint_for_sni(sni_value)
- .map(|v| (v as *const coap_dtls_spsk_info_t))
+ .map(|v| (v))
.unwrap_or(std::ptr::null())
} else {
std::ptr::null()
diff --git a/libcoap/src/error.rs b/libcoap/src/error.rs
index 4379089e..c58fb19a 100644
--- a/libcoap/src/error.rs
+++ b/libcoap/src/error.rs
@@ -9,6 +9,7 @@
//! Error types
+use std::ffi::NulError;
use std::string::FromUtf8Error;
use thiserror::Error;
@@ -82,16 +83,25 @@ pub enum OptionValueError {
/// A string value could not be converted to UTF-8.
#[error("CoAP option has invalid value: invalid string")]
StringConversion(#[from] FromUtf8Error),
+ /// URI encoded in message could not be parsed.
+ #[error("CoAP option has invalid value: invalid URI")]
+ UriParsing(#[from] UriParsingError),
/// Option has an illegal value.
#[error("CoAP option has invalid value")]
IllegalValue,
}
-#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Error, Debug, Clone, Eq, PartialEq)]
pub enum UriParsingError {
+ /// Unknown error inside of libcoap
+ #[error("CoAP option creation error: unknown error in call to libcoap")]
+ Unknown,
/// URI does not have a valid scheme for libcoap (coap, coaps, coap+tcp, coaps+tcp, http, https).
- #[error("URL does not have scheme valid for libcoap")]
- NotACoapScheme,
+ #[error("URI scheme {} is not a valid CoAP scheme known to libcoap", .0)]
+ NotACoapScheme(String),
+ /// Provided URI contains a null byte.
+ #[error("Provided URI contains a null byte")]
+ ContainsNullByte(#[from] NulError),
}
#[derive(Error, Debug, Clone, Eq, PartialEq)]
@@ -109,9 +119,6 @@ pub enum MessageConversionError {
/// Provided URI has invalid scheme.
#[error("CoAP message conversion error: provided uri does not have scheme valid for CoAP")]
NotACoapUri(UriParsingError),
- /// URI is invalid (most likely a Proxy URI cannot be parsed as a valid URL).
- #[error("CoAP message conversion error: invalid uri (malformed proxy URL?)")]
- InvalidUri(url::ParseError),
/// Invalid message code.
#[error("CoAP message conversion error: invalid message code")]
InvalidMessageCode(#[from] MessageCodeError),
@@ -143,12 +150,6 @@ impl From for MessageConversionError {
}
}
-impl From for MessageConversionError {
- fn from(v: url::ParseError) -> Self {
- MessageConversionError::InvalidUri(v)
- }
-}
-
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub enum MessageCodeError {
/// Provided message code for request was not a request code.
diff --git a/libcoap/src/lib.rs b/libcoap/src/lib.rs
index 003ff620..b34fb625 100644
--- a/libcoap/src/lib.rs
+++ b/libcoap/src/lib.rs
@@ -3,7 +3,7 @@
* lib.rs - Main library entry point for safe libcoap bindings.
* This file is part of the libcoap-rs crate, see the README and LICENSE files for
* more information and terms of use.
- * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
+ * Copyright © 2021-2024 The NAMIB Project Developers, all rights reserved.
* See the README as well as the LICENSE file for more information.
*/
@@ -56,8 +56,6 @@
//! types::{CoapUriScheme, CoapUri}
//! };
//!
-//! use url::Url;
-//!
//! let server_address : SocketAddr = "[::1]:5683".parse().unwrap();
//!
//! // Create a new context.
@@ -68,11 +66,10 @@
//! .expect("Failed to create client-side session");
//!
//! // Create a new CoAP URI to request from.
-//! let uri = CoapUri::try_from_url(Url::parse("coap://[::1]:5683/hello_world").unwrap()).unwrap();
+//! let uri = "coap://[::1]:5683/hello_world".parse().unwrap();
//!
//! // Create a new request of type get with the specified URI.
-//! let mut request = CoapRequest::new(CoapMessageType::Con, CoapRequestCode::Get).unwrap();
-//! request.set_uri(Some(uri)).unwrap();
+//! let mut request = CoapRequest::new(CoapMessageType::Con, CoapRequestCode::Get, uri).unwrap();
//!
//! // Send the request and wait for a response.
//! let req_handle = session.send_request(request).expect("Unable to send request");
@@ -178,6 +175,10 @@
//! Note that enabling multiple backends is not possible and doing so will result in a single
//! backend being chosen based on the priority order (gnutls > openssl > mbedtls > tinydtls).
+pub use context::CoapContext;
+pub use event::CoapEventHandler;
+pub use resource::{CoapRequestHandler, CoapResource};
+
mod context;
#[cfg(feature = "dtls")]
pub mod crypto;
@@ -190,7 +191,3 @@ mod resource;
pub mod session;
pub mod transport;
pub mod types;
-
-pub use context::CoapContext;
-pub use event::CoapEventHandler;
-pub use resource::{CoapRequestHandler, CoapResource};
diff --git a/libcoap/src/message/mod.rs b/libcoap/src/message/mod.rs
index a2e9152b..51ea217a 100644
--- a/libcoap/src/message/mod.rs
+++ b/libcoap/src/message/mod.rs
@@ -16,6 +16,7 @@
//! and [CoapResponse]).
use std::{ffi::c_void, mem::MaybeUninit, slice::Iter};
+use std::fmt::Write;
use num_traits::FromPrimitive;
@@ -86,7 +87,7 @@ impl CoapOption {
/// Create a CoAP option from its raw representation in the C library.
///
/// # Safety
- /// `opt` must be a valid pointer to a well formed coap_opt_t value as returned by
+ /// `opt` must be a valid pointer to a well-formed coap_opt_t value as returned by
/// [coap_option_next()].
pub(crate) unsafe fn from_raw_opt(
number: coap_option_num_t,
@@ -96,52 +97,7 @@ impl CoapOption {
coap_opt_value(opt),
coap_opt_length(opt) as usize,
));
- match CoapOptionType::try_from(number) {
- Ok(opt_type) => {
- if opt_type.min_len() > value.len() {
- return Err(OptionValueError::TooShort);
- } else if opt_type.max_len() < value.len() {
- return Err(OptionValueError::TooLong);
- }
- match opt_type {
- CoapOptionType::IfMatch => Ok(CoapOption::IfMatch(if value.is_empty() {
- CoapMatch::Empty
- } else {
- CoapMatch::ETag(value.into_boxed_slice())
- })),
- CoapOptionType::UriHost => Ok(CoapOption::UriHost(String::from_utf8(value)?)),
- CoapOptionType::ETag => Ok(CoapOption::ETag(value.into_boxed_slice())),
- CoapOptionType::IfNoneMatch => Ok(CoapOption::IfNoneMatch),
- CoapOptionType::UriPort => Ok(CoapOption::UriPort(decode_var_len_u16(value.as_slice()))),
- CoapOptionType::LocationPath => Ok(CoapOption::LocationPath(String::from_utf8(value)?)),
- CoapOptionType::UriPath => Ok(CoapOption::UriPath(String::from_utf8(value)?)),
- CoapOptionType::ContentFormat => {
- Ok(CoapOption::ContentFormat(decode_var_len_u16(value.as_slice())))
- },
- CoapOptionType::MaxAge => Ok(CoapOption::MaxAge(decode_var_len_u32(value.as_slice()))),
- CoapOptionType::UriQuery => Ok(CoapOption::UriQuery(String::from_utf8(value)?)),
- CoapOptionType::Accept => Ok(CoapOption::Accept(decode_var_len_u16(value.as_slice()))),
- CoapOptionType::LocationQuery => Ok(CoapOption::LocationQuery(String::from_utf8(value)?)),
- CoapOptionType::ProxyUri => Ok(CoapOption::ProxyUri(String::from_utf8(value)?)),
- CoapOptionType::ProxyScheme => Ok(CoapOption::ProxyScheme(String::from_utf8(value)?)),
- CoapOptionType::Size1 => Ok(CoapOption::Size1(decode_var_len_u32(value.as_slice()))),
- CoapOptionType::Size2 => Ok(CoapOption::Size2(decode_var_len_u32(value.as_slice()))),
- CoapOptionType::Block1 => Ok(CoapOption::Block1(decode_var_len_u32(value.as_slice()))),
- CoapOptionType::Block2 => Ok(CoapOption::Block2(decode_var_len_u32(value.as_slice()))),
- CoapOptionType::HopLimit => Ok(CoapOption::HopLimit(decode_var_len_u16(value.as_slice()))),
- CoapOptionType::NoResponse => {
- Ok(CoapOption::NoResponse(decode_var_len_u8(value.as_slice()) as NoResponse))
- },
- CoapOptionType::Observe => Ok(CoapOption::Observe(decode_var_len_u32(value.as_slice()))),
- CoapOptionType::Oscore => Ok(CoapOption::Oscore(value.into_boxed_slice())),
- CoapOptionType::Echo => Ok(CoapOption::Echo(value.into_boxed_slice())),
- CoapOptionType::RTag => Ok(CoapOption::RTag(value.into_boxed_slice())),
- CoapOptionType::QBlock1 => Ok(CoapOption::QBlock1(decode_var_len_u32(value.as_slice()))),
- CoapOptionType::QBlock2 => Ok(CoapOption::QBlock2(decode_var_len_u32(value.as_slice()))),
- }
- },
- _ => Ok(CoapOption::Other(number, value.into_boxed_slice())),
- }
+ Self::from_type_value(number, value)
}
/// Returns the option number associated with this option.
@@ -229,6 +185,94 @@ impl CoapOption {
let value = self.into_value_bytes()?;
Ok(unsafe { coap_new_optlist(num, value.len(), value.as_ptr()) })
}
+
+ /// Attempts to convert a raw coap_optlist_t instance into a [CoapOption].
+ ///
+ /// # Errors
+ ///
+ /// Returns an [OptionValueError] if the provided `optlist_entry` has an invalid value.
+ ///
+ /// # Safety
+ ///
+ /// optlist_entry must be a valid coap_optlist_t instance whose `number`, `data` and `length`
+ /// fields describe a CoAP option number and the option value respectively.
+ pub(crate) unsafe fn from_optlist_entry(optlist_entry: &coap_optlist_t) -> Result {
+ let value = Vec::from(std::slice::from_raw_parts(optlist_entry.data, optlist_entry.length));
+ Self::from_type_value(optlist_entry.number, value)
+ }
+
+ /// Convert coap_option_num_t and option value into a parsed [CoapOption] if possible.
+ fn from_type_value(type_: coap_option_num_t, value: Vec) -> Result {
+ match CoapOptionType::try_from(type_) {
+ Ok(opt_type) => {
+ if opt_type.min_len() > value.len() {
+ return Err(OptionValueError::TooShort);
+ } else if opt_type.max_len() < value.len() {
+ return Err(OptionValueError::TooLong);
+ }
+ match opt_type {
+ CoapOptionType::IfMatch => Ok(CoapOption::IfMatch(if value.is_empty() {
+ CoapMatch::Empty
+ } else {
+ CoapMatch::ETag(value.into_boxed_slice())
+ })),
+ CoapOptionType::UriHost => Ok(CoapOption::UriHost(String::from_utf8(value)?)),
+ CoapOptionType::ETag => Ok(CoapOption::ETag(value.into_boxed_slice())),
+ CoapOptionType::IfNoneMatch => Ok(CoapOption::IfNoneMatch),
+ CoapOptionType::UriPort => Ok(CoapOption::UriPort(decode_var_len_u16(value.as_slice()))),
+ CoapOptionType::LocationPath => Ok(CoapOption::LocationPath(String::from_utf8(value)?)),
+ CoapOptionType::UriPath => Ok(CoapOption::UriPath(String::from_utf8(value)?)),
+ CoapOptionType::ContentFormat => {
+ Ok(CoapOption::ContentFormat(decode_var_len_u16(value.as_slice())))
+ },
+ CoapOptionType::MaxAge => Ok(CoapOption::MaxAge(decode_var_len_u32(value.as_slice()))),
+ CoapOptionType::UriQuery => Ok(CoapOption::UriQuery(String::from_utf8(value)?)),
+ CoapOptionType::Accept => Ok(CoapOption::Accept(decode_var_len_u16(value.as_slice()))),
+ CoapOptionType::LocationQuery => Ok(CoapOption::LocationQuery(String::from_utf8(value)?)),
+ CoapOptionType::ProxyUri => Ok(CoapOption::ProxyUri(String::from_utf8(value)?)),
+ CoapOptionType::ProxyScheme => Ok(CoapOption::ProxyScheme(String::from_utf8(value)?)),
+ CoapOptionType::Size1 => Ok(CoapOption::Size1(decode_var_len_u32(value.as_slice()))),
+ CoapOptionType::Size2 => Ok(CoapOption::Size2(decode_var_len_u32(value.as_slice()))),
+ CoapOptionType::Block1 => Ok(CoapOption::Block1(decode_var_len_u32(value.as_slice()))),
+ CoapOptionType::Block2 => Ok(CoapOption::Block2(decode_var_len_u32(value.as_slice()))),
+ CoapOptionType::HopLimit => Ok(CoapOption::HopLimit(decode_var_len_u16(value.as_slice()))),
+ CoapOptionType::NoResponse => {
+ Ok(CoapOption::NoResponse(decode_var_len_u8(value.as_slice()) as NoResponse))
+ },
+ CoapOptionType::Observe => Ok(CoapOption::Observe(decode_var_len_u32(value.as_slice()))),
+ CoapOptionType::Oscore => Ok(CoapOption::Oscore(value.into_boxed_slice())),
+ CoapOptionType::Echo => Ok(CoapOption::Echo(value.into_boxed_slice())),
+ CoapOptionType::RTag => Ok(CoapOption::RTag(value.into_boxed_slice())),
+ CoapOptionType::QBlock1 => Ok(CoapOption::QBlock1(decode_var_len_u32(value.as_slice()))),
+ CoapOptionType::QBlock2 => Ok(CoapOption::QBlock2(decode_var_len_u32(value.as_slice()))),
+ }
+ },
+ _ => Ok(CoapOption::Other(type_, value.into_boxed_slice())),
+ }
+ }
+}
+
+/// Constructs a path string from a [Vec] of strings containing the separate path components.
+pub(crate) fn construct_path_string(path_components: Vec) -> String {
+ path_components.into_iter().fold(String::new(), |mut a: String, v| {
+ // Writing to a String _shouldn't_ cause an error.
+ // If it does, something is terribly wrong and we should panic.
+ write!(&mut a, "/{}", v).expect("unable to create path string");
+ a
+ })
+}
+
+/// Constructs a query string from a [Vec] of strings containing the separate query components.
+pub(crate) fn construct_query_string(query_components: Vec) -> String {
+ let mut iter = query_components.iter();
+ let mut out_str = String::from("?");
+ if let Some(q) = iter.next() {
+ out_str = out_str + q
+ }
+ for q in iter {
+ out_str += format!("&{}", q).as_ref();
+ }
+ out_str
}
/// Interface for CoAP messages common between requests, responses and other messages.
diff --git a/libcoap/src/message/request.rs b/libcoap/src/message/request.rs
index 27313ff6..67a00ac8 100644
--- a/libcoap/src/message/request.rs
+++ b/libcoap/src/message/request.rs
@@ -7,145 +7,29 @@
* See the README as well as the LICENSE file for more information.
*/
-use std::fmt::{Display, Formatter};
use std::str::FromStr;
-use url::Url;
-
use crate::{
- error::{MessageConversionError, MessageTypeError, OptionValueError},
+ error::{MessageConversionError, MessageTypeError},
message::{CoapMessage, CoapMessageCommon, CoapOption},
protocol::{
CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionType, CoapRequestCode, ContentFormat, ETag, HopLimit,
NoResponse, Observe,
},
- types::{CoapUri, CoapUriHost, CoapUriScheme},
+ types::{CoapUri, CoapUriScheme},
};
-
-pub const MAX_URI_SEGMENT_LENGTH: usize = 255;
-pub const MAX_PROXY_URI_LENGTH: usize = 1034;
-
-/// Internal representation of a CoAP URI that can be used for requests
-#[derive(Clone, Eq, PartialEq, Hash, Debug)]
-enum CoapRequestUri {
- Request(CoapUri),
- Proxy(CoapUri),
-}
-
-impl Display for CoapRequestUri {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- CoapRequestUri::Request(v) => f.write_fmt(format_args!("Request URI: {}", v)),
- CoapRequestUri::Proxy(v) => f.write_fmt(format_args!("Proxy URI: {}", v)),
- }
- }
-}
-
-impl CoapRequestUri {
- /// Creates a new request URI from the given [CoapUri], returning an [OptionValueError] if the URI
- /// contains invalid values for request URIs.
- // Using unwrap_or_else here will give us an error because we want to use an iterator that
- // outlives its Vec, so we have to use unwrap_or here.
- #[allow(clippy::or_fun_call)]
- pub fn new_request_uri(uri: CoapUri) -> Result {
- if uri
- .path_iter()
- .unwrap_or(vec![].iter())
- .chain(uri.query_iter().unwrap_or(vec![].iter()))
- .any(|x| x.len() > MAX_URI_SEGMENT_LENGTH)
- {
- return Err(OptionValueError::TooLong);
- }
- Ok(CoapRequestUri::Request(uri))
- }
-
- /// Creates a new request proxy URI from the given CoapUri, returning an OptionValueError if
- /// the URI contains invalid values for proxy URIs.
- pub fn new_proxy_uri(uri: CoapUri) -> Result {
- if uri.scheme().is_none() || uri.host().is_none() {
- return Err(OptionValueError::IllegalValue);
- }
- if CoapRequestUri::generate_proxy_uri_string(&uri).len() > MAX_PROXY_URI_LENGTH {
- return Err(OptionValueError::TooLong);
- }
- Ok(CoapRequestUri::Proxy(uri))
- }
-
- /// Generate a proxy URI string corresponding to this request URI.
- fn generate_proxy_uri_string(uri: &CoapUri) -> String {
- let mut proxy_uri_string = format!(
- "{}://{}",
- uri.scheme().unwrap().to_string().as_str(),
- uri.host().unwrap().to_string().as_str()
- );
- if let Some(port) = uri.port() {
- proxy_uri_string.push_str(format!(":{}", port).as_str());
- }
- if let Some(path) = uri.path_iter() {
- path.for_each(|path_component| {
- proxy_uri_string.push_str(format!("/{}", path_component).as_str());
- });
- }
- if let Some(query) = uri.query_iter() {
- let mut separator_char = '?';
- query.for_each(|query_option| {
- proxy_uri_string.push_str(format!("{}{}", separator_char, query_option).as_str());
- separator_char = '&';
- });
- }
- proxy_uri_string
- }
-
- /// Converts this request URI into a [`Vec`] that can be added to a message.
- pub fn into_options(self) -> Vec {
- let mut options = Vec::new();
- match self {
- CoapRequestUri::Request(mut uri) => {
- if let Some(host) = uri.host() {
- options.push(CoapOption::UriHost(host.to_string()))
- }
- if let Some(port) = uri.port() {
- options.push(CoapOption::UriPort(port))
- }
- if let Some(path) = uri.drain_path_iter() {
- options.extend(path.map(CoapOption::UriPath))
- }
- if let Some(query) = uri.drain_query_iter() {
- options.extend(query.map(CoapOption::UriQuery))
- }
- },
- CoapRequestUri::Proxy(uri) => {
- options.push(CoapOption::ProxyUri(CoapRequestUri::generate_proxy_uri_string(&uri)))
- },
- }
- options
- }
-
- /// Returns an immutable reference to the underlying URI.
- pub fn as_uri(&self) -> &CoapUri {
- match self {
- CoapRequestUri::Request(uri) => uri,
- CoapRequestUri::Proxy(uri) => uri,
- }
- }
-}
-
-impl TryFrom for CoapRequestUri {
- type Error = OptionValueError;
-
- fn try_from(value: CoapUri) -> Result {
- CoapRequestUri::new_request_uri(value)
- }
-}
+use crate::error::OptionValueError;
+use crate::message::{construct_path_string, construct_query_string};
+use crate::session::CoapSessionCommon;
/// Representation of a CoAP request message.
///
/// This struct wraps around the more direct [CoapMessage] and allows easier definition of typical
/// options used in requests.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CoapRequest {
pdu: CoapMessage,
- uri: Option,
+ uri: CoapUri,
accept: Option,
etag: Option>,
if_match: Option>,
@@ -160,15 +44,16 @@ impl CoapRequest {
/// Creates a new CoAP request with the given message type and code.
///
/// Returns an error if the given message type is not allowed for CoAP requests (the only
- /// allowed message types are [CoapMessageType::Con] and [CoapMessageType::Non]).
- pub fn new(type_: CoapMessageType, code: CoapRequestCode) -> Result {
+ /// allowed message types are [CoapMessageType::Con] and [CoapMessageType::Non]) or the request
+ /// URI is malformed.
+ pub fn new(type_: CoapMessageType, code: CoapRequestCode, uri: CoapUri) -> Result {
match type_ {
CoapMessageType::Con | CoapMessageType::Non => {},
v => return Err(MessageTypeError::InvalidForMessageCode(v)),
}
Ok(CoapRequest {
pdu: CoapMessage::new(type_, code.into()),
- uri: None,
+ uri,
accept: None,
etag: None,
if_match: None,
@@ -312,48 +197,18 @@ impl CoapRequest {
self.observe = observe;
}
- /// Returns the CoAP URI that is requested (either a normal request URI or a proxy URI)
- pub fn uri(&self) -> Option<&CoapUri> {
- self.uri.as_ref().map(|v| v.as_uri())
- }
-
- /// Sets the URI requested in this request.
- ///
- /// The request URI must not have a scheme defined, and path segments, query segments and the
- /// host itself each have to be smaller than 255 characters.
- ///
- /// If the URI has an invalid format, an [OptionValueError] is returned.
- ///
- /// This method overrides any previously set proxy URI.
- pub fn set_uri>(&mut self, uri: Option) -> Result<(), OptionValueError> {
- let uri = uri.map(Into::into);
- if let Some(uri) = uri {
- self.uri = Some(CoapRequestUri::new_request_uri(uri)?)
- }
- Ok(())
- }
-
- /// Sets the proxy URI requested in this request.
- ///
- /// The proxy URI must be an absolute URL with a schema valid for CoAP proxying (CoAP(s) or
- /// HTTP(s)),
- /// The proxy URI must not be longer than 1023 characters.
- ///
- /// If the URI has an invalid format, an [OptionValueError] is returned.
- ///
- /// This method overrides any previously set request URI.
- pub fn set_proxy_uri>(&mut self, uri: Option) -> Result<(), OptionValueError> {
- let uri = uri.map(Into::into);
- if let Some(uri) = uri {
- self.uri = Some(CoapRequestUri::new_proxy_uri(uri)?)
- }
- Ok(())
+ /// Returns the CoAP URI that is requested.
+ pub fn uri(&self) -> &CoapUri {
+ &self.uri
}
/// Parses the given [CoapMessage] into a CoapRequest.
///
/// Returns a [MessageConversionError] if the provided PDU cannot be parsed into a request.
- pub fn from_message(mut pdu: CoapMessage) -> Result {
+ pub fn from_message<'a>(
+ mut pdu: CoapMessage,
+ session: &impl CoapSessionCommon<'a>,
+ ) -> Result {
let mut host = None;
let mut port = None;
let mut path = None;
@@ -391,7 +246,7 @@ impl CoapRequest {
CoapOptionType::UriHost,
));
}
- host = Some(value.clone());
+ host = Some(value.clone().into_bytes());
},
CoapOption::UriPort(value) => {
if port.is_some() {
@@ -531,29 +386,31 @@ impl CoapRequest {
CoapOptionType::ProxyUri,
));
}
- let uri = if let Some(proxy_uri) = proxy_uri {
- Some(CoapUri::try_from_url(Url::parse(&proxy_uri)?)?)
+ let uri = if let Some(v) = proxy_uri {
+ CoapUri::try_from_str_proxy(v.as_str())
} else {
- Some(CoapUri::new(
- proxy_scheme,
- host.map(|v| CoapUriHost::from_str(v.as_str()).unwrap()),
- port,
- path,
- query,
- ))
- }
- .map(|uri| {
- if uri.scheme().is_some() {
- CoapRequestUri::new_proxy_uri(uri)
- } else {
- CoapRequestUri::new_request_uri(uri)
+ let path_str = path.map(construct_path_string);
+ let query_str = query.map(construct_query_string);
+
+ match proxy_scheme {
+ Some(scheme) => CoapUri::new_proxy(
+ scheme,
+ host.as_deref().unwrap_or(&[]),
+ port.unwrap_or(0),
+ path_str.as_ref().map(|v| v.as_bytes()),
+ query_str.as_ref().map(|v| v.as_bytes()),
+ ),
+ None => CoapUri::new(
+ session.proto().into(),
+ host.as_deref().unwrap_or(&[]),
+ port.unwrap_or(0),
+ path_str.as_ref().map(|v| v.as_bytes()),
+ query_str.as_ref().map(|v| v.as_bytes()),
+ ),
}
- });
- let uri = if let Some(uri) = uri {
- Some(uri.map_err(|e| MessageConversionError::InvalidOptionValue(None, e))?)
- } else {
- None
- };
+ }
+ .map_err(|e| MessageConversionError::InvalidOptionValue(None, OptionValueError::UriParsing(e)))?;
+
Ok(CoapRequest {
pdu,
uri,
@@ -570,9 +427,12 @@ impl CoapRequest {
/// Converts this request into a [CoapMessage] that can be sent over a [CoapSession](crate::session::CoapSession).
pub fn into_message(mut self) -> CoapMessage {
- if let Some(req_uri) = self.uri {
- req_uri.into_options().into_iter().for_each(|v| self.pdu.add_option(v));
+ if self.uri.is_proxy() {
+ self.pdu.add_option(CoapOption::ProxyUri(
+ self.uri.scheme().expect("Parsed CoAP URI must have scheme").to_string(),
+ ))
}
+ self.uri.into_options().into_iter().for_each(|v| self.pdu.add_option(v));
if let Some(accept) = self.accept {
self.pdu.add_option(CoapOption::Accept(accept))
}
diff --git a/libcoap/src/message/response.rs b/libcoap/src/message/response.rs
index 2f75f7bf..c3a0cc9a 100644
--- a/libcoap/src/message/response.rs
+++ b/libcoap/src/message/response.rs
@@ -7,71 +7,21 @@
* See the README as well as the LICENSE file for more information.
*/
-use std::fmt::Display;
-use std::fmt::Formatter;
-
use crate::error::{MessageConversionError, MessageTypeError, OptionValueError};
-use crate::message::{CoapMessage, CoapMessageCommon, CoapOption};
+use crate::message::{CoapMessage, CoapMessageCommon, CoapOption, construct_path_string, construct_query_string};
use crate::protocol::{
CoapMessageCode, CoapMessageType, CoapOptionType, CoapResponseCode, ContentFormat, Echo, ETag, MaxAge, Observe,
};
use crate::types::CoapUri;
-/// Internal representation of a CoAP URI that can be used as a response location.
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
-pub struct CoapResponseLocation(CoapUri);
-
-impl Display for CoapResponseLocation {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!("Response Location: {}", self.0))
- }
-}
-
-impl CoapResponseLocation {
- /// Creates a new response location from the given [CoapUri], returning an [OptionValueError] if
- /// the URI contains invalid values for response locations.
- pub fn new_response_location(uri: CoapUri) -> Result {
- if uri.scheme().is_some() || uri.host().is_some() || uri.port().is_some() {
- return Err(OptionValueError::IllegalValue);
- }
- Ok(CoapResponseLocation(uri))
- }
-
- /// Converts this response location into a [`Vec`] that can be added to a message.
- pub fn into_options(self) -> Vec {
- let mut options = Vec::new();
- let mut uri = self.0;
- if let Some(path) = uri.drain_path_iter() {
- options.extend(path.map(CoapOption::LocationPath));
- }
- if let Some(query) = uri.drain_query_iter() {
- options.extend(query.map(CoapOption::LocationQuery));
- }
- options
- }
-
- /// Returns an immutable reference to the underlying URI.
- pub fn as_uri(&self) -> &CoapUri {
- &self.0
- }
-}
-
-impl TryFrom for CoapResponseLocation {
- type Error = OptionValueError;
-
- fn try_from(value: CoapUri) -> Result {
- CoapResponseLocation::new_response_location(value)
- }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CoapResponse {
pdu: CoapMessage,
content_format: Option,
max_age: Option,
etag: Option,
echo: Option,
- location: Option,
+ location: Option,
observe: Option,
}
@@ -182,7 +132,7 @@ impl CoapResponse {
}
/// Returns the "Location" option value for this request.
- pub fn location(&self) -> Option<&CoapResponseLocation> {
+ pub fn location(&self) -> Option<&CoapUri> {
self.location.as_ref()
}
@@ -201,7 +151,7 @@ impl CoapResponse {
pub fn set_location>(&mut self, uri: Option) -> Result<(), OptionValueError> {
let uri = uri.map(Into::into);
if let Some(uri) = uri {
- self.location = Some(CoapResponseLocation::new_response_location(uri)?)
+ self.location = Some(uri)
}
Ok(())
}
@@ -373,16 +323,12 @@ impl CoapResponse {
}
}
let location = if location_path.is_some() || location_query.is_some() {
- Some(
- CoapResponseLocation::new_response_location(CoapUri::new(
- None,
- None,
- None,
- location_path,
- location_query,
- ))
- .map_err(|e| MessageConversionError::InvalidOptionValue(None, e))?,
- )
+ let path_str = location_path.map(construct_path_string);
+ let query_str = location_query.map(construct_query_string);
+ Some(CoapUri::new_relative(
+ path_str.as_ref().map(|v| v.as_bytes()),
+ query_str.as_ref().map(|v| v.as_bytes()),
+ )?)
} else {
None
};
@@ -402,6 +348,7 @@ impl CoapMessageCommon for CoapResponse {
/// Sets the message code of this response.
///
/// # Panics
+ ///
/// Panics if the provided message code is not a response code.
fn set_code>(&mut self, code: C) {
match code.into() {
diff --git a/libcoap/src/resource.rs b/libcoap/src/resource.rs
index 87a0d4a7..0629fde5 100644
--- a/libcoap/src/resource.rs
+++ b/libcoap/src/resource.rs
@@ -88,7 +88,7 @@ pub unsafe fn prepare_resource_handler_data<'a, D: Any + ?Sized + Debug>(
let resource_tmp = CoapFfiRcCell::clone_raw_weak(coap_resource_get_userdata(raw_resource));
let resource = CoapResource::from(resource_tmp);
let session = CoapServerSession::from_raw(raw_session);
- let request = CoapMessage::from_raw_pdu(raw_incoming_pdu).and_then(CoapRequest::from_message);
+ let request = CoapMessage::from_raw_pdu(raw_incoming_pdu).and_then(|v| CoapRequest::from_message(v, &session));
let response = CoapMessage::from_raw_pdu(raw_response_pdu).and_then(CoapResponse::from_message);
match (request, response) {
(Ok(request), Ok(response)) => Ok((resource, session, request, response)),
diff --git a/libcoap/src/session/mod.rs b/libcoap/src/session/mod.rs
index 8cd8617c..c72477f6 100644
--- a/libcoap/src/session/mod.rs
+++ b/libcoap/src/session/mod.rs
@@ -7,8 +7,6 @@
* See the README as well as the LICENSE file for more information.
*/
-use std::borrow::BorrowMut;
-use std::cell::{Ref, RefMut};
use std::{
any::Any,
collections::{HashMap, VecDeque},
@@ -16,6 +14,8 @@ use std::{
net::{SocketAddr, ToSocketAddrs},
rc::Rc,
};
+use std::borrow::BorrowMut;
+use std::cell::{Ref, RefMut};
use rand::Rng;
@@ -30,22 +30,19 @@ use libcoap_sys::{
coap_session_t, coap_session_type_t,
};
-#[cfg(feature = "dtls")]
-use crate::crypto::{CoapCryptoPskData, CoapCryptoPskIdentity};
-
-use crate::message::request::CoapRequest;
-use crate::message::response::CoapResponse;
use crate::{
error::{MessageConversionError, SessionGetAppDataError},
message::{CoapMessage, CoapMessageCommon},
protocol::CoapToken,
types::{CoapAddress, CoapMessageId, CoapProtocol, IfIndex, MaxRetransmit},
};
+#[cfg(feature = "dtls")]
+use crate::crypto::{CoapCryptoPskData, CoapCryptoPskIdentity};
+use crate::message::request::CoapRequest;
+use crate::message::response::CoapResponse;
pub use self::client::CoapClientSession;
-
-pub(self) use self::sealed::{CoapSessionCommonInternal, CoapSessionInnerProvider};
-
+use self::sealed::{CoapSessionCommonInternal, CoapSessionInnerProvider};
pub use self::server::CoapServerSession;
pub mod client;
diff --git a/libcoap/src/types.rs b/libcoap/src/types.rs
index 6b0a2b74..7009abf9 100644
--- a/libcoap/src/types.rs
+++ b/libcoap/src/types.rs
@@ -10,34 +10,40 @@
//! Types required for conversion between libcoap C library abstractions and Rust types.
use std::{
- convert::Infallible,
fmt::Debug,
mem::MaybeUninit,
- net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
+ net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
os::raw::c_int,
- slice::Iter,
str::FromStr,
- vec::Drain,
};
+use std::ffi::CString;
use std::fmt::{Display, Formatter};
+use std::marker::PhantomPinned;
+use std::ops::{Deref, DerefMut};
+use std::pin::Pin;
use libc::{AF_INET, AF_INET6, c_ushort, in6_addr, in_addr, sa_family_t, sockaddr_in, sockaddr_in6, socklen_t};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
-use url::{Host, Url};
+#[cfg(feature = "url")]
+use url::Url;
use libcoap_sys::{
- coap_address_t, coap_mid_t, coap_proto_t,
+ coap_address_t, coap_delete_optlist, coap_mid_t, coap_proto_t,
coap_proto_t::{COAP_PROTO_DTLS, COAP_PROTO_NONE, COAP_PROTO_TCP, COAP_PROTO_TLS, COAP_PROTO_UDP},
+ coap_split_proxy_uri, coap_split_uri, coap_str_const_t, coap_string_equal, coap_uri_into_options,
COAP_URI_SCHEME_SECURE_MASK,
coap_uri_scheme_t,
coap_uri_scheme_t::{
COAP_URI_SCHEME_COAP, COAP_URI_SCHEME_COAP_TCP, COAP_URI_SCHEME_COAPS, COAP_URI_SCHEME_COAPS_TCP,
COAP_URI_SCHEME_HTTP, COAP_URI_SCHEME_HTTPS,
- },
+ }, coap_uri_t,
};
+use libcoap_sys::coap_uri_scheme_t::{COAP_URI_SCHEME_COAP_WS, COAP_URI_SCHEME_COAPS_WS};
use crate::error::UriParsingError;
+use crate::message::CoapOption;
+use crate::protocol::UriPort;
/// Interface index used internally by libcoap to refer to an endpoint.
pub type IfIndex = c_int;
@@ -211,6 +217,8 @@ pub enum CoapUriScheme {
CoapsTcp = COAP_URI_SCHEME_COAPS_TCP as u32,
Http = COAP_URI_SCHEME_HTTP as u32,
Https = COAP_URI_SCHEME_HTTPS as u32,
+ CoapWs = COAP_URI_SCHEME_COAP_WS as u32,
+ CoapsWs = COAP_URI_SCHEME_COAPS_WS as u32,
}
impl CoapUriScheme {
@@ -219,7 +227,7 @@ impl CoapUriScheme {
}
pub fn from_raw_scheme(scheme: coap_uri_scheme_t) -> CoapUriScheme {
- num_traits::FromPrimitive::from_u32(scheme as u32).expect("unknown scheme")
+ FromPrimitive::from_u32(scheme as u32).expect("unknown scheme")
}
}
@@ -234,7 +242,9 @@ impl FromStr for CoapUriScheme {
"coaps+tcp" => Ok(CoapUriScheme::CoapsTcp),
"http" => Ok(CoapUriScheme::Http),
"https" => Ok(CoapUriScheme::Https),
- _ => Err(UriParsingError::NotACoapScheme),
+ "coap+ws" => Ok(CoapUriScheme::CoapWs),
+ "coaps+ws" => Ok(CoapUriScheme::CoapsWs),
+ _ => Err(UriParsingError::NotACoapScheme(s.to_string())),
}
}
}
@@ -248,6 +258,8 @@ impl Display for CoapUriScheme {
CoapUriScheme::CoapsTcp => "coaps+tcp",
CoapUriScheme::Http => "http",
CoapUriScheme::Https => "https",
+ CoapUriScheme::CoapWs => "coap+ws",
+ CoapUriScheme::CoapsWs => "coaps+ws",
})
}
}
@@ -258,153 +270,567 @@ impl From for CoapUriScheme {
}
}
-/// Representation of the host part of a CoAP request.
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-pub enum CoapUriHost {
- IpLiteral(IpAddr),
- Name(String),
-}
-
-impl From> for CoapUriHost {
- fn from(host: Host) -> Self {
- match host {
- Host::Domain(d) => CoapUriHost::Name(d.to_string()),
- Host::Ipv4(addr) => CoapUriHost::IpLiteral(IpAddr::V4(addr)),
- Host::Ipv6(addr) => CoapUriHost::IpLiteral(IpAddr::V6(addr)),
+impl From for CoapUriScheme {
+ fn from(value: CoapProtocol) -> Self {
+ match value {
+ CoapProtocol::None | CoapProtocol::Udp => CoapUriScheme::Coap,
+ CoapProtocol::Dtls => CoapUriScheme::Coaps,
+ CoapProtocol::Tcp => CoapUriScheme::CoapTcp,
+ CoapProtocol::Tls => CoapUriScheme::CoapsTcp,
}
}
}
-impl FromStr for CoapUriHost {
- type Err = Infallible;
-
- fn from_str(s: &str) -> Result {
- Ok(IpAddr::from_str(s).map_or_else(|_| CoapUriHost::Name(s.to_string()), CoapUriHost::IpLiteral))
- }
+/// Representation of a URI for CoAP requests, responses or proxy URIs.
+///
+/// See https://datatracker.ietf.org/doc/html/rfc7252#section-6 for a description of how a URI
+/// should look like.
+///
+/// # Examples
+/// The easiest way to instantiate a request or location CoAP URI is by parsing a string (either
+/// using the [FromStr] implementation or using [CoapUri::try_from_str]):
+/// ```
+/// use libcoap_rs::error::UriParsingError;
+/// use libcoap_rs::types::{CoapUri, CoapUriScheme};
+///
+/// let uri: CoapUri = "coap://example.com:4711/foo/bar?answer=42".parse()?;
+///
+/// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
+/// assert_eq!(uri.host(), Some("example.com".as_bytes()));
+/// assert_eq!(uri.port(), Some(4711));
+/// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
+/// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
+/// assert!(!uri.is_proxy());
+///
+/// # Result::<(), UriParsingError>::Ok(())
+/// ```
+///
+/// Alternatively, a [CoapUri] may be constructed from its parts using [CoapUri::new] or
+/// [CoapUri::new_relative] or from a [Url] (requires the `url` feature), refer to the method level
+/// documentation for more information.
+///
+/// If you want to create a proxy URI, refer to the method-level documentation [CoapUri::new_proxy],
+/// [CoapUri::try_from_str_proxy] or [CoapUri::try_from_url_proxy].
+///
+/// # Note on URI Length Limits
+///
+/// Due to [the specified limits](https://datatracker.ietf.org/doc/html/rfc7252#section-5.10)
+/// of CoAP option lengths, URI path components for a request/location URI must not exceed 255
+/// bytes each, i.e. a full path with more than 255 bytes is fine, but each individual path segment
+/// must be smaller than 255 bytes.
+///
+/// For proxy URIs, there is a length limit of 255 bytes for the scheme and 1034 bytes for the
+/// remainder of the URI (not for each segment separately).
+#[derive(Debug)]
+pub struct CoapUri {
+ is_proxy: bool,
+ raw_uri: coap_uri_t,
+ uri_str: Pin,
}
-impl Display for CoapUriHost {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- f.write_str(
- match self {
- CoapUriHost::IpLiteral(addr) => addr.to_string(),
- CoapUriHost::Name(host) => host.clone(),
- }
- .as_str(),
- )
+#[derive(Debug)]
+struct CoapUriInner(CString, PhantomPinned);
+
+impl Deref for CoapUriInner {
+ type Target = CString;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
}
}
-/// Representation of a URI for CoAP requests or responses.
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-pub struct CoapUri {
- scheme: Option,
- host: Option,
- port: Option,
- path: Option>,
- query: Option>,
+impl DerefMut for CoapUriInner {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
}
impl CoapUri {
- /// Creates a new CoapUri from the given components.
+ /// Creates a new [CoapUri] for use as a request or location URI from its constituent parts.
+ ///
+ /// # Errors
+ /// May fail if the provided fields do not represent a valid relative URI or if the arguments
+ /// exceed maximum lengths (see the struct level documentation).
+ ///
+ /// # Examples
+ /// ```
+ /// use libcoap_rs::error::UriParsingError;
+ /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
+ ///
+ /// let uri: CoapUri = CoapUri::new(
+ /// CoapUriScheme::Coap,
+ /// "example.com".as_bytes(),
+ /// 4711,
+ /// Some("/foo/bar".as_bytes()),
+ /// Some("?answer=42".as_bytes())
+ /// )?;
+ ///
+ /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
+ /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
+ /// assert_eq!(uri.port(), Some(4711));
+ /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
+ /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
+ /// assert!(!uri.is_proxy());
+ ///
+ /// # Result::<(), UriParsingError>::Ok(())
+ /// ```
pub fn new(
- scheme: Option,
- host: Option,
- port: Option,
- path: Option>,
- query: Option>,
- ) -> CoapUri {
- CoapUri {
- scheme,
- host,
- port,
- path,
- query,
- }
+ scheme: CoapUriScheme,
+ host: &[u8],
+ port: u16,
+ path: Option<&[u8]>,
+ query: Option<&[u8]>,
+ ) -> Result {
+ let (uri_str, _, _, _) =
+ Self::construct_uri_string_from_parts(scheme, host, port, path.unwrap_or(&[b'/']), query.unwrap_or(&[]))?;
+ // SAFETY: coap_split_uri is one of the allowed functions.
+ unsafe { CoapUri::create_parsed_uri(uri_str, coap_split_uri, false) }
+ }
+
+ /// Creates a new [CoapUri] for use as a proxy URI from its constituent parts.
+ ///
+ /// # Errors
+ /// May fail if the provided fields do not represent a valid relative URI or if the arguments
+ /// exceed maximum lengths (see the struct level documentation).
+ /// # Examples
+ /// ```
+ /// use libcoap_rs::error::UriParsingError;
+ /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
+ ///
+ /// let uri: CoapUri = CoapUri::new_proxy(
+ /// CoapUriScheme::Coap,
+ /// "example.com".as_bytes(),
+ /// 4711,
+ /// Some("/foo/bar".as_bytes()),
+ /// Some("?answer=42".as_bytes())
+ /// )?;
+ ///
+ /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
+ /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
+ /// assert_eq!(uri.port(), Some(4711));
+ /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
+ /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
+ /// assert!(uri.is_proxy());
+ ///
+ /// # Result::<(), UriParsingError>::Ok(())
+ /// ```
+ pub fn new_proxy(
+ scheme: CoapUriScheme,
+ host: &[u8],
+ port: u16,
+ path: Option<&[u8]>,
+ query: Option<&[u8]>,
+ ) -> Result {
+ let (uri_str, _, _, _) =
+ Self::construct_uri_string_from_parts(scheme, host, port, path.unwrap_or(&[b'/']), query.unwrap_or(&[]))?;
+ // SAFETY: coap_split_proxy_uri is one of the allowed functions.
+ unsafe { CoapUri::create_parsed_uri(uri_str, coap_split_proxy_uri, true) }
+ }
+
+ /// Attempts to convert the provided `path` and `query` into a relative [CoapUri] suitable as a
+ /// request/location URI.
+ ///
+ /// # Errors
+ /// May fail if the provided `path` and `query` do not represent a valid relative URI or if the
+ /// arguments exceed maximum lengths (see the struct level documentation).
+ ///
+ /// # Examples
+ /// ```
+ /// use libcoap_rs::error::UriParsingError;
+ /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
+ ///
+ /// let uri: CoapUri = CoapUri::new_relative(
+ /// Some("/foo/bar".as_bytes()),
+ /// Some("?answer=42".as_bytes())
+ /// )?;
+ ///
+ /// assert_eq!(uri.scheme(), None);
+ /// assert_eq!(uri.host(), None);
+ /// assert_eq!(uri.port(), Some(5683));
+ /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
+ /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
+ /// assert!(!uri.is_proxy());
+ ///
+ /// # Result::<(), UriParsingError>::Ok(())
+ /// ```
+ pub fn new_relative(path: Option<&[u8]>, query: Option<&[u8]>) -> Result {
+ CoapUri::new(CoapUriScheme::Coap, &[], 0, path, query)
+ }
+
+ /// Attempts to convert the provided `uri_str` into a [CoapUri] suitable as a request/location
+ /// URI.
+ ///
+ /// # Errors
+ /// May fail if the provided `uri_str` is not a valid URI or if the URI components exceed
+ /// maximum lengths (see the struct level documentation).
+ ///
+ /// # Examples
+ /// ```
+ /// use libcoap_rs::error::UriParsingError;
+ /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
+ ///
+ /// let uri: CoapUri = CoapUri::try_from_str("coap://example.com:4711/foo/bar?answer=42")?;
+ ///
+ /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
+ /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
+ /// assert_eq!(uri.port(), Some(4711));
+ /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
+ /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
+ /// assert!(!uri.is_proxy());
+ ///
+ /// # Result::<(), UriParsingError>::Ok(())
+ /// ```
+ pub fn try_from_str(uri_str: &str) -> Result {
+ // SAFETY: coap_split_uri is one of the allowed functions.
+ unsafe { CoapUri::create_parsed_uri(CString::new(uri_str)?, coap_split_uri, false) }
+ }
+
+ /// Attempts to convert the provided `uri_str` into a [CoapUri] suitable as a proxy URI.
+ ///
+ /// # Errors
+ /// May fail if the provided `uri_str` is not a valid proxy URI or if the URI components exceed
+ /// maximum lengths (see the struct level documentation).
+ ///
+ /// # Examples
+ /// ```
+ /// use libcoap_rs::error::UriParsingError;
+ /// use libcoap_rs::types::{CoapUri, CoapUriScheme};
+ ///
+ /// let uri: CoapUri = CoapUri::try_from_str_proxy("coap://example.com:4711/foo/bar?answer=42")?;
+ ///
+ /// assert_eq!(uri.scheme(), Some(CoapUriScheme::Coap));
+ /// assert_eq!(uri.host(), Some("example.com".as_bytes()));
+ /// assert_eq!(uri.port(), Some(4711));
+ /// assert_eq!(uri.path(), Some("foo/bar".as_bytes()));
+ /// assert_eq!(uri.query(), Some("answer=42".as_bytes()));
+ /// assert!(uri.is_proxy());
+ ///
+ /// # Result::<(), UriParsingError>::Ok(())
+ /// ```
+ pub fn try_from_str_proxy(uri_str: &str) -> Result {
+ // SAFETY: coap_split_proxy_uri is one of the allowed functions.
+ unsafe { CoapUri::create_parsed_uri(CString::new(uri_str)?, coap_split_proxy_uri, true) }
}
/// Attempts to convert a [Url] into a [CoapUri].
///
/// # Errors
- /// May fail if the provided Url has an invalid scheme.
- pub fn try_from_url(url: Url) -> Result {
- let path: Vec = url
- .path()
- .split('/')
- .map(String::from)
- .filter(|v| !v.is_empty())
- .collect();
- let path = if path.is_empty() { None } else { Some(path) };
-
- let query: Vec = url.query_pairs().map(|(k, v)| format!("{}={}", k, v)).collect();
- let query = if query.is_empty() { None } else { Some(query) };
- Ok(CoapUri {
- scheme: Some(CoapUriScheme::from_str(url.scheme())?),
- host: url.host().map(|h| h.into()),
- port: url.port(),
- path,
- query,
- })
+ /// May fail if the provided Url is not a valid URI supported by libcoap or if the URI
+ /// components exceed maximum lengths (see the struct level documentation).
+ #[cfg(feature = "url")]
+ pub fn try_from_url(url: &Url) -> Result {
+ Self::try_from_str(url.as_str())
+ }
+
+ /// Attempts to convert a [Url] into a proxy [CoapUri].
+ ///
+ /// # Errors
+ /// May fail if the provided Url is not a valid proxy URI supported by libcoap or if the URI
+ /// components exceed maximum lengths (see the struct level documentation).
+ #[cfg(feature = "url")]
+ pub fn try_from_url_proxy(url: &Url) -> Result {
+ Self::try_from_str_proxy(url.as_str())
}
/// Returns the scheme part of this URI.
- pub fn scheme(&self) -> Option<&CoapUriScheme> {
- self.scheme.as_ref()
+ pub fn scheme(&self) -> Option {
+ // URIs can either be absolute or relative. If they are relative, the scheme is also not
+ // set (but defaults to CoAP as the default enum value is 0).
+ self.host()?;
+ Some(CoapUriScheme::from_raw_scheme(self.raw_uri.scheme))
}
/// Returns the host part of this URI.
- pub fn host(&self) -> Option<&CoapUriHost> {
- self.host.as_ref()
+ pub fn host(&self) -> Option<&[u8]> {
+ let raw_str = self.raw_uri.host;
+ if raw_str.length == 0 {
+ return None;
+ }
+ // SAFETY: After construction the fields of self.raw_uri always reference the corresponding
+ // parts of the underlying string, which is pinned. Therefore, the pointer and
+ // length are valid for the lifetime of this struct.
+ Some(unsafe { std::slice::from_raw_parts(raw_str.s, raw_str.length) })
}
/// Returns the port of this URI (if provided).
- pub fn port(&self) -> Option {
- self.port
+ pub fn port(&self) -> Option {
+ match self.raw_uri.port {
+ 0 => None,
+ v => Some(v),
+ }
}
- /// Drains the parts of the URI path of this CoapUri into an iterator.
- pub(crate) fn drain_path_iter(&mut self) -> Option> {
- self.path.as_mut().map(|p| p.drain(..))
+ /// Returns the URI path part of this URI.
+ pub fn path(&self) -> Option<&[u8]> {
+ let raw_str = self.raw_uri.path;
+ if raw_str.s.is_null() {
+ return None;
+ }
+ // SAFETY: After construction the fields of self.raw_uri always reference the corresponding
+ // parts of the underlying string, which is pinned. Therefore, the pointer and
+ // length are valid for the lifetime of this struct.
+ Some(unsafe { std::slice::from_raw_parts(raw_str.s, raw_str.length) })
}
- /// Returns an iterator over the path components of this URI.
- pub fn path_iter(&self) -> Option> {
- self.path.as_ref().map(|p| p.iter())
+ /// Returns the host part of this URI.
+ pub fn query(&self) -> Option<&[u8]> {
+ let raw_str = self.raw_uri.query;
+ if raw_str.s.is_null() {
+ return None;
+ }
+ // SAFETY: After construction the fields of self.raw_uri always reference the corresponding
+ // parts of the underlying string, which is pinned. Therefore, the pointer and
+ // length are valid for the lifetime of this struct.
+ Some(unsafe { std::slice::from_raw_parts(raw_str.s, raw_str.length) })
}
- /// Drains the parts of the URI query of this CoapUri into an iterator.
- pub fn drain_query_iter(&mut self) -> Option> {
- self.query.as_mut().map(|p| p.drain(..))
+ /// Returns whether this URI is a proxy URI.
+ pub fn is_proxy(&self) -> bool {
+ self.is_proxy
}
- /// Returns an iterator over the query components of this URI.
- pub fn query_iter(&self) -> Option> {
- self.query.as_ref().map(|p| p.iter())
+ /// Converts the given URI into a `Vec` of [CoapOption]s that can be added to a
+ /// [crate::message::CoapMessage].
+ pub fn into_options(self) -> Vec {
+ // TODO this is a lot of copying around, however, fixing that would require an entire
+ // rewrite of the option handling code, so it's better kept for a separate PR.
+ let mut buf = vec![0u8; self.path().map(|v| v.len()).unwrap_or(0) + self.query().map(|v| v.len()).unwrap_or(0)];
+ let mut optlist = std::ptr::null_mut();
+ // SAFETY: self.raw_uri is always valid after construction. The destination may be a null
+ // pointer, optlist may be a null pointer at the start (it will be set to a valid
+ // pointer by this call). Buf and create_port_host_opt are set according to the
+ // libcoap documentation.
+ if unsafe {
+ coap_uri_into_options(
+ &self.raw_uri,
+ std::ptr::null(),
+ &mut optlist,
+ 1,
+ buf.as_mut_ptr(),
+ buf.len(),
+ )
+ } < 0
+ {
+ // We have already parsed this URI. If converting it into options fails, something went
+ // terribly wrong.
+ panic!("could not convert valid coap URI into options");
+ }
+ let mut out_opts = Vec::new();
+ while !optlist.is_null() {
+ // SAFETY: coap_uri_into_options should have ensured that optlist is either null or a
+ // valid coap option list. In the former case, we wouldn't be in this loop, in
+ // the latter case calling from_optlist_entry is fine.
+ out_opts.push(unsafe {
+ CoapOption::from_optlist_entry(optlist.as_ref().expect("self-generated options should always be valid"))
+ .expect("self-generated options should always be valid")
+ });
+ optlist = unsafe { *optlist }.next;
+ }
+ // SAFETY: optlist has been set by coap_uri_into_options, which has not returned an error.
+ unsafe {
+ coap_delete_optlist(optlist);
+ }
+ drop(self);
+ out_opts
+ }
+
+ /// Provides a reference to the raw [coap_uri_t] struct represented by this [CoapUri].
+ ///
+ /// Note that while obtaining this struct and reading the fields is safe (which is why this
+ /// method is safe), modifying the referenced URI parts by (unsafely) dereferencing and mutating
+ /// the `const` pointers inside is not.
+ pub fn as_raw_uri(&self) -> &coap_uri_t {
+ &self.raw_uri
+ }
+
+ /// Converts the given `raw_uri` to a new [CoapUri] instance.
+ ///
+ /// This method will create a copy of the provided URI, i.e. `raw_uri` will remain valid and not
+ /// be owned by the created [CoapUri] instance.
+ ///
+ /// # Safety
+ ///
+ /// The provided `raw_uri` must point to a valid instance of [coap_uri_t].
+ /// In particular, the provided pointers for the URI components must also be valid.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided `raw_uri` is null or the provided URI contains a null byte.
+ pub unsafe fn from_raw_uri(raw_uri: *const coap_uri_t, is_proxy: bool) -> CoapUri {
+ // Loosely based on coap_clone_uri.
+ assert!(!raw_uri.is_null());
+ // Clone the actual URI string.
+ let (uri_str_copy, host_pos, path_pos, query_pos) = Self::construct_uri_string_from_parts(
+ CoapUriScheme::from_raw_scheme((*raw_uri).scheme),
+ std::slice::from_raw_parts((*raw_uri).host.s, (*raw_uri).host.length),
+ (*raw_uri).port,
+ std::slice::from_raw_parts((*raw_uri).path.s, (*raw_uri).path.length),
+ std::slice::from_raw_parts((*raw_uri).query.s, (*raw_uri).query.length),
+ )
+ .expect("provided raw URI is invalid");
+
+ let mut result = CoapUri::create_unparsed_uri(
+ CString::new(uri_str_copy).expect("provided raw_uri contains null bytes!"),
+ is_proxy,
+ );
+ result.raw_uri.port = (*raw_uri).port;
+ result.raw_uri.scheme = (*raw_uri).scheme;
+ // Now, _after_ the uri_str is pinned, we can set the new object's raw_uri string fields.
+ result.raw_uri.host = coap_str_const_t {
+ length: (*raw_uri).host.length,
+ s: result.uri_str.as_bytes_with_nul()[host_pos..host_pos + 1].as_ptr(),
+ };
+ result.raw_uri.path = coap_str_const_t {
+ length: (*raw_uri).path.length,
+ s: result.uri_str.as_bytes_with_nul()[path_pos..path_pos + 1].as_ptr(),
+ };
+ result.raw_uri.query = coap_str_const_t {
+ length: (*raw_uri).query.length,
+ s: result.uri_str.as_bytes_with_nul()[query_pos..query_pos + 1].as_ptr(),
+ };
+
+ result
+ }
+
+ /// Create an instance of [CoapUri] with the given `uri_str`, but don't parse the value, i.e.
+ /// the resulting `raw_uri` is not set correctly.
+ fn create_unparsed_uri(uri_str: CString, is_proxy: bool) -> Self {
+ let uri_str = Pin::new(CoapUriInner(uri_str, PhantomPinned));
+ CoapUri {
+ raw_uri: coap_uri_t {
+ host: coap_str_const_t {
+ length: 0,
+ s: std::ptr::null(),
+ },
+ port: 0,
+ path: coap_str_const_t {
+ length: 0,
+ s: std::ptr::null(),
+ },
+ query: coap_str_const_t {
+ length: 0,
+ s: std::ptr::null(),
+ },
+ scheme: coap_uri_scheme_t::COAP_URI_SCHEME_COAP,
+ },
+ uri_str,
+ is_proxy,
+ }
+ }
+
+ /// Create and parse a URI from a CString.
+ ///
+ /// # Safety
+ ///
+ /// parsing_fn must be either coap_split_uri or coap_split_proxy_uri.
+ unsafe fn create_parsed_uri(
+ uri_str: CString,
+ parsing_fn: unsafe extern "C" fn(*const u8, usize, *mut coap_uri_t) -> c_int,
+ is_proxy: bool,
+ ) -> Result {
+ let mut uri = Self::create_unparsed_uri(uri_str, is_proxy);
+
+ // SAFETY: The provided pointers to raw_uri and uri_str are valid.
+ // Because uri_str is pinned (and its type is not Unpin), the pointer locations are always
+ // valid while this object lives, therefore the resulting coap_uri_t remains valid for the
+ // entire lifetime of this object too.
+ if unsafe {
+ parsing_fn(
+ uri.uri_str.as_ptr() as *const u8,
+ libc::strlen(uri.uri_str.as_ptr()),
+ std::ptr::from_mut(&mut uri.raw_uri),
+ )
+ } < 0
+ {
+ return Err(UriParsingError::Unknown);
+ }
+ Ok(uri)
+ }
+
+ /// Constructs a CString representing the given URI parts in a form parsable by libcoap.
+ fn construct_uri_string_from_parts(
+ scheme: CoapUriScheme,
+ host: &[u8],
+ port: u16,
+ path: &[u8],
+ query: &[u8],
+ ) -> Result<(CString, usize, usize, usize), UriParsingError> {
+ // Reconstruct string for scheme.
+ let scheme = if !host.is_empty() {
+ format!("{}://", scheme)
+ } else {
+ String::new()
+ };
+ let port = if port != 0 { format!(":{}", port) } else { String::new() };
+ let parts = [scheme.as_bytes(), host, port.as_bytes(), path, query];
+ let uri_str_len = parts.iter().map(|v| v.len()).sum::();
+
+ let mut uri_str_copy = vec![0u8; uri_str_len];
+ let mut cur;
+ let mut rest = uri_str_copy.as_mut_slice();
+ for part in parts.iter() {
+ (cur, rest) = rest.split_at_mut(part.len());
+ cur.clone_from_slice(part)
+ }
+
+ // The host is index 1 in the parts list
+ let host_pos = parts[..1].iter().map(|v| v.len()).sum();
+ // The path is index 3 in the parts list
+ let path_pos = parts[..3].iter().map(|v| v.len()).sum();
+ // The query is index 4 in the parts list
+ let query_pos = parts[..4].iter().map(|v| v.len()).sum();
+
+ CString::new(uri_str_copy)
+ .map(|v| (v, host_pos, path_pos, query_pos))
+ .map_err(UriParsingError::from)
}
}
-impl TryFrom for CoapUri {
+impl PartialEq for CoapUri {
+ fn eq(&self, other: &Self) -> bool {
+ self.raw_uri.port == other.raw_uri.port
+ && self.raw_uri.scheme == other.raw_uri.scheme
+ // SAFETY: After construction the fields of self.raw_uri always reference the
+ // corresponding parts of the underlying string, which is pinned. Therefore, the
+ // pointer and length are valid for the lifetime of this struct.
+ && unsafe {
+ coap_string_equal!(&self.raw_uri.host, &other.raw_uri.host)
+ && coap_string_equal!(&self.raw_uri.path, &other.raw_uri.path)
+ && coap_string_equal!(&self.raw_uri.query, &other.raw_uri.query)
+ }
+ }
+}
+
+impl Eq for CoapUri {}
+
+impl Clone for CoapUri {
+ fn clone(&self) -> Self {
+ // SAFETY: raw_uri is a valid pointer to a coap_uri_t (by construction of this type and
+ // contract of from_raw_uri)
+ unsafe { CoapUri::from_raw_uri(&self.raw_uri, self.is_proxy) }
+ }
+}
+
+#[cfg(feature = "url")]
+impl TryFrom<&Url> for CoapUri {
type Error = UriParsingError;
- fn try_from(value: Url) -> Result {
+ fn try_from(value: &Url) -> Result {
CoapUri::try_from_url(value)
}
}
+impl FromStr for CoapUri {
+ type Err = UriParsingError;
+
+ fn from_str(s: &str) -> Result {
+ Self::try_from_str(s)
+ }
+}
+
impl Display for CoapUri {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!(
- "{}{}{}{}{}",
- self.scheme.map_or_else(String::new, |v| format!("{}://", v)),
- self.host.as_ref().map_or_else(String::new, |v| v.to_string()),
- self.port.map_or_else(String::new, |v| format!(":{}", v)),
- self.path
- .as_ref()
- .map_or_else(String::new, |v| format!("/{}", v.join("/"))),
- self.query
- .as_ref()
- .map_or_else(String::new, |v| format!("?{}", v.join("&"))),
- ))
+ self.uri_str.fmt(f)
}
}
diff --git a/libcoap/tests/common/mod.rs b/libcoap/tests/common/mod.rs
index 2bd52ac9..b082f0f8 100644
--- a/libcoap/tests/common/mod.rs
+++ b/libcoap/tests/common/mod.rs
@@ -7,18 +7,18 @@
* See the README as well as the LICENSE file for more information.
*/
-use libcoap_rs::message::{CoapMessageCommon, CoapRequest, CoapResponse};
-use libcoap_rs::protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode};
-use libcoap_rs::session::CoapSessionCommon;
-use libcoap_rs::types::{CoapUri, CoapUriHost};
-use libcoap_rs::{CoapContext, CoapRequestHandler, CoapResource};
use std::net::{SocketAddr, UdpSocket};
use std::rc::Rc;
-use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex};
+use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use std::time::Duration;
+use libcoap_rs::{CoapContext, CoapRequestHandler, CoapResource};
+use libcoap_rs::message::{CoapMessageCommon, CoapRequest, CoapResponse};
+use libcoap_rs::protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode};
+use libcoap_rs::session::CoapSessionCommon;
+
pub(crate) fn get_unused_server_addr() -> SocketAddr {
// This will give us a SocketAddress with a port in the local port range automatically
// assigned by the operating system.
@@ -100,16 +100,8 @@ pub(crate) fn run_test_server(context_configurator:
context.shutdown(Some(Duration::from_secs(0))).unwrap();
}
-pub(crate) fn gen_test_request(server_address: SocketAddr) -> CoapRequest {
- let uri = CoapUri::new(
- None,
- Some(CoapUriHost::IpLiteral(server_address.ip())),
- Some(server_address.port()),
- Some(vec!["test1".to_string()]),
- None,
- );
+pub(crate) fn gen_test_request() -> CoapRequest {
+ let uri = "/test1".parse().expect("unable to parse request URI");
- let mut request = CoapRequest::new(CoapMessageType::Con, CoapRequestCode::Get).unwrap();
- request.set_uri(Some(uri)).unwrap();
- request
+ CoapRequest::new(CoapMessageType::Con, CoapRequestCode::Get, uri).unwrap()
}
diff --git a/libcoap/tests/dtls_client_server_test.rs b/libcoap/tests/dtls_client_server_test.rs
index 753c0611..1230987d 100644
--- a/libcoap/tests/dtls_client_server_test.rs
+++ b/libcoap/tests/dtls_client_server_test.rs
@@ -72,7 +72,7 @@ pub fn dtls_client_server_request() {
let mut context = CoapContext::new().unwrap();
let session = CoapClientSession::connect_dtls(&mut context, server_address, DummyCryptoProvider {}).unwrap();
- let request = common::gen_test_request(server_address);
+ let request = common::gen_test_request();
let req_handle = session.send_request(request).unwrap();
loop {
assert!(context.do_io(Some(Duration::from_secs(10))).expect("error during IO") <= Duration::from_secs(10));
diff --git a/libcoap/tests/tcp_client_server_test.rs b/libcoap/tests/tcp_client_server_test.rs
index ee0377bd..da5db4f7 100644
--- a/libcoap/tests/tcp_client_server_test.rs
+++ b/libcoap/tests/tcp_client_server_test.rs
@@ -28,7 +28,7 @@ pub fn basic_client_server_request() {
let mut context = CoapContext::new().unwrap();
let session = CoapClientSession::connect_tcp(&mut context, server_address).unwrap();
- let request = common::gen_test_request(server_address);
+ let request = common::gen_test_request();
let req_handle = session.send_request(request).unwrap();
loop {
assert!(context.do_io(Some(Duration::from_secs(10))).expect("error during IO") <= Duration::from_secs(10));
diff --git a/libcoap/tests/udp_client_server_test.rs b/libcoap/tests/udp_client_server_test.rs
index 2958a4c0..88250e04 100644
--- a/libcoap/tests/udp_client_server_test.rs
+++ b/libcoap/tests/udp_client_server_test.rs
@@ -27,7 +27,7 @@ pub fn basic_client_server_request() {
let mut context = CoapContext::new().unwrap();
let session = CoapClientSession::connect_udp(&mut context, server_address).unwrap();
- let request = common::gen_test_request(server_address);
+ let request = common::gen_test_request();
let req_handle = session.send_request(request).unwrap();
loop {
assert!(context.do_io(Some(Duration::from_secs(10))).expect("error during IO") <= Duration::from_secs(10));