diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml index 874b34ce..0d7e42da 100644 --- a/.github/workflows/report.yml +++ b/.github/workflows/report.yml @@ -59,7 +59,7 @@ jobs: pattern: docs run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }} - path: coverage-data + path: ./doc - name: 'Get artifact ID' id: get-artifact-id uses: actions/github-script@v7 @@ -79,7 +79,7 @@ jobs: - if: ${{ (github.event.workflow_run.head_repository.owner.login == github.event.workflow_run.repository.owner.login) && (vars.DOCS_AND_COV_REPO != '') }} uses: peaceiris/actions-gh-pages@v4 with: - publish_dir: ./target/doc + publish_dir: ./doc publish_branch: main external_repository: ${{ vars.DOCS_AND_COV_REPO }} personal_token: ${{ secrets.DOCS_AND_COV_REPO_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3e05a6e..2cb36940 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,18 +30,20 @@ jobs: RUSTDOCFLAGS: "${{ matrix.rust_version == 'nightly' && '-C instrument-coverage -Cpanic=abort -Zpanic_abort_tests -Z unstable-options --persist-doctests target/debug/doctests' || ' ' }}" LIBRARY_FEATURES: | ${{ (matrix.crate == 'libcoap-rs' && 'tcp,vendored,rand') - || (matrix.crate == 'libcoap-sys' && 'default') + || (matrix.crate == 'libcoap-sys' && 'default,vendored') || 'vendored' }} + LIBCOAP_RS_DTLS_BACKEND: ${{ matrix.dtls_backend }} + LIBCOAP_RS_BUILD_SYSTEM: "vendored" DTLS_LIBRARY_FEATURES: | - ${{ (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'tinydtls' && 'tcp,dtls-psk,dtls-rpk,dtls_tinydtls_vendored') - || (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'mbedtls' && 'tcp,dtls-psk,dtls-pki,dtls_mbedtls_vendored') - || (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'openssl' && 'tcp,dtls-psk,dtls-pki,dtls_openssl_vendored') - || (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'gnutls' && 'tcp,dtls-psk,dtls-pki,dtls-rpk,dtls_gnutls') - || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'tinydtls' && 'dtls,dtls_backend_tinydtls,dtls_backend_tinydtls_vendored') - || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'mbedtls' && 'dtls,dtls_backend_mbedtls,dtls_backend_mbedtls_vendored') - || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'openssl' && 'dtls,dtls_backend_openssl,dtls_backend_openssl_vendored') - || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'gnutls' && 'dtls,dtls_backend_gnutls') + ${{ (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'tinydtls' && 'tcp,dtls-psk,dtls-rpk,dtls-tinydtls-sys-vendored') + || (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'mbedtls' && 'tcp,dtls-psk,dtls-pki,dtls-mbedtls-sys') + || (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'openssl' && 'tcp,dtls-psk,dtls-pki,dtls-openssl-sys-vendored') + || (matrix.crate == 'libcoap-rs' && matrix.dtls_backend == 'gnutls' && 'tcp,dtls-psk,dtls-pki,dtls-rpk') + || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'tinydtls' && 'dtls,dtls-tinydtls-sys-vendored') + || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'mbedtls' && 'dtls,dtls-mbedtls-sys') + || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'openssl' && 'dtls,dtls-openssl-sys-vendored') + || (matrix.crate == 'libcoap-sys' && matrix.dtls_backend == 'gnutls' && 'dtls') || 'vendored' }} steps: @@ -51,9 +53,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: rust-src, rustc, rust-std, cargo, llvm-tools, llvm-tools-preview - toolchain: ${{ matrix.rust_version == 'msrv' && '1.81' || matrix.rust_version }} + toolchain: ${{ matrix.rust_version == 'msrv' && '1.82' || matrix.rust_version }} - if: matrix.dtls_backend == 'gnutls' - uses: awalsh128/cache-apt-pkgs-action@latest + uses: awalsh128/cache-apt-pkgs-action@v1.4.3 with: packages: libgnutls28-dev libgnutls30 version: 1.0 @@ -105,7 +107,7 @@ jobs: submodules: true ref: ${{ env.HEAD_REF }} # --all-features uses GNUTLS as backend, must provide it. - - uses: awalsh128/cache-apt-pkgs-action@latest + - uses: awalsh128/cache-apt-pkgs-action@v1.4.3 with: packages: libgnutls28-dev libgnutls30 version: 1.0 diff --git a/.idea/libcoap-rs.iml b/.idea/libcoap-rs.iml index 84e5d2c4..e82ac0ec 100644 --- a/.idea/libcoap-rs.iml +++ b/.idea/libcoap-rs.iml @@ -5,7 +5,15 @@ + + + + + + + + diff --git a/libcoap-sys/Cargo.toml b/libcoap-sys/Cargo.toml index 769576b4..08faf2ee 100644 --- a/libcoap-sys/Cargo.toml +++ b/libcoap-sys/Cargo.toml @@ -18,54 +18,56 @@ categories = ["external-ffi-bindings", "network-programming", "embedded"] keywords = ["coap", "libcoap"] exclude = ["src/libcoap/ext/"] resolver = "2" -# Current reason for MSRV (please update when increasing MSRV): Transient dependency "home" requires Rust 1.81. -rust-version = "1.81.0" +build = "build/main.rs" +# Current reason for MSRV (please update when increasing MSRV): bindgen generates unsafe extern "C" blocks, which are +# not supported on Rust < 1.82. +# See also: https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-extern.html +rust-version = "1.82.0" [features] -# The default features match those of libcoaps configure script, except for dtls, which is disabled here because it -# requires a backend to be set manually. -default = ["oscore", "ipv4", "ipv6", "af-unix", "tcp", "websockets", "async", "observe-persist", "q-block", "thread-safe", "thread-recursive-lock-detection", "server", "client", "epoll", "vendored", "static"] -# While not specified here due to limitations in Cargo's syntax, the DTLS feature depends on one of the DTLS backends -# being enabled. -# If you are developing a library based on libcoap-sys and do not care about the DTLS backend, enable the dtls feature -# and let the user decide on the backend to use, either by re-exporting these features (see -# https://doc.rust-lang.org/cargo/reference/features.html#dependency-features) or by assuming that the user will use -# libcoap-sys as a dependency and enable the corresponding backend feature themselves, relying on Cargo's feature -# unification to enable it for your crate as well. -# Also note that the backends are **mutually exclusive** due to the C library having these backends as mutually -# exclusive features. If multiple backends are enabled (e.g. because multiple dependencies use libcoap-sys and use -# different backends), we select one based on the auto-detection order specified in -# https://github.com/obgm/libcoap/blob/develop/configure.ac#L494 (gnutls > openssl > mbedtls > tinydtls). +# The default features match those of libcoap's configure script for +# the minimum supported version. +default = [ + "dtls", + "oscore", + "ipv4", + "ipv6", + "af-unix", + "tcp", + "websockets", + "async", + "observe-persist", + "q-block", + "thread-safe", + "thread-recursive-lock-detection", + "server", + "client", + "epoll", + # TODO add proxy +] +# Allows using the version of OpenSSL provided by openssl-sys instead of a system-provided one. +# Note that this does not enforce the use of OpenSSL in libcoap, see the crate-level documentation for more info. +dtls-openssl-sys = ["dep:openssl-sys"] +# Tell the openssl-sys version that is possibly used by libcoap-sys to use the vendored version of its library. +dtls-openssl-sys-vendored = ["dtls-openssl-sys", "openssl-sys/vendored"] +# Allows using the version of MbedTLS provided by mbedtls-sys-auto instead of a system-provided one. +# Note that this does not enforce the use of MbedTLS in libcoap, see the crate-level documentation for more info. +dtls-mbedtls-sys = ["dep:mbedtls-sys-auto"] +# Allows using the version of TinyDTLS provided by tinydtls-sys instead of a system-provided one. +# Note that this does not enforce the use of TinyDTLS in libcoap, see the crate-level documentation for more info. +dtls-tinydtls-sys = ["dep:tinydtls-sys", "tinydtls-sys/ecc", "tinydtls-sys/psk"] +# Tell the tinydtls-sys version that is possibly used by libcoap-sys to use the vendored version of its library. +dtls-tinydtls-sys-vendored = ["dtls-tinydtls-sys", "tinydtls-sys/vendored"] -# Corresponding libcoap configure flag: --with-openssl -dtls_backend_openssl = ["dtls", "dep:openssl-sys"] -dtls_backend_openssl_vendored = ["dtls_backend_openssl", "openssl-sys/vendored"] -# Corresponding libcoap configure flag: --with-gnutls -dtls_backend_gnutls = ["dtls"] -# Corresponding libcoap configure flag: --with-mbedtls -dtls_backend_mbedtls = ["dtls"] # can't use mbedtls-sys-auto to generate linker flags here, as the crate doesn't support mbedtls >= 3.0.0 -dtls_backend_mbedtls_vendored = ["dep:mbedtls-sys-auto", "dtls_backend_mbedtls"] -# Corresponding libcoap configure flags: --with-tinydtls --without-submodule-tinydtls -dtls_backend_tinydtls = ["dtls", "dep:tinydtls-sys", "tinydtls-sys/ecc", "tinydtls-sys/psk"] -dtls_backend_tinydtls_vendored = ["dtls_backend_tinydtls", "tinydtls-sys/vendored"] -# Enabling this feature will force libcoap-sys to be built with and statically linked to a vendored version of libcoap, -# which will be built by the build-script before building libcoap-sys. +# Enabling this feature will allow libcoap-sys to be built with and statically linked to a vendored version of libcoap, # This way, it is no longer required to have libcoap installed to use this crate. -vendored = ["static"] -# Enable this feature to use static linking to libcoap instead of dynamic linking. -static = [] +vendored = [] # --- FEATURE FLAGS --- -# Note that setting the feature flags currently has no effect on the generated Rust code, because the libcoap headers do -# not use these feature flags. They only affect the features built into the vendored C library (if enabled). - # Enable this feature to enable/require CoAP over DTLS support in the C library. -# Important: also read the section on DTLS backends before enabling this feature. # Corresponding libcoap configure flag: --enable-dtls dtls = [] # Enable this feature to enable/require TLS support in addition to DTLS support. -# Note: Will also enable the TCP and DTLS features, so consider the above section regarding DTLS backends before + -# enabling this. tls = ["dtls", "tcp"] # Enable this feature to enable/require OSCORE functionality in the C library. # Corresponding libcoap configure flag: --enable-oscore @@ -137,24 +139,28 @@ dtls-rpk = ["dtls"] [dependencies] openssl-sys = { version = "^0.9.74", optional = true } mbedtls-sys-auto = { version = "^2.26", optional = true } -libc = "^0.2.126" tinydtls-sys = { version = "^0.2.0", default-features = false, optional = true } [target.'cfg(target_os="espidf")'.dependencies] -esp-idf-sys = { version = "0.35.0" } +esp-idf-sys = { version = "0.36.1" } + +[target.'cfg(not(target_os="espidf"))'.dependencies] +libc = "0.2.126" [build-dependencies] -bindgen = "0.69.4" +bindgen = { version = "0.71.1" } autotools = "^0.2.3" fs_extra = "^1.2" pkg-config = "^0.3.24" -regex = "1.10.5" -embuild = { version = "0.32.0", features = ["bindgen", "espidf", "cmake"] } version-compare = "0.2.0" +anyhow = { version = "1.0.94", features = ["backtrace"] } +enumset = "1.1.5" +syn = { version = "2.0.96" } +embuild = { version = "0.33.0", features = ["espidf"] } [package.metadata.docs.rs] features = ["dtls", "dtls_backend_openssl", "vendored"] [[package.metadata.esp-idf-sys.extra_components]] -remote_component = { name = "espressif/coap", version = "4.3.4~3" } +remote_component = { name = "espressif/coap", version = "4.3.5~3" } bindings_header = "src/wrapper.h" diff --git a/libcoap-sys/build.rs b/libcoap-sys/build.rs deleted file mode 100644 index 476c0e3f..00000000 --- a/libcoap-sys/build.rs +++ /dev/null @@ -1,804 +0,0 @@ -// SPDX-License-Identifier: BSD-2-CLAUSE -/* - * build.rs - build script for libcoap Rust bindings. - * This file is part of the libcoap-sys crate, see the README and LICENSE files for - * more information and terms of use. - * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved. - * See the README as well as the LICENSE file for more information. - */ - -use std::{ - cell::RefCell, - collections::BTreeSet, - default::Default, - env, - ffi::{OsStr, OsString}, - fmt::{Debug, Display}, - io::ErrorKind, - path::{Path, PathBuf}, - process::Command, - rc::Rc, -}; - -use bindgen::{ - callbacks::{IntKind, ParseCallbacks}, - EnumVariation, -}; -use pkg_config::probe_library; -use version_compare::{Cmp, Version}; - -/// Features whose availability can be checked during compile time based on `#define` directives. -const COMPILE_TIME_FEATURE_CHECKS: [&str; 16] = [ - "af-unix", - "async", - "client", - "small-stack", - "tcp", - "epoll", - "ipv4", - "ipv6", - "oscore", - "q-block", - "server", - "thread-recursive-lock-detection", - "thread-safe", - "dtls", - "observe-persist", - "websockets", -]; - -/// Data structure describing meta-information about the used version of libcoap. -#[derive(Debug)] -struct LibcoapMetadata { - package_version: String, - version: i64, - feature_defines_available: bool, - feature_defines: BTreeSet, - dtls_backend: Option, -} - -impl Default for LibcoapMetadata { - fn default() -> Self { - Self { - package_version: Default::default(), - version: 0, - feature_defines_available: false, - // By default, TCP is assumed to be supported if COAP_DISABLE_TCP is unset. - feature_defines: BTreeSet::from(["tcp".to_string()]), - dtls_backend: None, - } - } -} - -/// Implementation of bindgen's [ParseCallbacks] that allow reading some meta-information about the -/// used libcoap version from its defines (package version, supported features, ...) -#[derive(Debug, Default)] -struct CoapDefineParser { - defines: Rc>, -} - -impl ParseCallbacks for CoapDefineParser { - fn int_macro(&self, name: &str, value: i64) -> Option { - match name { - "LIBCOAP_VERSION" => { - self.defines.borrow_mut().version = value; - }, - "COAP_AF_UNIX_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("af-unix".to_string()); - }, - "COAP_ASYNC_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("async".to_string()); - }, - "COAP_CLIENT_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("client".to_string()); - }, - "COAP_CONSTRAINED_STACK" => { - self.defines - .borrow_mut() - .feature_defines - .insert("small-stack".to_string()); - }, - "COAP_DISABLE_TCP" => { - if value == 1 { - self.defines.borrow_mut().feature_defines.remove("tcp"); - } - }, - "COAP_EPOLL_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("epoll".to_string()); - }, - "COAP_IPV4_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("ipv4".to_string()); - }, - "COAP_IPV6_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("ipv6".to_string()); - }, - "COAP_OSCORE_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("oscore".to_string()); - }, - "COAP_Q_BLOCK_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("q-block".to_string()); - }, - "COAP_SERVER_SUPPORT" => { - self.defines.borrow_mut().feature_defines.insert("server".to_string()); - }, - "COAP_THREAD_RECURSIVE_CHECK" => { - self.defines - .borrow_mut() - .feature_defines - .insert("thread-recursive-lock-detection".to_string()); - }, - "COAP_THREAD_SAFE" => { - self.defines - .borrow_mut() - .feature_defines - .insert("thread-safe".to_string()); - }, - "COAP_WITH_LIBGNUTLS" => { - self.defines.borrow_mut().dtls_backend = Some(DtlsBackend::GnuTls); - self.defines.borrow_mut().feature_defines.insert("dtls".to_string()); - }, - "COAP_WITH_LIBMBEDTLS" => { - self.defines.borrow_mut().dtls_backend = Some(DtlsBackend::MbedTls); - self.defines.borrow_mut().feature_defines.insert("dtls".to_string()); - }, - "COAP_WITH_LIBOPENSSL" => { - self.defines.borrow_mut().dtls_backend = Some(DtlsBackend::OpenSsl); - self.defines.borrow_mut().feature_defines.insert("dtls".to_string()); - }, - "COAP_WITH_LIBTINYDTLS" => { - self.defines.borrow_mut().dtls_backend = Some(DtlsBackend::TinyDtls); - self.defines.borrow_mut().feature_defines.insert("dtls".to_string()); - }, - // TODO(#29): as soon as we have wolfSSL support in libcoap-sys - /*"COAP_WITH_LIBWOLFSSL" => { - self.defines.borrow_mut().dtls_backend = Some(DtlsBackend::WolfSsl); - self.defines - .borrow_mut() - .feature_defines - .insert("dtls".to_string()); - },*/ - "COAP_WITH_OBSERVE_PERSIST" => { - self.defines - .borrow_mut() - .feature_defines - .insert("observe-persist".to_string()); - }, - "COAP_WS_SUPPORT" => { - self.defines - .borrow_mut() - .feature_defines - .insert("websockets".to_string()); - }, - _ => {}, - } - None - } - - fn str_macro(&self, name: &str, value: &[u8]) { - // Will allow this here, as we might want to add additional cfg flags later on. - #[allow(clippy::single_match)] - match name { - "LIBCOAP_PACKAGE_VERSION" => { - let version_str = String::from_utf8_lossy(value); - println!("cargo:rustc-cfg=libcoap_version=\"{}\"", version_str.as_ref()); - println!("cargo:libcoap_version={}", version_str.as_ref()); - let version = Version::from(version_str.as_ref()).expect("invalid libcoap version"); - match version.compare(Version::from("4.3.4").unwrap()) { - Cmp::Gt => println!("cargo:rustc-cfg=non_inlined_coap_send_rst"), - _ => {}, - } - self.defines.borrow_mut().package_version = version.to_string(); - }, - _ => {}, - } - } - - fn include_file(&self, filename: &str) { - let header_path = Path::new(filename); - if header_path.file_name().eq(&Some(OsStr::new("coap_defines.h"))) { - self.defines.borrow_mut().feature_defines_available = true; - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum DtlsBackend { - GnuTls, - OpenSsl, - MbedTls, - TinyDtls, -} -impl Display for DtlsBackend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let str = match self { - DtlsBackend::GnuTls => "gnutls", - DtlsBackend::OpenSsl => "openssl", - DtlsBackend::MbedTls => "mbedtls", - DtlsBackend::TinyDtls => "tinydtls", - } - .to_string(); - write!(f, "{}", str) - } -} - -fn get_target_mcu() -> &'static str { - let cfg_flags = embuild::espidf::sysenv::cfg_args().expect("missing cfg flags from IDF"); - let mcus = [ - "esp32", "esp32s2", "esp32s3", "esp32c3", "esp32c2", "esp32h2", "esp32c5", "esp32c6", "esp32p4", - ]; - for mcu in mcus { - if cfg_flags.get(mcu).is_some() { - return mcu; - } - } - panic!("unknown ESP target MCU, please add target to libcoap-sys build.rs file!") -} - -fn get_builder_espidf() -> bindgen::Builder { - embuild::espidf::sysenv::output(); - let esp_idf_path = embuild::espidf::sysenv::idf_path().expect("missing IDF path"); - let esp_idf_buildroot = env::var("DEP_ESP_IDF_ROOT").expect("DEP_ESP_IDF_ROOT is not set"); - let esp_include_path = embuild::espidf::sysenv::cincl_args().expect("missing IDF cincl args"); - let embuild_env = embuild::espidf::sysenv::env_path().expect("missing IDF env path"); - 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"), - "cargo", - &[ - embuild::cmake::file_api::ObjKind::Codemodel, - embuild::cmake::file_api::ObjKind::Toolchains, - embuild::cmake::file_api::ObjKind::Cache, - ], - ) - .expect("unable to query cmake API for compiler path") - .get_replies() - .expect("unable to get cmake query replies for compiler path"); - let compiler = cmake_info - .get_toolchains() - .map_err(|_e| "Can't get toolchains") - .and_then(|mut t| { - t.take(embuild::cmake::file_api::codemodel::Language::C) - .ok_or("No C toolchain") - }) - .and_then(|t| t.compiler.path.ok_or("No compiler path set")) - .expect("unable to determine compiler path"); - - // Parse include arguments - // Regexes are correct and never change, therefore it is ok to unwrap here. - let arg_splitter = regex::Regex::new(r##"(?:[^\\]"[^"]*[^\\]")?(\s)"##).unwrap(); - let apostrophe_remover = regex::Regex::new(r##"^"(?.*)"$"##).unwrap(); - let esp_clang_args = arg_splitter - .split(esp_include_path.args.as_str()) - .map(|x| apostrophe_remover.replace(x.trim(), "$content").to_string()) - .collect::>(); - let bindgen_builder = embuild::bindgen::Factory { - clang_args: esp_clang_args.clone(), - linker: Some(compiler), - mcu: None, - force_cpp: false, - sysroot: None, - } - .builder() - .expect("unable to create bindgen builder for libcoap bindings from ESP-IDF"); - - let clang_target = if esp_arch.starts_with("riscv32") { - "riscv32" - } else { - esp_arch.as_str() - }; - let short_target = if esp_arch.starts_with("riscv32") { - "riscv" - } else { - esp_arch.as_str() - }; - let target_mcu = get_target_mcu(); - - bindgen_builder - .clang_args(&esp_clang_args) - .clang_arg("-target") - .clang_arg(clang_target) - .clang_arg("-DESP_PLATFORM") - .clang_arg("-DLWIP_IPV4=1") - .clang_arg("-DLWIP_IPV6=1") - .clang_arg("-DconfigUSE_PASSIVE_IDLE_HOOK=1") - .clang_arg(format!("-I{}/components/newlib/platform_include", esp_idf_path)) - .clang_arg(format!("-I{}/components/lwip/port/include", esp_idf_path)) - .clang_arg(format!("-I{}/components/lwip/port/esp32xx/include", esp_idf_path)) - .clang_arg(format!("-I{}/components/lwip/lwip/src/include", esp_idf_path)) - .clang_arg(format!("-I{}/components/lwip/port/freertos/include", esp_idf_path)) - .clang_arg(format!("-I{}/components/esp_system/include", esp_idf_path)) - .clang_arg(format!( - "-I{}/components/freertos/config/include/freertos", - esp_idf_path - )) - .clang_arg(format!("-I{}/components/freertos/esp_additions/include", esp_idf_path)) - .clang_arg(format!( - "-I{}/components/freertos/esp_additions/include/freertos", - esp_idf_path - )) - .clang_arg(format!( - "-I{}/components/freertos/esp_additions/arch/{}/include", - esp_idf_path, short_target - )) // for older espidf - .clang_arg(format!( - "-I{}/components/freertos/config/{}/include", - esp_idf_path, short_target - )) // for newer espidf - .clang_arg(format!("-I{}/components/{}/include", esp_idf_path, short_target)) - .clang_arg(format!( - "-I{}/components/{}/{}/include", - esp_idf_path, short_target, target_mcu - )) - .clang_arg(format!("-I{}/components/esp_hw_support/include", esp_idf_path)) - .clang_arg(format!("-I{}/components/esp_common/include", esp_idf_path)) - .clang_arg(format!( - "-I{}/components/freertos/FreeRTOS-Kernel-SMP/include", - esp_idf_path - )) - .clang_arg(format!( - "-I{}/components/freertos/FreeRTOS-Kernel-SMP/portable/{}/include/freertos", - esp_idf_path, short_target - )) - .clang_arg(format!("-I{}/components/soc/{}/include", esp_idf_path, target_mcu)) - .clang_arg(format!("-I{}/components/heap/include", esp_idf_path)) - .clang_arg(format!("-I{}/components/esp_rom/include", esp_idf_path)) - .clang_arg(format!( - "-I{}/managed_components/espressif__coap/port/include", - esp_idf_buildroot - )) - .clang_arg(format!( - "-I{}/managed_components/espressif__coap/libcoap/include", - esp_idf_buildroot - )) - .clang_arg(format!("-I{}/build/config/", esp_idf_buildroot)) - .allowlist_type("epoll_event") -} - -fn get_builder() -> bindgen::Builder { - bindgen::Builder::default().blocklist_type("epoll_event") -} - -fn build_vendored_library( - out_dir: &OsString, - dtls_backend: Option<&DtlsBackend>, - mut builder: bindgen::Builder, -) -> bindgen::Builder { - let libcoap_src_dir = Path::new(&out_dir).join("libcoap"); - - // Even though libcoap supports out-of-source builds, autogen.sh (or the corresponding - // autotools) modify files in the source tree, which causes verification problems when - // running cargo package. - // Therefore, we copy the libcoap source over to the output directory and build from there. - let copy_options = fs_extra::dir::CopyOptions { - overwrite: true, - ..Default::default() - }; - match std::fs::remove_dir_all(&libcoap_src_dir) { - Ok(_) => {}, - Err(e) if e.kind() == ErrorKind::NotFound => {}, - e => e.expect("unable to clear libcoap build directory"), - } - fs_extra::dir::copy( - Path::new(env!("CARGO_MANIFEST_DIR")).join("src").join("libcoap"), - Path::new(&out_dir), - ©_options, - ) - .expect("unable to prepare libcoap build source directory"); - let current_dir_backup = env::current_dir().expect("unable to get current directory"); - env::set_current_dir(&libcoap_src_dir).expect("unable to change to libcoap build dir"); - Command::new(libcoap_src_dir.join("autogen.sh")) - .status() - .expect("unable to execute autogen.sh"); - let mut build_config = autotools::Config::new(&libcoap_src_dir); - build_config.out_dir(out_dir); - if let Some(dtls_backend) = dtls_backend { - build_config - .enable("dtls", None) - .with(dtls_backend.to_string().as_str(), None); - - // If DTLS is vendored we need to tell libcoap about the vendored version - match dtls_backend { - DtlsBackend::TinyDtls => { - // We do not ship tinydtls with our source distribution. Instead, we use tinydtls-sys. - build_config.without("submodule-tinydtls", None); - - // If tinydtls-sys is built with the vendored feature, the library is built alongside - // the Rust crate. To use the version built by the tinydtls-sys build script, we use the - // environment variables set by the build script. - if let Some(tinydtls_include) = env::var_os("DEP_TINYDTLS_INCLUDE") { - build_config.env( - "TinyDTLS_CFLAGS", - format!( - "-I{} -I{}", - tinydtls_include - .to_str() - .expect("DEP_TINYDTLS_INCLUDE is not a valid string"), - Path::new(&tinydtls_include) - .join("tinydtls") - .to_str() - .expect("DEP_TINYDTLS_INCLUDE is not a valid string") - ), - ); - }; - - if let Some(tinydtls_libs) = env::var_os("DEP_TINYDTLS_LIBS") { - build_config.env( - "TinyDTLS_LIBS", - format!( - "-L{}", - tinydtls_libs.to_str().expect("DEP_TINYDTLS_LIBS is invalid string") - ), - ); - - build_config.env( - "PKG_CONFIG_PATH", - Path::new(tinydtls_libs.as_os_str()) - .join("lib") - .join("pkgconfig") - .into_os_string(), - ); - } - }, - DtlsBackend::OpenSsl => { - // Set include path according to the path provided by openssl-sys (relevant if - // openssl-sys is vendored) - if let Some(openssl_include) = env::var_os("DEP_OPENSSL_INCLUDE") { - build_config.env( - "OpenSSL_CFLAGS", - format!( - "-I{}", - openssl_include.to_str().expect("DEP_OPENSSL_INCLUDE is invalid path") - ), - ); - build_config.env( - "PKG_CONFIG_PATH", - Path::new(openssl_include.as_os_str()) - .parent() - .expect("DEP_OPENSSL_INCLUDE has no parent directory") - .join("lib") - .join("pkgconfig") - .into_os_string(), - ); - } - }, - DtlsBackend::MbedTls => { - // Set include path according to the path provided by mbedtls-sys (relevant if - // mbedtls-sys is vendored). - // libcoap doesn't support overriding the MbedTLS CFLAGS, but doesn't set those - // either, so we just set CFLAGS and hope they propagate. - if let Some(mbedtls_include) = env::var_os("DEP_MBEDTLS_INCLUDE") { - // the config.h of mbedtls-sys-auto is generated separately from all other - // includes in the root of mbedtls-sys-auto's OUT_DIR. - // In order to let libcoap read use the correct config file, we need to copy - // this file into our own OUT_DIR under include/mbedtls/config.h, so that we - // can then set OUT_DIR/include as an additional include path. - let config_h = env::var_os("DEP_MBEDTLS_CONFIG_H") - .expect("DEP_MBEDTLS_INCLUDE is set but DEP_MBEDTLS_CONFIG_H is not"); - let config_path = Path::new(&config_h); - let out_include = Path::new(&out_dir).join("include"); - std::fs::create_dir_all(out_include.join("mbedtls")) - .expect("unable to prepare include directory for mbedtls config.h"); - std::fs::copy(config_path, out_include.join("mbedtls").join("config.h")) - .expect("unable to copy mbedtls config.h to include directory"); - let mbedtls_library_path = config_path - .parent() - .expect("DEP_MBEDTLS_CONFIG_H has no parent directory") - .join("build") - .join("library"); - build_config.env( - "MbedTLS_CFLAGS", - format!( - "-I{} -I{}", - out_include.to_str().expect("OUT_DIR is not a valid string"), - mbedtls_include - .to_str() - .expect("DEP_MBEDTLS_INCLUDE is not a valid string") - ), - ); - build_config.env( - "MbedTLS_LIBS", - format!( - "-L{0} -l:libmbedtls.a -l:libmbedcrypto.a -l:libmbedx509.a", - mbedtls_library_path - .to_str() - .expect("DEP_MBEDTLS_CONFIG_H is not a valid string"), - ), - ); - } - }, - DtlsBackend::GnuTls => { - // Vendoring not supported - }, - } - } else { - build_config.disable("dtls", None); - } - build_config - // Disable shared library compilation because the vendored library will always be - // statically linked - .disable("shared", None) - // Disable any documentation for vendored C library - .disable("documentation", None) - .disable("doxygen", None) - .disable("manpages", None) - // This would install the license into the documentation directory, but we don't use the - // generated documentation anywhere. - .disable("license-install", None) - // Disable tests and examples as well as test coverage - .disable("tests", None) - .disable("examples", None) - .disable("gcov", None); - - // Enable debug symbols if enabled in Rust - match env::var_os("DEBUG") - .expect("env variable DEBUG that should have been set by cargo is not set") - .to_str() - .expect("env variable DEBUG is not valid") - { - "0" | "false" => {}, - _ => { - build_config.with("debug", None); - }, - } - // Enable dependency features based on selected cargo features. - build_config - .enable("oscore", Some(if cfg!(feature = "oscore") { "yes" } else { "no" })) - .enable("ipv4-support", Some(if cfg!(feature = "ipv4") { "yes" } else { "no" })) - .enable("ipv6-support", Some(if cfg!(feature = "ipv6") { "yes" } else { "no" })) - .enable( - "af-unix-support", - Some(if cfg!(feature = "af-unix") { "yes" } else { "no" }), - ) - .enable("tcp", Some(if cfg!(feature = "tcp") { "yes" } else { "no" })) - .enable( - "websockets", - Some(if cfg!(feature = "websockets") { "yes" } else { "no" }), - ) - .enable("async", Some(if cfg!(feature = "async") { "yes" } else { "no" })) - .enable( - "observe-persist", - Some(if cfg!(feature = "observe-persist") { "yes" } else { "no" }), - ) - .enable("q-block", Some(if cfg!(feature = "q-block") { "yes" } else { "no" })) - .enable( - "thread-safe", - Some(if cfg!(feature = "thread-safe") { "yes" } else { "no" }), - ) - .enable( - "thread-recursive-lock-detection", - Some(if cfg!(feature = "thread-recursive-lock-detection") { - "yes" - } else { - "no" - }), - ) - .enable( - "small-stack", - Some(if cfg!(feature = "small-stack") { "yes" } else { "no" }), - ) - .enable("server-mode", Some(if cfg!(feature = "server") { "yes" } else { "no" })) - .enable("client-mode", Some(if cfg!(feature = "client") { "yes" } else { "no" })) - .with("epoll", Some(if cfg!(feature = "epoll") { "yes" } else { "no" })); - - // Run build - let dst = build_config.build(); - - // Add the built library to the search path - println!( - "cargo:rustc-link-search=native={}", - dst.join("lib") - .to_str() - .expect("libcoap build output dir is not a valid string") - ); - println!( - "cargo:include={}", - dst.join("include") - .to_str() - .expect("libcoap build output dir is not a valid string") - ); - builder = builder - .clang_arg(format!( - "-I{}", - dst.join("include") - .to_str() - .expect("libcoap build output dir is not a valid string") - )) - .clang_arg(format!( - "-L{}", - dst.join("lib") - .to_str() - .expect("libcoap build output dir is not a valid string") - )); - env::set_current_dir(current_dir_backup).expect("unable to switch back to source dir"); - builder -} - -fn main() { - println!("cargo::rustc-check-cfg=cfg(feature_checks_available)"); - println!("cargo::rustc-check-cfg=cfg(non_inlined_coap_send_rst)"); - println!("cargo:rerun-if-changed=src/libcoap/"); - println!("cargo:rerun-if-changed=src/wrapper.h"); - // Read required environment variables. - let out_dir = env::var_os("OUT_DIR").expect("unsupported OUT_DIR"); - let target_os = env::var("CARGO_CFG_TARGET_OS").expect("invalid TARGET_OS environment variable"); - - let mut bindgen_builder = match target_os.as_str() { - "espidf" => get_builder_espidf(), - _ => get_builder(), - }; - - let mut dtls_backend = Option::None; - if cfg!(feature = "dtls") { - // We can only select one TLS backend at a time for libcoap, but cargo does not support mutually - // exclusive features, and it would be really bad if a project that uses multiple dependencies - // which depend on different TLS backends would not compile. - // Therefore, if multiple TLS backend features are enabled, we choose one based on the following - // priority order: gnutls > openssl > mbedtls > tinydtls, matching the order specified in - // https://github.com/obgm/libcoap/blob/develop/configure.ac#L494 - let mut multiple_backends = false; - if cfg!(feature = "dtls_backend_tinydtls") { - dtls_backend = Some(DtlsBackend::TinyDtls); - } - if cfg!(feature = "dtls_backend_mbedtls") { - if dtls_backend.is_some() { - multiple_backends = true; - } - println!("cargo:rerun-if-env-changed=MBEDTLS_LIBRARY_PATH"); - dtls_backend = Some(DtlsBackend::MbedTls); - } - if cfg!(feature = "dtls_backend_openssl") { - if dtls_backend.is_some() { - multiple_backends = true; - } - dtls_backend = Some(DtlsBackend::OpenSsl); - } - if cfg!(feature = "dtls_backend_gnutls") { - if dtls_backend.is_some() { - multiple_backends = true; - } - dtls_backend = Some(DtlsBackend::GnuTls); - } - if multiple_backends { - // more than one backend was set, so unwrapping is ok here. - println!("cargo:warning=Multiple DTLS backends enabled for libcoap-sys. Only one can be enabled, choosing {:?} as the backend to use. This may cause problems.", dtls_backend.as_ref().unwrap()); - } - if dtls_backend.is_none() { - println!("cargo:warning=No DTLS backend selected for libcoap-sys, aborting build."); - panic!("No DTLS backend selected for libcoap-sys, aborting build") - } - } - - // Build vendored library if feature was set. - if cfg!(feature = "vendored") && target_os.as_str() != "espidf" { - bindgen_builder = build_vendored_library(&out_dir, dtls_backend.as_ref(), bindgen_builder); - }; - - if target_os.as_str() != "espidf" { - // Tell cargo to link libcoap. - println!( - "cargo:rustc-link-lib={}{}", - cfg!(feature = "static").then(|| "static=").unwrap_or("dylib="), - format!( - "coap-3-{}", - &dtls_backend - .as_ref() - .map(|v| v.to_string()) - .unwrap_or_else(|| "notls".to_string()) - ) - .as_str() - ); - - // For the DTLS libraries, we need to tell cargo which external libraries to link. - // Note that these linker instructions have to be added *after* the linker instruction - // for libcoap itself, as some linkers require dependencies to be in reverse order. - if let Some(dtls_backend) = dtls_backend { - match dtls_backend { - DtlsBackend::TinyDtls => { - // Handled by tinydtls-sys - }, - DtlsBackend::OpenSsl => { - // Handled by openssl-sys - }, - DtlsBackend::MbedTls => { - // If mbedtls is vendored, mbedtls-sys-auto already takes care of linking. - if env::var_os("DEP_MBEDTLS_INCLUDE").is_none() { - // We aren't using mbedtls-sys-auto if we aren't vendoring (as it doesn't support - // mbedtls >= 3.0.0), so we need to tell cargo to link to mbedtls ourselves. - - // Try to find mbedtls using pkg-config, will emit cargo link statements if successful - if pkg_config::Config::new() - .statik(cfg!(feature = "static")) - .probe("mbedtls") - .is_err() - { - // couldn't find using pkg-config, just try linking with given library - // search path. - println!("cargo:rustc-link-lib=mbedtls",); - println!("cargo:rustc-link-lib=mbedx509",); - println!("cargo:rustc-link-lib=mbedcrypto",); - } - } - }, - DtlsBackend::GnuTls => { - // gnutls-sys is unmaintained, so we need to link to gnutls ourselves. - - // try pkg-config - if probe_library("gnutls").is_err() { - // if that doesn't work, try using the standard library search path. - println!("cargo:rustc-link-lib=gnutls") - } - }, - } - } - } - - let libcoap_defines = Rc::new(RefCell::new(LibcoapMetadata::default())); - - let cfg_info = Box::new(CoapDefineParser { - defines: Rc::clone(&libcoap_defines), - }); - - bindgen_builder = bindgen_builder - .header("src/wrapper.h") - .default_enum_style(EnumVariation::Rust { non_exhaustive: true }) - // Causes invalid syntax for some reason, so we have to disable it. - .generate_comments(false) - .dynamic_link_require_all(true) - .allowlist_function("(oscore|coap)_.*") - .allowlist_type("(oscore|coap)_.*") - .allowlist_var("(oscore|coap)_.*") - .allowlist_function("(OSCORE|COAP)_.*") - .allowlist_type("(OSCORE|COAP)_.*") - .allowlist_var("(OSCORE|COAP|LIBCOAP)_.*") - // We use the definitions made by the libc crate instead - .blocklist_type("sockaddr(_in|_in6)?") - .blocklist_type("in6?_(addr|port)(_t)?") - .blocklist_type("in6_addr__bindgen_ty_1") - .blocklist_type("(__)?socklen_t") - .blocklist_type("fd_set") - .blocklist_type("sa_family_t") - .blocklist_type("(__)?time_t") - .blocklist_type("__fd_mask") - // Are generated because they are typedef-ed inside of the C headers, blocklisting them - // will instead replace them with the appropriate rust types. - // See https://github.com/rust-lang/rust-bindgen/issues/1215 for an open issue concerning - // this problem. - .blocklist_type("__(u)?int(8|16|32|64|128)_t") - .size_t_is_usize(true) - .parse_callbacks(cfg_info); - if !cfg!(feature = "vendored") { - // Triggers a rebuild on every cargo build invocation if used for the vendored version, as - // the included headers seem to come from our built version. - // Should be fine though, as we already printed `cargo:rerun-if-changed=src/libcoap/` at the - // start of the file. - bindgen_builder = bindgen_builder.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); - } - let bindings = bindgen_builder.generate().expect("unable to generate bindings"); - - // Check if required features are available in libcoap. - let libcoap_defines = libcoap_defines.take(); - if libcoap_defines.feature_defines_available { - println!("cargo:rustc-cfg=feature_checks_available"); - for feature in COMPILE_TIME_FEATURE_CHECKS { - let feature_env_var_name = "CARGO_FEATURE_".to_string() + &feature.replace('-', "_").to_uppercase(); - if env::var(&feature_env_var_name).is_ok() && !libcoap_defines.feature_defines.contains(feature) { - panic!("Required feature {feature} is not available in the used version of libcoap!"); - } - } - if dtls_backend != libcoap_defines.dtls_backend { - // Should be fine, as applications should expect that the DTLS library could differ. - println!("cargo:warning=DTLS library used by libcoap does not match chosen one. This might lead to issues.") - } - } else { - println!("cargo:warning=The used version of libcoap does not provide a coap_defines.h file, either because it is too old (<4.3.5) or because this file is somehow not included. Compile-time feature checks are not available, and the availability of some features (small-stack, IPv4/IPv6,) cannot be asserted at all!"); - } - - let out_path = PathBuf::from(out_dir); - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("unable to write generated bindings to file"); -} diff --git a/libcoap-sys/build/bindings.rs b/libcoap-sys/build/bindings.rs new file mode 100644 index 00000000..c8f95868 --- /dev/null +++ b/libcoap-sys/build/bindings.rs @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/bindings.rs - Binding generation tools for the libcoap-sys build script. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ + +use std::{cell::RefCell, fmt::Debug, path::PathBuf, rc::Rc}; + +use anyhow::{Context, Result}; +use bindgen::callbacks::{DeriveTrait, ImplementsTrait, IntKind, ParseCallbacks}; + +use crate::metadata::{DtlsBackend, LibcoapDefineInfo, LibcoapFeature}; + +/// Implementation of bindgen's [ParseCallbacks] that allow reading some meta-information about the +/// used libcoap version from its defines (package version, supported features, ...) +#[derive(Debug, Default)] +pub struct LibcoapDefineParser { + defines: Rc>, +} + +impl LibcoapDefineParser { + pub fn new() -> (Rc>, Self) { + let target = std::env::var_os("TARGET").unwrap_or_default(); + let host = std::env::var_os("HOST").unwrap_or_default(); + + if target != host { + println!(concat!( + "cargo:warning=libcoap-rs compile-time feature checks may be inaccurate when cross", + " compiling, see https://libcoap.net/doc/reference/4.3.5/man_coap_supported.html", + " for more information." + )); + } + + let value: LibcoapDefineParser = Default::default(); + (Rc::clone(&value.defines), value) + } +} + +impl ParseCallbacks for LibcoapDefineParser { + fn int_macro(&self, name: &str, value: i64) -> Option { + let mut defines = self.defines.borrow_mut(); + defines.supported_features |= LibcoapFeature::features_from_define(name, value); + if let Some(dtls_backend) = DtlsBackend::library_from_define(name, value) { + if let Some(old_backend) = defines.dtls_backend.replace(dtls_backend) { + println!("cargo:warning=The libcoap header files indicate that more than one DTLS library is active at the same time ({dtls_backend} and {old_backend}), which should not be possible. Are the header paths misconfigured?"); + } + } + None + } + + fn str_macro(&self, name: &str, value: &[u8]) { + if name == "LIBCOAP_PACKAGE_VERSION" { + let version_str = String::from_utf8_lossy(value); + self.defines.borrow_mut().version = Some(version_str.to_string()) + } + } +} + +#[derive(Debug, Default)] +struct LibcoapBindingHelper; + +impl ParseCallbacks for LibcoapBindingHelper { + // Even if we don't use CargoCallbacks, we want to rebuild if relevant environment variables + // change. + fn read_env_var(&self, key: &str) { + println!("cargo:rerun-if-env-changed={key}") + } + + fn blocklisted_type_implements_trait(&self, name: &str, derive_trait: DeriveTrait) -> Option { + // This is based on what libc reports for Unix-based OSes + #[cfg(unix)] + match (name, derive_trait) { + ( + "struct epoll_event" | "fd_set" | "struct sockaddr_in" | "struct sockaddr_in6" | "struct sockaddr", + DeriveTrait::Debug | DeriveTrait::Hash | DeriveTrait::PartialEqOrPartialOrd | DeriveTrait::Copy, + ) => Some(ImplementsTrait::Yes), + ( + "struct epoll_event" | "fd_set" | "struct sockaddr_in" | "struct sockaddr_in6" | "struct sockaddr", + DeriveTrait::Default, + ) => Some(ImplementsTrait::No), + ( + "time_t" | "socklen_t" | "sa_family_t", + DeriveTrait::Debug + | DeriveTrait::Hash + | DeriveTrait::PartialEqOrPartialOrd + | DeriveTrait::Copy + | DeriveTrait::Default, + ) => Some(ImplementsTrait::Yes), + (_, _) => None, + } + #[cfg(not(unix))] + // Let's just assume that bindgen's default behavior is fine. + None + } +} + +pub fn generate_libcoap_bindings( + bindgen_builder_configurator: impl FnOnce(bindgen::Builder) -> Result, + rerun_on_header_file_changes: bool, +) -> Result { + let source_root = PathBuf::from( + std::env::var_os("CARGO_MANIFEST_DIR") + .expect("CARGO_MANIFEST_DIR is not set (are we not running as a cargo build script?)"), + ); + let mut builder = bindgen::Builder::default() + .header( + source_root + .join("src") + .join("wrapper.h") + .to_str() + .context("unable to convert header path to &str")? + .to_string(), + ) + .parse_callbacks(Box::new(LibcoapBindingHelper)) + .generate_comments(true) + .generate_cstr(true) + .allowlist_function("(oscore|coap)_.*") + .allowlist_type("(oscore|coap)_.*") + .allowlist_var("(oscore|coap)_.*") + .allowlist_function("(OSCORE|COAP)_.*") + .allowlist_type("(OSCORE|COAP)_.*") + .allowlist_var("(OSCORE|COAP|LIBCOAP)_.*") + // We use the definitions made by the libc crate instead + .blocklist_type("sockaddr(_in|_in6)?") + .blocklist_type("in6?_(addr|port)(_t)?") + .blocklist_type("in6_addr__bindgen_ty_1") + .blocklist_type("(__)?socklen_t") + .blocklist_type("fd_set") + .blocklist_type("sa_family_t") + .blocklist_type("(__)?time_t") + .blocklist_type("__fd_mask") + .blocklist_type("epoll_event") + // Are generated because they are typedef-ed inside of the C headers, blocklisting them + // will instead replace them with the appropriate rust types. + // See https://github.com/rust-lang/rust-bindgen/issues/1215 for an open issue concerning + // this problem. + .blocklist_type("__(u)?int(8|16|32|64|128)_t") + .size_t_is_usize(true); + + // The `rerun_on_header_files()` method only applies to the top level header (in our case + // src/wrapper.h, so we must not add CargoCallbacks at all if we want to get our desired + // effect). + // To still handle environment variable changes properly, LibcoapBindingHeader already includes + // the relevant parts of CargoCallbacks. + if rerun_on_header_file_changes { + builder = builder.parse_callbacks(Box::new( + bindgen::CargoCallbacks::new().rerun_on_header_files(rerun_on_header_file_changes), + )) + } + builder = bindgen_builder_configurator(builder)?; + + builder.generate().context("unable to generate bindings") +} diff --git a/libcoap-sys/build/build_system/esp_idf.rs b/libcoap-sys/build/build_system/esp_idf.rs new file mode 100644 index 00000000..02379a47 --- /dev/null +++ b/libcoap-sys/build/build_system/esp_idf.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/build_system/esp_idf.rs - ESP-IDF build system for libcoap-sys. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ +use std::{env, fs::File, io::Write, iter::once, path::PathBuf}; + +use anyhow::{anyhow, Context, Result}; +use enumset::EnumSet; +use syn::{ForeignItem, Ident, Item}; +use version_compare::Version; + +use crate::{ + build_system::BuildSystem, + metadata::{DtlsBackend, LibcoapFeature}, +}; + +pub struct EspIdfBuildSystem { + out_dir: PathBuf, + esp_idf_bindings_file: PathBuf, + requested_features: EnumSet, + bypass_compile_time_feature_checks: bool, +} + +impl EspIdfBuildSystem { + pub fn new( + out_dir: PathBuf, + requested_features: EnumSet, + requested_dtls_backend: Option, + bypass_compile_time_feature_checks: bool, + ) -> Result { + embuild::espidf::sysenv::output(); + let esp_idf_bindings_file = env::var_os("DEP_ESP_IDF_ROOT") + .map(PathBuf::from) + .expect("Environment variable DEP_ESP_IDF_ROOT has not been set by esp-idf-sys") + .join("bindings.rs"); + + if let Some(backend) = requested_dtls_backend { + if backend != DtlsBackend::MbedTls { + return Err(anyhow!("libcoap only supports the MbedTLS DTLS backend when compiling for ESP-IDF, but you have requested the {backend} backend.")); + } + } + + Ok(Self { + out_dir, + esp_idf_bindings_file, + requested_features, + bypass_compile_time_feature_checks, + }) + } +} + +impl BuildSystem for EspIdfBuildSystem { + fn detected_features(&self) -> Option> { + // We ensure the availability of some requested features by generating checks for the + // cfg values set by esp-idf-sys based on the used sdkconfig. + // Therefore, we can tell the build script feature checker that all requested features are + // available (to mute the warning about there being no feature check). + + // However, do warn the user if features are requested that cannot be checked this way, but + // would be checkable if the defines-based checker was used. + let uncheckable_features: EnumSet = self + .requested_features + .iter() + .filter(|v| v.define_name().is_some() && v.sdkconfig_flag_name().is_none()) + .collect(); + if !uncheckable_features.is_empty() { + println!( + concat!( + "cargo:warning=When building for ESP-IDF, the availability of the following ", + "requested features that usually can be checked during compile time can only", + "be checked during runtime instead: {}" + ), + uncheckable_features + .iter() + .map(|v| v.as_str()) + .collect::>() + .join(", ") + ) + } + + Some(self.requested_features) + } + + fn detected_dtls_backend(&self) -> Option { + // If DTLS is a requested feature, we check during compile time whether MbedTLS is + // supposed to be enabled. + self.requested_features + .contains(LibcoapFeature::Dtls) + .then_some(DtlsBackend::MbedTls) + } + + fn version(&self) -> Option { + None + } + + fn generate_bindings(&mut self) -> Result { + // Find, read and parse the Rust bindings generated by esp-idf-sys. + let esp_bindings_file = + std::fs::read_to_string(&self.esp_idf_bindings_file).context("unable to read ESP-IDF bindings file")?; + let parsed_esp_bindings_file = + syn::parse_file(&esp_bindings_file).context("unable to parse ESP-IDF bindings file")?; + + // Create file for our own bindings. + let bindings_file_path = self.out_dir.join("bindings.rs"); + let mut libcoap_bindings_file = File::create(&bindings_file_path).context("unable to create bindings file")?; + + // Iterate over all items in the esp-idf-sys bindings file. + for item in parsed_esp_bindings_file.items { + // Find the list of identifiers provided by this item. + let ident: Box> = match item { + Item::Const(v) => Box::new(once(v.ident)), + Item::Enum(v) => Box::new(once(v.ident)), + Item::ExternCrate(v) => Box::new(once(v.ident)), + Item::Fn(v) => Box::new(once(v.sig.ident)), + Item::Macro(v) => Box::new(v.ident.into_iter()), + Item::Mod(v) => Box::new(once(v.ident)), + Item::Static(v) => Box::new(once(v.ident)), + Item::Struct(v) => Box::new(once(v.ident)), + Item::Trait(v) => Box::new(once(v.ident)), + Item::TraitAlias(v) => Box::new(once(v.ident)), + Item::Type(v) => Box::new(once(v.ident)), + Item::Union(v) => Box::new(once(v.ident)), + Item::ForeignMod(v) => Box::new(v.items.into_iter().filter_map(|fe| match fe { + ForeignItem::Fn(fi) => Some(fi.sig.ident), + ForeignItem::Static(fi) => Some(fi.ident), + ForeignItem::Type(fi) => Some(fi.ident), + _ => None, + })), + _ => Box::new(std::iter::empty()), + }; + + for ident in ident.map(|i| i.to_string()) { + let lowercase_ident = ident.to_lowercase(); + // If the item belongs to the libcoap crate (starts with coap or oscore), re-export it in our bindings. + if lowercase_ident.starts_with("coap") || lowercase_ident.starts_with("oscore") { + writeln!(&mut libcoap_bindings_file, "pub use esp_idf_sys::{};", ident) + .context("unable to write to bindings file")?; + } + } + } + + if !self.bypass_compile_time_feature_checks { + for (feature_name, feature_flag) in self + .requested_features + .iter() + .filter_map(|v| v.sdkconfig_flag_name().map(|flag| (v.as_str(), flag))) + { + let feature_flag_lowercase = feature_flag.to_lowercase(); + // For some reason, embuild adds expected cfg flags for some, but not all + // feature-related sdkconfig flags, causing warnings if we don't do this. + println!("cargo::rustc-check-cfg=cfg(esp_idf_{})", feature_flag_lowercase); + + writeln!( + &mut libcoap_bindings_file, + // Only show these errors if the coap component is enabled at all (in order to + // only show the relevant compilation error). + "#[cfg(all(esp_idf_comp_espressif__coap_enabled, not(esp_idf_{})))]", + feature_flag_lowercase + ) + .context("unable to write to bindings file")?; + writeln!( + &mut libcoap_bindings_file, + concat!( + "compile_error!(\"Requested feature \\\"{}\\\" is not enabled\n", + "in ESP-IDF sdkconfig.defaults (set `CONFIG_{}=y` to fix this)\");" + ), + feature_flag, feature_name + ) + .context("unable to write to bindings file")?; + } + } + + Ok(bindings_file_path) + } +} diff --git a/libcoap-sys/build/build_system/manual.rs b/libcoap-sys/build/build_system/manual.rs new file mode 100644 index 00000000..fc18437c --- /dev/null +++ b/libcoap-sys/build/build_system/manual.rs @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/build_system/manual.rs - Manual build system for libcoap-sys. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ + +use std::{cell::RefCell, env, env::VarError, path::PathBuf}; + +use anyhow::{Context, Result}; +use enumset::EnumSet; +use version_compare::Version; + +use crate::{ + bindings::{generate_libcoap_bindings, LibcoapDefineParser}, + build_system::BuildSystem, + metadata::{DtlsBackend, LibcoapDefineInfo, LibcoapFeature}, +}; +pub struct ManualBuildSystem { + out_dir: PathBuf, + include_dirs: Vec, + define_info: Option, +} + +impl ManualBuildSystem { + pub fn link_with_libcoap(out_dir: PathBuf, requested_dtls_backend: Option) -> Result { + println!("cargo:rerun-if-env-changed=LIBCOAP_RS_INCLUDE_DIRS"); + println!("cargo:rerun-if-env-changed=LIBCOAP_RS_LIB_DIRS"); + println!("cargo:rerun-if-env-changed=LIBCOAP_RS_STATIC"); + println!("cargo:rerun-if-env-changed=LIBCOAP_RS_ADDITIONAL_LIBRARIES"); + + // Parse environment variables. + let include_dirs: Vec = env::var("LIBCOAP_RS_INCLUDE_DIRS") + .context("LIBCOAP_RS_INCLUDE_DIRS has not been set or is not valid unicode")? + .split(":") + .map(PathBuf::from) + .collect(); + let lib_dirs: Vec = env::var("LIBCOAP_RS_LIB_DIRS") + .context("LIBCOAP_RS_LIB_DIRS has not been set or is not valid unicode")? + .split(":") + .map(PathBuf::from) + .collect(); + let additional_libraries: Vec = match env::var("LIBCOAP_RS_ADDITIONAL_LIBRARIES") { + Ok(v) => v.split(":").map(ToString::to_string).collect(), + Err(VarError::NotPresent) => vec![], + Err(e) => return Err(e).context("Unable to parse LIBCOAP_RS_ADDITIONAL_LIBRARIES environment variable."), + }; + let use_static = match env::var("LIBCOAP_RS_STATIC") { + Ok(v) => !(v == "0" || v.is_empty()), + Err(VarError::NotPresent) => false, + Err(e) => return Err(e).context("Unable to parse LIBCOAP_RS_STATIC environment variable."), + }; + + // Determine name of libcoap library. + let library_name = if let Some(backend) = requested_dtls_backend { + format!("coap-3-{}", backend.library_suffix()) + } else { + "coap-3".to_string() + }; + + // Add given library paths to search path. + for lib_dir in lib_dirs { + println!("cargo:rustc-link-search={}", lib_dir.display()); + } + // Instruct rustc to link with the desired version of libcoap. + println!( + "cargo:rustc-link-lib={}{}", + if use_static { "static=" } else { "" }, + library_name + ); + + // Instruct rustc to link with additional libraries (note that this *must* happen *after* + // linking with libcoap, at least with some linkers). + for additional_library in additional_libraries { + println!("cargo:rustc-link-lib={}", additional_library); + } + + Ok(Self { + out_dir, + include_dirs, + define_info: None, + }) + } +} + +impl BuildSystem for ManualBuildSystem { + fn detected_features(&self) -> Option> { + self.define_info.as_ref().map(|v| v.supported_features) + } + + fn detected_dtls_backend(&self) -> Option { + self.define_info.as_ref().and_then(|v| v.dtls_backend) + } + + fn version(&self) -> Option { + self.define_info + .as_ref() + .and_then(|i| i.version.as_ref().map(|v| Version::from(v.as_str()))) + .expect("unable to parse version string obtained from coap_defines.h") + } + + fn generate_bindings(&mut self) -> anyhow::Result { + let (define_info, define_parser) = LibcoapDefineParser::new(); + let bindings = generate_libcoap_bindings( + |builder| { + Ok(builder + .parse_callbacks(Box::new(define_parser)) + // If the pkg-config provided include path coincides with a system include directory, + // setting the "-I{}" command line argument will not do anything, potentially resulting + // in clang using different CoAP headers than provided by pkg-config, e.g., if there + // is an old libcoap in /usr/local/include, but the desired one has its headers in /usr/include. + // Therefore, we use `-isystem` instead. + // See also: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-I-dir + .clang_args(self.include_dirs.iter().map(|v| format!("-isystem{}", v.display())))) + }, + true, + )?; + + self.define_info = Some(RefCell::take(&define_info)); + + let out_path = self.out_dir.join("bindings.rs"); + bindings + .write_to_file(&out_path) + .context("unable to write bindings to file")?; + Ok(out_path) + } +} diff --git a/libcoap-sys/build/build_system/mod.rs b/libcoap-sys/build/build_system/mod.rs new file mode 100644 index 00000000..ebc12399 --- /dev/null +++ b/libcoap-sys/build/build_system/mod.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/build_system/mod.rs - Basic definitions for libcoap-sys build systems. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ +use std::path::PathBuf; + +use anyhow::Result; +use enumset::EnumSet; +use version_compare::Version; + +use crate::metadata::{DtlsBackend, LibcoapFeature}; + +pub mod esp_idf; +pub mod manual; +pub mod pkgconfig; +pub mod vendored; + +/// Trait that is implemented by build systems for libcoap. +/// +/// It is assumed that the constructor structs implementing this trait already perform all +/// necessary steps to link against libcoap, and that only binding generation and compile-time +/// checks remain. +/// +/// If you want to implement your own build system, you may want to use the `manual` build system +/// as a basis. +/// +/// In order to implement the compile-time checks, you may want to use +/// [`LibcoapDefineParser`](crate::bindings::LibcoapDefineParser), at least in cases where you have +/// the corresponding `coap_defines.h` header file available. +pub trait BuildSystem { + /// Returns the set of features that are supported by the linked version of libcoap, or `None` + /// if this detection is not possible or has not been performed yet. + /// + /// It is assumed that after `generate_bindings()` is called, a `None` return value indicates + /// that compile-time feature detection is unsupported. + fn detected_features(&self) -> Option>; + + /// Returns the DTLS backend that has been used in the `libcoap` version this build + /// system built against, or `None` if this detection is not possible or has not been performed + /// yet. + /// + /// It is assumed that after `generate_bindings()` is called, a `None` return value indicates + /// that compile-time DTLS library detection is unsupported. + fn detected_dtls_backend(&self) -> Option; + + /// Returns the `libcoap` library version this build system built against, or `None` if this + /// detection is not possible or has not been performed yet. + /// + /// It is assumed that after `generate_bindings()` is called, a `None` return value indicates + /// that compile-time DTLS library detection is unsupported. + fn version(&self) -> Option; + + /// Generate Rust bindings to the `libcoap` C library that we linked against and return a + /// `PathBuf` to the generated bindings file to use. + fn generate_bindings(&mut self) -> Result; +} diff --git a/libcoap-sys/build/build_system/pkgconfig.rs b/libcoap-sys/build/build_system/pkgconfig.rs new file mode 100644 index 00000000..3d972930 --- /dev/null +++ b/libcoap-sys/build/build_system/pkgconfig.rs @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/build_system/pkgconfig.rs - pkg-config build system for libcoap-sys. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ + +use std::{cell::RefCell, path::PathBuf}; + +use anyhow::{anyhow, Context}; +use enumset::EnumSet; +use pkg_config::Library; +use version_compare::Version; + +use crate::{ + bindings::{generate_libcoap_bindings, LibcoapDefineParser}, + build_system::BuildSystem, + metadata::{DtlsBackend, LibcoapDefineInfo, LibcoapFeature, MINIMUM_LIBCOAP_VERSION}, +}; + +pub struct PkgConfigBuildSystem { + define_info: Option, + out_dir: PathBuf, + library: Library, +} + +impl PkgConfigBuildSystem { + /// Obtain some built version of libcoap and set the appropriate linker flags to link with it + /// (and its dependencies, if any). + pub fn link_with_libcoap(out_dir: PathBuf, requested_dtls_backend: Option) -> anyhow::Result { + let mut prober = pkg_config::Config::new(); + let prober = prober + .atleast_version(MINIMUM_LIBCOAP_VERSION) + .cargo_metadata(true) + .env_metadata(true); + let library = if let Some(requested_dtls_backend) = requested_dtls_backend { + // Use the libcoap version corresponding to the requested DTLS library, if one has been set. + prober.probe(&format!("libcoap-3-{}", requested_dtls_backend.library_suffix())) + } else { + // Otherwise, use the "default" version. + prober.probe("libcoap-3") + }; + + library + .map(|lib| Self { + out_dir, + define_info: None, + library: lib, + }) + .context("unable to probe library using pkg-config") + } +} + +impl BuildSystem for PkgConfigBuildSystem { + fn detected_features(&self) -> Option> { + self.define_info.as_ref().map(|v| v.supported_features) + } + + fn detected_dtls_backend(&self) -> Option { + self.define_info.as_ref().and_then(|v| v.dtls_backend) + } + + fn version(&self) -> Option { + Version::from(&self.library.version) + .map(Some) + .expect("unable to parse version string obtained from pkg-config") + } + + fn generate_bindings(&mut self) -> anyhow::Result { + let (define_info, define_parser) = LibcoapDefineParser::new(); + let bindings = generate_libcoap_bindings( + |builder| { + Ok(builder + .parse_callbacks(Box::new(define_parser)) + // If the pkg-config provided include path coincides with a system include directory, + // setting the "-I{}" command line argument will not do anything, potentially resulting + // in clang using different CoAP headers than provided by pkg-config, e.g., if there + // is an old libcoap in /usr/local/include, but the desired one has its headers in /usr/include. + // Therefore, we use `-isystem` instead. + // See also: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-I-dir + .clang_args( + self.library + .include_paths + .iter() + .map(|v| format!("-isystem{}", v.display())), + )) + }, + true, + )?; + + self.define_info = Some(RefCell::take(&define_info)); + + if let Some(version) = &self.define_info.as_ref().unwrap().version { + if Version::from(&self.library.version) != Version::from(version) { + return Err(anyhow!("The library version indicated by pkg-config does not match the one indicated by the headers. Are the include paths misconfigured?")); + } + } + + let out_path = self.out_dir.join("bindings.rs"); + bindings + .write_to_file(&out_path) + .context("unable to write bindings to file")?; + Ok(out_path) + } +} diff --git a/libcoap-sys/build/build_system/vendored.rs b/libcoap-sys/build/build_system/vendored.rs new file mode 100644 index 00000000..58d2d891 --- /dev/null +++ b/libcoap-sys/build/build_system/vendored.rs @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/build_system/vendored.rs - Vendored build system for libcoap-sys. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ + +use std::{ + cell::RefCell, + env, + env::VarError, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::{anyhow, ensure, Context, Result}; +use enumset::EnumSet; +use version_compare::Version; + +use crate::{ + bindings::{generate_libcoap_bindings, LibcoapDefineParser}, + build_system::BuildSystem, + metadata::{DtlsBackend, LibcoapDefineInfo, LibcoapFeature}, +}; + +const VENDORED_LIBCOAP_VERSION: &str = "4.3.5"; + +pub struct VendoredBuildSystem { + out_dir: PathBuf, + define_info: Option, + include_paths: Vec, +} + +impl VendoredBuildSystem { + /// Obtain some built version of libcoap and set the appropriate linker flags to link with it + /// (and its dependencies, if any). + pub fn build_libcoap( + out_dir: PathBuf, + requested_features: EnumSet, + requested_dtls_backend: Option, + ) -> Result { + println!("cargo:rerun-if-changed=src/libcoap"); + + let libcoap_src_dir = out_dir.join("libcoap"); + let libcoap_build_prefix = out_dir.join("build"); + + // Even though libcoap supports out-of-source builds, autogen.sh (or the corresponding + // autotools) modify files in the source tree, which causes verification problems when + // running cargo package. + // Therefore, we copy the libcoap source over to the output directory and build from there. + let copy_options = fs_extra::dir::CopyOptions { + overwrite: true, + ..Default::default() + }; + std::fs::create_dir_all(&libcoap_src_dir)?; + std::fs::create_dir_all(&libcoap_build_prefix)?; + std::fs::remove_dir_all(&libcoap_src_dir).context("unable to clear libcoap build directory")?; + std::fs::remove_dir_all(&libcoap_build_prefix).context("unable to clear libcoap build directory")?; + std::fs::create_dir_all(&libcoap_build_prefix)?; + std::fs::create_dir_all(&libcoap_src_dir)?; + fs_extra::dir::copy( + Path::new(env!("CARGO_MANIFEST_DIR")).join("src").join("libcoap"), + &out_dir, + ©_options, + ) + .context("unable to prepare libcoap build source directory")?; + + env::set_current_dir(&libcoap_src_dir).expect("unable to change to libcoap build dir"); + ensure!( + Command::new(libcoap_src_dir.join("autogen.sh")) + .status() + .context("unable to execute autogen.sh")? + .success(), + "autogen.sh returned an error code" + ); + + let mut build_config = autotools::Config::new(&libcoap_src_dir); + + let mut build_config = build_config + // Disable shared library compilation because the vendored library will always be + // statically linked + .out_dir(&libcoap_build_prefix) + .disable("shared", None) + .enable("static", None) + // Disable any documentation for vendored C library + .disable("documentation", None) + .disable("doxygen", None) + .disable("manpages", None) + // This would install the license into the documentation directory, but we don't use the + // generated documentation anywhere. + .disable("license-install", None) + // Disable tests and examples as well as test coverage + .disable("tests", None) + .disable("examples", None) + .disable("gcov", None) + // We do not include the TinyDTLS submodule in our source distribution, make sure that + // libcoap doesn't try to use it. + // This will generate a warning message if TinyDTLS isn't explicitly enabled, but this + // has no negative consequences. + .without("submodule-tinydtls", None); + + for feature in requested_features { + if let Some(feature_flag) = feature.configure_flag_name() { + build_config = build_config.enable(feature_flag, None); + } + } + for feature in EnumSet::::all().difference(requested_features) { + if let Some(feature_flag) = feature.configure_flag_name() { + build_config = build_config.disable(feature_flag, None); + } + } + let pkg_config_path_bak = env::var_os("PKG_CONFIG_PATH"); + + let link_using_pkgconfig = if requested_features.contains(LibcoapFeature::Dtls) { + // Check if we have any DTLS libraries already added as a Rust dependency. + // For each one, set the appropriate PKG_CONFIG_PATHs, CFLAGS and/or LIBS to use them + // instead of system versions if they are going to be used. + let mut additional_pkg_config_paths: Vec = vec![]; + // May be unused if none of the DTLS crate features has been enabled. + #[allow(unused_mut)] + let mut dtls_libraries_linked_by_other_crates = EnumSet::::empty(); + #[cfg(feature = "dtls-tinydtls-sys")] + { + let (pkg_config_path, linked) = Self::configure_tinydtls_sys(build_config)?; + if let Some(pkg_config_path) = pkg_config_path { + additional_pkg_config_paths.push(pkg_config_path) + } + if linked { + dtls_libraries_linked_by_other_crates |= DtlsBackend::TinyDtls + } + } + #[cfg(feature = "dtls-openssl-sys")] + { + let (pkg_config_path, linked) = Self::configure_openssl_sys(build_config)?; + if let Some(pkg_config_path) = pkg_config_path { + additional_pkg_config_paths.push(pkg_config_path) + } + if linked { + dtls_libraries_linked_by_other_crates |= DtlsBackend::OpenSsl + } + } + #[cfg(feature = "dtls-mbedtls-sys")] + { + let (pkg_config_path, linked) = Self::configure_mbedtls_sys(&out_dir, build_config)?; + if let Some(pkg_config_path) = pkg_config_path { + additional_pkg_config_paths.push(pkg_config_path) + } + if linked { + dtls_libraries_linked_by_other_crates |= DtlsBackend::MbedTls + } + } + + // Add libcoap's own build directory to the PKG_CONFIG_PATH (might be used later on to + // find the generated .pc file to link against libcoap). + additional_pkg_config_paths.push(libcoap_build_prefix.join("lib").join("pkgconfig")); + + let pkg_config_path = match env::var("PKG_CONFIG_PATH") { + Ok(v) => { + format!( + "{}:{}", + additional_pkg_config_paths + .iter() + .map(|v| v + .to_str() + .ok_or(anyhow!("unable to convert PKG_CONFIG_PATH value to UTF-8"))) + .collect::>>()? + .join(":"), + v + ) + }, + Err(VarError::NotPresent) => additional_pkg_config_paths + .iter() + .map(|v| { + v.to_str() + .ok_or(anyhow!("unable to convert PKG_CONFIG_PATH value to UTF-8")) + }) + .collect::>>()? + .join(":"), + Err(e) => Err(e).context("PKG_CONFIG_PATH is not a valid UTF-8 string")?, + }; + build_config.env("PKG_CONFIG_PATH", &pkg_config_path); + + // SAFETY: We are single-threaded here. + unsafe { env::set_var("PKG_CONFIG_PATH", pkg_config_path) } + + // Choose a DTLS backend. + let selected_dtls_backend = if let Some(requested_dtls_backend) = requested_dtls_backend { + // If one has been explicitly requested by the user, use that one. + Some(requested_dtls_backend) + } else if cfg!(feature = "dtls-openssl-sys") { + // If we do have a library already linked via a rust dependency, prefer those, but + // maintain the order also used in libcoap itself. + Some(DtlsBackend::OpenSsl) + } else if cfg!(feature = "dtls-mbedtls-sys") { + Some(DtlsBackend::MbedTls) + } else if cfg!(feature = "dtls-tinydtls-sys") { + Some(DtlsBackend::TinyDtls) + } else { + // Otherwise, we will rely on libcoap to find us a suitable DTLS library. + None + }; + + // If we are not using one of the DTLS libraries already linked by another rust crate, + // we need to link the DTLS library as well. Set a boolean variable to keep track of this. + let dtls_library_already_linked = if let Some(selected_dtls_backend) = selected_dtls_backend { + build_config = build_config.with(selected_dtls_backend.as_str(), None); + if dtls_libraries_linked_by_other_crates.contains(selected_dtls_backend) { + println!("cargo:rustc-cfg=used_dtls_crate=\"{}\"", selected_dtls_backend.as_str()) + } + dtls_libraries_linked_by_other_crates.contains(selected_dtls_backend) + } else { + false + }; + + !dtls_library_already_linked + } else { + false + }; + + build_config.build(); + + if link_using_pkgconfig { + // We need to link both libcoap and its DTLS library. Use the generated pkg-config + // file to determine how to do this. + let library = pkg_config::Config::new().statik(true).exactly_version(VENDORED_LIBCOAP_VERSION).probe("libcoap-3").context("unable to link against build version of libcoap using pkg-config (which is necessary if you're not using a Rust dependency to link the DTLS library)")?; + + // SAFETY: We are still single-threaded here. + unsafe { env::set_var("PKG_CONFIG_PATH", pkg_config_path_bak.unwrap_or_default()) } + Ok(Self { + out_dir, + define_info: None, + include_paths: library.include_paths, + }) + } else { + // SAFETY: We are still single-threaded here. + unsafe { env::set_var("PKG_CONFIG_PATH", pkg_config_path_bak.unwrap_or_default()) } + println!( + "cargo:rustc-link-search={}", + libcoap_build_prefix + .join("lib") + .to_str() + .context("unable to convert OUT_DIR to a valid UTF-8 string.")? + ); + println!("cargo:rustc-link-lib=static=coap-3"); + Ok(Self { + out_dir, + define_info: None, + include_paths: vec![libcoap_build_prefix.join("include")], + }) + } + } + + #[cfg(feature = "dtls-tinydtls-sys")] + fn configure_tinydtls_sys(build_config: &mut autotools::Config) -> Result<(Option, bool)> { + if env::var_os("TinyDTLS_CFLAGS").is_some() || env::var_os("TinyDTLS_LIBS").is_some() { + // Do not use tinydtls-sys if the user manually set either the corresponding LIBS or + // CFLAGS variable. + // However, do warn the user that this might cause issues. + println!("cargo:warning=You have enabled the tinydtls-sys dependency, but have overridden either the TinyDTLS_CFLAGS or TinyDTLS_LIBS environment variable used by libcoap to find TinyDTLS."); + println!("cargo:warning=Note that attempting to link more than one version of the same library at once may cause unexpected issues and/or cryptic compilation errors, especially if both versions are statically linked."); + Ok((None, false)) + } else { + let tinydtls_include = env::var_os("DEP_TINYDTLS_INCLUDE") + .expect("tinydtls-sys dependency has been added, but DEP_TINYDTLS_INCLUDE has not been set"); + let tinydtls_libs = env::var_os("DEP_TINYDTLS_LIBS") + .expect("tinydtls-sys dependency has been added, but DEP_TINYDTLS_LIBS has not been set"); + build_config.env( + "TinyDTLS_CFLAGS", + format!( + "-I{} -I{}", + tinydtls_include + .to_str() + .context("DEP_TINYDTLS_INCLUDE path is not a valid UTF-8 string")?, + Path::new(&tinydtls_include) + .join("tinydtls") + .to_str() + .context("DEP_TINYDTLS_INCLUDE path is not a valid UTF-8 string")? + ), + ); + + // Need to set TinyDTLS_LIBS explicitly to force static linking (TinyDTLS also builds a shared version of the library). + build_config.env( + "TinyDTLS_LIBS", + format!( + "-L{} -l:libtinydtls.a", + tinydtls_libs + .to_str() + .context("DEP_TINYDTLS_LIBS path is not a valid UTF-8 string")? + ), + ); + + // Add TinyDTLS's pkg-config directory to the path for version checking. + Ok((Some(PathBuf::from(tinydtls_libs).join("lib").join("pkgconfig")), true)) + } + } + + #[cfg(feature = "dtls-openssl-sys")] + fn configure_openssl_sys(_build_config: &mut autotools::Config) -> Result<(Option, bool)> { + if env::var_os("OpenSSL_CFLAGS").is_some() || env::var_os("OpenSSL_LIBS").is_some() { + // Do not use tinydtls-sys if the user manually set either the corresponding LIBS or + // CFLAGS variable. + // However, do warn the user that this might cause issues. + println!("cargo:warning=You have enabled the openssl-sys dependency, but have overridden either the OpenSSL_CFLAGS or OpenSSL_LIBS environment variable used by libcoap to find OpenSSL."); + println!("cargo:warning=Note that attempting to link more than one version of the same library at once may cause unexpected issues and/or cryptic compilation errors, especially if both versions are statically linked."); + Ok((None, false)) + } else { + let openssl_include = env::var_os("DEP_OPENSSL_INCLUDE") + .expect("openssl-sys dependency has been added, but DEP_OPENSSL_INCLUDE has not been set"); + let openssl_libs = Path::new(openssl_include.as_os_str()) + .parent() + .context("DEP_OPENSSL_INCLUDE has no parent directory")? + .join("lib"); + + // Just add the OpenSSL directory to the PKG_CONFIG_PATH, that way libcoap will find it. + Ok((Some(openssl_libs.join("pkgconfig")), true)) + } + } + + #[cfg(feature = "dtls-mbedtls-sys")] + fn configure_mbedtls_sys(out_dir: &Path, build_config: &mut autotools::Config) -> Result<(Option, bool)> { + if env::var_os("MbedTLS_CFLAGS").is_some() || env::var_os("MbedTLS_LIBS").is_some() { + // Do not use tinydtls-sys if the user manually set either the corresponding LIBS or + // CFLAGS variable. + // However, do warn the user that this might cause issues. + println!("cargo:warning=You have enabled the mbedtls-sys dependency, but have overridden either the MbedTLS_CFLAGS or MbedTLS_LIBS environment variable used by libcoap to find MbedTLS."); + println!("cargo:warning=Note that attempting to link more than one version of the same library at once may cause unexpected issues and/or cryptic compilation errors, especially if both versions are statically linked."); + Ok((None, false)) + } else { + let mbedtls_include = env::var_os("DEP_MBEDTLS_INCLUDE") + .expect("mbedtls-sys dependency has been added, but DEP_MBEDTLS_INCLUDE has not been set"); + + // Can't use pkg-config here, as pkg-config was only added to MbedTLS recently. + + // the config.h of mbedtls-sys-auto is generated separately from all other + // includes in the root of mbedtls-sys-auto's OUT_DIR. + // In order to let libcoap read use the correct config file, we need to copy + // this file into our own OUT_DIR under include/mbedtls/config.h, so that we + // can then set OUT_DIR/include as an additional include path. + let config_h = env::var_os("DEP_MBEDTLS_CONFIG_H") + .expect("DEP_MBEDTLS_INCLUDE is set but DEP_MBEDTLS_CONFIG_H is not"); + + let config_path = Path::new(&config_h); + let out_include = Path::new(&out_dir).join("include"); + std::fs::create_dir_all(out_include.join("mbedtls")) + .context("unable to prepare include directory for mbedtls config.h")?; + std::fs::copy(config_path, out_include.join("mbedtls").join("config.h")) + .context("unable to copy mbedtls config.h to include directory")?; + let mbedtls_library_path = config_path + .parent() + .context("DEP_MBEDTLS_CONFIG_H has no parent directory")? + .join("build") + .join("library"); + + build_config.env( + "MbedTLS_CFLAGS", + format!( + "-I{} -I{}", + out_include.to_str().expect("OUT_DIR is not a valid UTF-8 string"), + mbedtls_include + .to_str() + .expect("DEP_MBEDTLS_INCLUDE is not a valid UTF-8 string") + ), + ); + build_config.env( + "MbedTLS_LIBS", + format!( + "-L{0} -l:libmbedtls.a -l:libmbedcrypto.a -l:libmbedx509.a", + mbedtls_library_path + .to_str() + .expect("DEP_MBEDTLS_CONFIG_H is not a valid string"), + ), + ); + + // If MbedTLS_CFLAGS and MbedTLS_LIBS are both set, libcoap will fall back to + // determining the library version using other methods. No need to add to pkg-config + // path here (as of now). + Ok((None, true)) + } + } +} + +impl BuildSystem for VendoredBuildSystem { + fn detected_features(&self) -> Option> { + self.define_info.as_ref().map(|v| v.supported_features) + } + + fn detected_dtls_backend(&self) -> Option { + self.define_info.as_ref().and_then(|v| v.dtls_backend) + } + + fn version(&self) -> Option { + Version::from(VENDORED_LIBCOAP_VERSION) + } + + fn generate_bindings(&mut self) -> anyhow::Result { + let (define_info, define_parser) = LibcoapDefineParser::new(); + let bindings = generate_libcoap_bindings( + |builder| { + Ok(builder + .parse_callbacks(Box::new(define_parser)) + // If the pkg-config provided include path coincides with a system include directory, + // setting the "-I{}" command line argument will not do anything, potentially resulting + // in clang using different CoAP headers than provided by pkg-config, e.g., if there + // is an old libcoap in /usr/local/include, but the desired one has its headers in /usr/include. + // Therefore, we use `-isystem` instead. + // See also: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-I-dir + .clang_args(self.include_paths.iter().map(|v| format!("-isystem{}", v.display())))) + }, + // Do not run on header file changes, as this will cause cargo to __always__ rebuild, + // no matter what. + // This is probably related to the fact that the headers are only generated during + // build. + // Without changes to the actual source code in src, crate features or environment + // variables that influence this build script (which are already covered by + // cargo:rerun-if-env-changed), the headers will never change and necessitate a rebuild, + // so this should be fine. + false, + )?; + // Just to make sure we do actually run if the libcoap source code changed. + println!("cargo:rerun-if-changed=src/libcoap"); + + self.define_info = Some(RefCell::take(&define_info)); + + if let Some(version) = &self.define_info.as_ref().unwrap().version { + if Version::from(VENDORED_LIBCOAP_VERSION) != Version::from(version) { + return Err(anyhow!("The library version indicated by the headers does not match the vendored version that should be in use. Are the include paths misconfigured?")); + } + } + + let out_path = self.out_dir.join("bindings.rs"); + bindings + .write_to_file(&out_path) + .context("unable to write bindings to file")?; + Ok(out_path) + } +} diff --git a/libcoap-sys/build/main.rs b/libcoap-sys/build/main.rs new file mode 100644 index 00000000..96c7baae --- /dev/null +++ b/libcoap-sys/build/main.rs @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/main.rs - Build script entrypoint for libcoap-sys. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ + +use std::{env, env::VarError, path::PathBuf}; + +use anyhow::{anyhow, bail, Context, Result}; +use enumset::EnumSet; +use version_compare::Version; + +use crate::{ + build_system::{ + esp_idf::EspIdfBuildSystem, manual::ManualBuildSystem, pkgconfig::PkgConfigBuildSystem, + vendored::VendoredBuildSystem, BuildSystem, + }, + metadata::{DtlsBackend, LibcoapFeature, MINIMUM_LIBCOAP_VERSION}, +}; + +mod bindings; +mod build_system; +mod metadata; + +fn main() -> Result<()> { + println!("cargo:rerun-if-env-changed=LIBCOAP_RS_DTLS_BACKEND"); + println!("cargo:rerun-if-env-changed=LIBCOAP_RS_BUILD_SYSTEM"); + println!("cargo:rerun-if-env-changed=LIBCOAP_RS_BYPASS_COMPILE_FEATURE_CHECKS"); + // On ESP-IDF builds, this indicates whether the libcoap component has been enabled. + println!("cargo::rustc-check-cfg=cfg(esp_idf_comp_espressif__coap_enabled)"); + // Indicates the DTLS library crate that was linked against, if a library version vendored by + // another crate was used. + println!("cargo:rustc-check-cfg=cfg(used_dtls_crate, values(\"mbedtls\", \"tinydtls\", \"openssl\"))"); + // Indicates the DTLS backend used, if any. + println!("cargo:rustc-check-cfg=cfg(dtls_backend, values(\"mbedtls\", \"tinydtls\", \"openssl\", \"gnutls\", \"wolfssl\"))"); + // The detected libcoap version, if any. + println!("cargo::rustc-check-cfg=cfg(libcoap_version, values(any()))"); + + let out_dir = PathBuf::from( + env::var_os("OUT_DIR").expect("no OUT_DIR was provided (are we not running as a cargo build script?)"), + ); + + let target_os = env::var("CARGO_CFG_TARGET_OS").expect("unable to parse CARGO_CFG_TARGET_OS env variable"); + + let requested_features: EnumSet = EnumSet::::all() + .iter() + .filter(|feat| env::var_os(format!("CARGO_FEATURE_{}", feat.cargo_feature_var_suffix())).is_some()) + .collect(); + + let requested_dtls_backend: Option = match env::var("LIBCOAP_RS_DTLS_BACKEND") { + Ok(v) => v.parse().map(Some), + Err(VarError::NotPresent) => Ok(None), + Err(e) => Err(anyhow!(e)), + } + .context("unable to parse environment variable LIBCOAP_RS_DTLS_BACKEND")?; + + let chosen_build_system = match env::var("LIBCOAP_RS_BUILD_SYSTEM") { + Ok(v) => Ok(Some(v)), + Err(VarError::NotPresent) => Ok(None), + Err(e) => Err(e).context("unable to parse environment variable LIBCOAP_BUILD_SYSTEM"), + }?; + + let bypass_compile_time_feature_checks = match env::var("LIBCOAP_RS_BYPASS_COMPILE_FEATURE_CHECKS") { + Ok(v) if v == "0" => Ok(false), + Ok(_v) => Ok(true), + Err(VarError::NotPresent) => Ok(false), + Err(e) => Err(e).context("unable to parse environment variable LIBCOAP_RS_BYPASS_COMPILE_FEATURE_CHECKS"), + }?; + + let mut build_system: Box = match chosen_build_system.as_deref() { + Some(requested_build_system) => link_libcoap_explicit( + requested_build_system, + &target_os, + out_dir, + requested_features, + requested_dtls_backend, + bypass_compile_time_feature_checks, + ), + None => link_libcoap_auto( + &target_os, + out_dir, + requested_features, + requested_dtls_backend, + bypass_compile_time_feature_checks, + ), + }?; + + let bindings_file = build_system.generate_bindings()?; + println!( + "cargo:rustc-env=BINDINGS_FILE={}", + bindings_file.canonicalize()?.display() + ); + + if let Some(version) = build_system.version() { + if version < Version::from(MINIMUM_LIBCOAP_VERSION).unwrap() { + println!("cargo:warning=The linked version of libcoap is lower than the minimal version required for libcoap-sys ({}), this will most likely cause errors.", MINIMUM_LIBCOAP_VERSION); + } + println!("cargo::metadata=libcoap_version={}", version.as_str()); + println!("cargo::rustc-cfg=libcoap_version=\"{}\"", version.as_str()); + } else { + println!("cargo:warning=Unable to automatically detect the linked version of libcoap, please manually ensure that the used version is at least {} for libcoap-sys to work as expected.", MINIMUM_LIBCOAP_VERSION); + } + + if let Some(dtls_backend) = build_system.detected_dtls_backend() { + println!("cargo::metadata=dtls_backend={}", dtls_backend.as_str()); + println!("cargo::rustc-cfg=dtls_backend=\"{}\"", dtls_backend.as_str()); + + if !bypass_compile_time_feature_checks { + if let Some(req_backend) = requested_dtls_backend { + assert_eq!(req_backend, dtls_backend, + concat!( + "The libcoap-rs compile-time check has determined that the DTLS library\n", + "the used version of libcoap linked against ({}) does not match the one set in LIBCOAP_RS_DTLS_BACKEND ({}).\n", + "If you are certain that this check is mistaken (e.g., because you are cross-compiling), you\n", + "may bypass it by setting the `LIBCOAP_RS_BYPASS_COMPILE_FEATURE_CHECKS` environment\n", + "variable to any non-zero value.\n", + "Be aware, however, that this might lead to more cryptic errors if the requested features are\n", + "not available after all.\n", + "Refer to the libcoap-sys crate-level documentation for more information: https://docs.rs/libcoap-sys." + ), dtls_backend.as_str(), req_backend.as_str()) + } else if bypass_compile_time_feature_checks { + println!("cargo:warning=You have bypassed the libcoap-sys compile-time DTLS library check.") + } + } + } + + match build_system.detected_features() { + Some(detected_features) => { + let compile_time_checkable_features: EnumSet = requested_features + .iter() + .filter(|feat| feat.define_name().is_some()) + .collect(); + if !bypass_compile_time_feature_checks && !compile_time_checkable_features.is_subset(detected_features) { + let missing_features = requested_features.difference(detected_features); + bail!( + concat!( + "the libcoap-rs compile-time feature check has determined that the following enabled features\n", + "are not supported by the used C library: {}.\n", + "If you are certain that this check is mistaken (e.g., because you are cross-compiling), you\n", + "may bypass this check by setting the `LIBCOAP_RS_BYPASS_COMPILE_FEATURE_CHECKS` environment\n", + "variable to any non-zero value.\n", + "Be aware, however, that this might lead to more cryptic errors if the requested features are\n", + "not available after all.\n", + "Refer to the libcoap-sys crate-level documentation for more information: https://docs.rs/libcoap-sys." + ), + missing_features + .iter() + .map(|v| v.as_str()) + .collect::>() + .join(", ") + ); + } else if bypass_compile_time_feature_checks { + println!("cargo:warning=You have bypassed the libcoap-sys compile-time feature check.") + } + }, + None => { + println!("cargo:warning=The used build system for libcoap-sys does not support compile-time feature checks. Missing features may therefore only be detected during runtime."); + }, + } + + Ok(()) +} + +fn link_libcoap_explicit( + requested_build_system: &str, + target_os: &str, + out_dir: PathBuf, + requested_features: EnumSet, + requested_dtls_backend: Option, + bypass_compile_time_feature_checks: bool, +) -> Result> { + match requested_build_system { + "vendored" if target_os == "espidf" => { + EspIdfBuildSystem::new(out_dir, requested_features, requested_dtls_backend, bypass_compile_time_feature_checks).map(|v| Box::::from(Box::new(v))) + }, + "vendored" if cfg!(not(feature = "vendored")) => Err(anyhow!("LIBCOAP_RS_BUILD_SYSTEM has been set to \"vendored\", but the corresponding crate feature \"vendored\" has not been enabled.")), + "vendored" => VendoredBuildSystem::build_libcoap(out_dir, requested_features, requested_dtls_backend) + .map(|v| Box::::from(Box::new(v))), + "pkgconfig" if target_os != "espidf" => { + PkgConfigBuildSystem::link_with_libcoap(out_dir, requested_dtls_backend) + .map(|v| Box::::from(Box::new(v))) + }, + "manual" => ManualBuildSystem::link_with_libcoap(out_dir, requested_dtls_backend).map(|v| Box::::from(Box::new(v))), + v => Err(anyhow!("build system {v} is unknown or unsupported for this target")), + } + .context(format!( + "unable to link libcoap using force-configured build system {requested_build_system}" + )) +} + +fn vendored_libcoap_build( + target_os: &str, + out_dir: PathBuf, + requested_features: EnumSet, + requested_dtls_backend: Option, + bypass_compile_time_feature_checks: bool, +) -> Result> { + // TODO: Later on, we'll probably want to use the CMake based build system for any host+target + // combination that the libcoap build documentation recommends CMake for (most notably: + // Windows). + // See: https://github.com/obgm/libcoap/blob/develop/BUILDING + match target_os { + "espidf" => EspIdfBuildSystem::new( + out_dir, + requested_features, + requested_dtls_backend, + bypass_compile_time_feature_checks, + ) + .map(|v| Box::::from(Box::new(v))), + _ => VendoredBuildSystem::build_libcoap(out_dir, requested_features, requested_dtls_backend) + .map(|v| Box::::from(Box::new(v))), + } +} + +fn link_libcoap_auto( + target_os: &str, + out_dir: PathBuf, + requested_features: EnumSet, + requested_dtls_backend: Option, + bypass_compile_time_feature_checks: bool, +) -> Result> { + let mut errors = Vec::<(&'static str, anyhow::Error)>::new(); + // Try vendored build first if the feature is enabled and supported by the host. + // If the vendored build fails on a supported target, do not try anything else (we assume that + // the user wanted to use the vendored library for a reason). + if cfg!(feature = "vendored") || target_os == "espidf" { + return vendored_libcoap_build( + target_os, + out_dir, + requested_features, + requested_dtls_backend, + bypass_compile_time_feature_checks, + ); + } + PkgConfigBuildSystem::link_with_libcoap(out_dir.clone(), requested_dtls_backend) + .map(|v| Box::::from(Box::new(v))) + .or_else(|e| { + errors.push(("pkgconfig", e)); + ManualBuildSystem::link_with_libcoap(out_dir, requested_dtls_backend) + .map(|v| Box::::from(Box::new(v))) + }) + .map_err(|e| { + errors.push(("manual", e)); + anyhow!( + "unable to find a version of libcoap to link with:\n{}", + errors + .iter() + .map(|(k, v)| format!("Build system {k} failed with error: {v:?}")) + .collect::>() + .join("\n") + ) + }) +} diff --git a/libcoap-sys/build/metadata.rs b/libcoap-sys/build/metadata.rs new file mode 100644 index 00000000..b7e63bbf --- /dev/null +++ b/libcoap-sys/build/metadata.rs @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * build/metadata.rs - Type definitions for metadata in the libcoap-sys build script. + * This file is part of the libcoap-sys crate, see the README and LICENSE files for + * more information and terms of use. + * Copyright © 2021-2025 The NAMIB Project Developers, all rights reserved. + * See the README as well as the LICENSE file for more information. + */ + +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; + +use anyhow::anyhow; +use enumset::{EnumSet, EnumSetType}; + +/// Minimum required version of libcoap in order to build the bindgen-generated bindings. +/// +/// Note that this is *not* the minimum supported version of the safe wrapper, and should only be +/// increased if building on older versions causes issues with libcoap-sys specifically. +pub const MINIMUM_LIBCOAP_VERSION: &str = "4.3.5"; + +/// Information about a version of libcoap that was parsed by +/// [`LibcoapDefineParser`](crate::bindings::LibcoapDefineParser). +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct LibcoapDefineInfo { + pub version: Option, + pub dtls_backend: Option, + pub supported_features: EnumSet, +} + +/// An optional feature that may or may not be supported by a version of `libcoap`. +#[derive(EnumSetType, Debug)] +pub enum LibcoapFeature { + AfUnix, + Async, + Client, + SmallStack, + Tcp, + Epoll, + Ipv4, + Ipv6, + Oscore, + QBlock, + Server, + ThreadRecursiveLockDetection, + ThreadSafe, + Dtls, + ObservePersist, + WebSockets, + SecureWebSockets, + DtlsCid, + DtlsPsk, + DtlsPki, + DtlsPkcs11, + DtlsRpk, + Tls, +} + +impl LibcoapFeature { + /// Returns the name of the #define in `coap_defines.h` that corresponds to the given feature, + /// or None if there is no direct correspondence to such a #define. + pub fn define_name(&self) -> Option<&'static str> { + match self { + LibcoapFeature::AfUnix => Some("COAP_AF_UNIX_SUPPORT"), + LibcoapFeature::Async => Some("COAP_ASYNC_SUPPORT"), + LibcoapFeature::Client => Some("COAP_CLIENT_SUPPORT"), + LibcoapFeature::SmallStack => Some("COAP_CONSTRAINED_STACK"), + LibcoapFeature::Tcp => Some("COAP_DISABLE_TCP"), + LibcoapFeature::Epoll => Some("COAP_EPOLL_SUPPORT"), + LibcoapFeature::Ipv4 => Some("COAP_IPV4_SUPPORT"), + LibcoapFeature::Ipv6 => Some("COAP_IPV6_SUPPORT"), + LibcoapFeature::Oscore => Some("COAP_OSCORE_SUPPORT"), + LibcoapFeature::QBlock => Some("COAP_Q_BLOCK_SUPPORT"), + LibcoapFeature::Server => Some("COAP_SERVER_SUPPORT"), + LibcoapFeature::ThreadRecursiveLockDetection => Some("COAP_THREAD_RECURSIVE_CHECK"), + LibcoapFeature::ThreadSafe => Some("COAP_THREAD_SAFE"), + LibcoapFeature::ObservePersist => Some("COAP_WITH_OBSERVE_PERSIST"), + LibcoapFeature::WebSockets => Some("COAP_WS_SUPPORT"), + _ => None, + } + } + + /// Returns the set of features that are supposedly enabled if the #define with the name + /// `define_name` is set to `define_value` in `coap_defines.h`, or an empty set if the provided + /// define does not correspond to such a feature. + pub fn features_from_define(define_name: &str, define_value: i64) -> EnumSet { + // Only consider values != 0. + if define_name != "COAP_DISABLE_TCP" && define_value == 0 { + return EnumSet::empty(); + } + match define_name { + "COAP_AF_UNIX_SUPPORT" => EnumSet::from(LibcoapFeature::AfUnix), + "COAP_ASYNC_SUPPORT" => EnumSet::from(LibcoapFeature::Async), + "COAP_CLIENT_SUPPORT" => EnumSet::from(LibcoapFeature::Client), + "COAP_CONSTRAINED_STACK" => EnumSet::from(LibcoapFeature::SmallStack), + "COAP_EPOLL_SUPPORT" => EnumSet::from(LibcoapFeature::Epoll), + "COAP_IPV4_SUPPORT" => EnumSet::from(LibcoapFeature::Ipv4), + "COAP_IPV6_SUPPORT" => EnumSet::from(LibcoapFeature::Ipv6), + "COAP_OSCORE_SUPPORT" => EnumSet::from(LibcoapFeature::Oscore), + "COAP_Q_BLOCK_SUPPORT" => EnumSet::from(LibcoapFeature::QBlock), + "COAP_SERVER_SUPPORT" => EnumSet::from(LibcoapFeature::Server), + "COAP_THREAD_RECURSIVE_CHECK" => EnumSet::from(LibcoapFeature::ThreadRecursiveLockDetection), + "COAP_THREAD_SAFE" => EnumSet::from(LibcoapFeature::ThreadSafe), + "COAP_WITH_OBSERVE_PERSIST" => EnumSet::from(LibcoapFeature::ObservePersist), + "COAP_WS_SUPPORT" => EnumSet::from(LibcoapFeature::WebSockets), + "COAP_DISABLE_TCP" => { + if define_value == 0 { + EnumSet::from(LibcoapFeature::Tcp) + } else { + EnumSet::empty() + } + }, + "COAP_WITH_LIBGNUTLS" + | "COAP_WITH_LIBMBEDTLS" + | "COAP_WITH_LIBOPENSSL" + | "COAP_WITH_LIBTINYDTLS" + | "COAP_WITH_LIBWOLFSSL" => EnumSet::from(LibcoapFeature::Dtls), + _ => EnumSet::empty(), + } + } + + /// Return the configure argument name (--enable-) that can be provided to libcoap's + /// configure-script to enable the given feature (or None if no such flag is available). + pub fn configure_flag_name(&self) -> Option<&'static str> { + match self { + LibcoapFeature::AfUnix => Some("af-unix"), + LibcoapFeature::Async => Some("async"), + LibcoapFeature::Client => Some("client-mode"), + LibcoapFeature::SmallStack => Some("small-stack"), + LibcoapFeature::Tcp => Some("tcp"), + LibcoapFeature::Epoll => Some("epoll"), + LibcoapFeature::Ipv4 => Some("ipv4"), + LibcoapFeature::Ipv6 => Some("ipv6"), + LibcoapFeature::Oscore => Some("oscore"), + LibcoapFeature::QBlock => Some("q-block"), + LibcoapFeature::Server => Some("server-mode"), + LibcoapFeature::ThreadRecursiveLockDetection => Some("thread-recursive-lock-detection"), + LibcoapFeature::ThreadSafe => Some("thread-safe"), + LibcoapFeature::Dtls => Some("dtls"), + LibcoapFeature::ObservePersist => Some("observe-persist"), + LibcoapFeature::WebSockets => Some("websockets"), + _ => None, + } + } + + /// Return the ESP-IDF sdkconfig option name that must be set to enable this feature, or None + /// if no configuration option is available. + /// Reference: https://github.com/espressif/idf-extra-components/blob/master/coap/Kconfig + pub fn sdkconfig_flag_name(&self) -> Option<&'static str> { + match self { + LibcoapFeature::DtlsPsk => Some("COAP_MBEDTLS_PSK"), + LibcoapFeature::DtlsPki => Some("COAP_MBEDTLS_PKI"), + // Should be implied by mbedtls being enabled. + LibcoapFeature::DtlsCid => Some("COAP_MBEDTLS_PSK"), + LibcoapFeature::Tcp => Some("COAP_TCP_SUPPORT"), + LibcoapFeature::Oscore => Some("COAP_OSCORE_SUPPORT"), + LibcoapFeature::ObservePersist => Some("COAP_OBSERVE_PERSIST"), + LibcoapFeature::QBlock => Some("COAP_Q_BLOCK"), + LibcoapFeature::Async => Some("COAP_ASYNC_SUPPORT"), + LibcoapFeature::ThreadSafe => Some("COAP_THREAD_SAFE"), + LibcoapFeature::ThreadRecursiveLockDetection => Some("COAP_THREAD_RECURSIVE_CHECK"), + LibcoapFeature::WebSockets => Some("COAP_WEBSOCKETS"), + LibcoapFeature::Client => Some("COAP_CLIENT_SUPPORT"), + LibcoapFeature::Server => Some("COAP_SERVER_SUPPORT"), + _ => None, + } + } + + /// Returns the suffix of the CARGO_FEATURE_ environment variable that must be set + /// during build script execution if the feature has been enabled. + pub fn cargo_feature_var_suffix(&self) -> String { + self.as_str().to_uppercase().replace('-', "_") + } + + pub fn as_str(&self) -> &'static str { + match self { + LibcoapFeature::AfUnix => "af-unix", + LibcoapFeature::Async => "async", + LibcoapFeature::Client => "client", + LibcoapFeature::SmallStack => "small-stack", + LibcoapFeature::Tcp => "tcp", + LibcoapFeature::Epoll => "epoll", + LibcoapFeature::Ipv4 => "ipv4", + LibcoapFeature::Ipv6 => "ipv6", + LibcoapFeature::Oscore => "oscore", + LibcoapFeature::QBlock => "q-block", + LibcoapFeature::Server => "server", + LibcoapFeature::ThreadRecursiveLockDetection => "thread-recursive-lock-detection", + LibcoapFeature::ThreadSafe => "thread-safe", + LibcoapFeature::Dtls => "dtls", + LibcoapFeature::ObservePersist => "observe-persist", + LibcoapFeature::WebSockets => "websockets", + LibcoapFeature::SecureWebSockets => "secure-websockets", + LibcoapFeature::DtlsCid => "dtls-cid", + LibcoapFeature::DtlsPsk => "dtls-psk", + LibcoapFeature::DtlsPki => "dtls-pki", + LibcoapFeature::DtlsPkcs11 => "dtls-pkcs11", + LibcoapFeature::DtlsRpk => "dtls-rpk", + LibcoapFeature::Tls => "tls", + } + } +} + +/// A DLTS library that can be used by `libcoap` for encryption support. +#[derive(Debug, EnumSetType, Hash)] +pub enum DtlsBackend { + GnuTls, + OpenSsl, + MbedTls, + TinyDtls, + WolfSsl, +} + +impl FromStr for DtlsBackend { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "gnutls" => Ok(DtlsBackend::GnuTls), + "openssl" => Ok(DtlsBackend::OpenSsl), + "mbedtls" => Ok(DtlsBackend::MbedTls), + "tinydtls" => Ok(DtlsBackend::TinyDtls), + "wolfssl" => Ok(DtlsBackend::WolfSsl), + v => Err(anyhow!("unknown DTLS backend {v}")), + } + } +} + +impl Display for DtlsBackend { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl DtlsBackend { + /// Returns the suffix that has to be appended to libcoap-3- to find a library + /// version linked against the right DTLS library. + pub fn library_suffix(&self) -> &'static str { + // just keeping this here in case we ever need to change this definition to something + // different than self.as_str() for some libraries. + self.as_str() + } + + pub fn as_str(&self) -> &'static str { + match self { + DtlsBackend::GnuTls => "gnutls", + DtlsBackend::OpenSsl => "openssl", + DtlsBackend::MbedTls => "mbedtls", + DtlsBackend::TinyDtls => "tinydtls", + DtlsBackend::WolfSsl => "wolfssl", + } + } + + /// Returns the DTLS library that is supposedly enabled if the #define with the name + /// `define_name` is set to `define_value` in `coap_defines.h`, or None if the provided + /// define does not correspond to a DTLS library. + pub fn library_from_define(define_name: &str, define_value: i64) -> Option { + if define_value == 0 { + return None; + } + match define_name { + "COAP_WITH_LIBGNUTLS" => Some(DtlsBackend::GnuTls), + "COAP_WITH_LIBMBEDTLS" => Some(DtlsBackend::MbedTls), + "COAP_WITH_LIBOPENSSL" => Some(DtlsBackend::OpenSsl), + "COAP_WITH_LIBTINYDTLS" => Some(DtlsBackend::TinyDtls), + "COAP_WITH_LIBWOLFSSL" => Some(DtlsBackend::WolfSsl), + _ => None, + } + } +} diff --git a/libcoap-sys/examples/esp-idf/.cargo/config.toml b/libcoap-sys/examples/esp-idf/.cargo/config.toml new file mode 100644 index 00000000..11494290 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/.cargo/config.toml @@ -0,0 +1,16 @@ +[build] +target = "riscv32imc-esp-espidf" + +[target.riscv32imc-esp-espidf] +linker = "ldproxy" +runner = "espflash flash --monitor" +rustflags = [ "--cfg", "espidf_time64"] + +[unstable] +build-std = ["std", "panic_abort"] + +[env] +MCU="esp32c3" +# Note: this variable is not used by the pio builder (`cargo build --features pio`) +ESP_IDF_VERSION = "v5.2.3" + diff --git a/libcoap-sys/examples/esp-idf/.github/workflows/rust_ci.yml b/libcoap-sys/examples/esp-idf/.github/workflows/rust_ci.yml new file mode 100644 index 00000000..e7e9b1ec --- /dev/null +++ b/libcoap-sys/examples/esp-idf/.github/workflows/rust_ci.yml @@ -0,0 +1,41 @@ +name: Continuous Integration + +on: + push: + paths-ignore: + - "**/README.md" + pull_request: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + rust-checks: + name: Rust Checks + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + action: + - command: build + args: --release + - command: fmt + args: --all -- --check --color always + - command: clippy + args: --all-targets --all-features --workspace -- -D warnings + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Rust + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: nightly + components: rust-src rustfmt clippy + - name: Enable caching + uses: Swatinem/rust-cache@v2 + - name: Install ldproxy + run: cargo install ldproxy + - name: Run command + run: cargo ${{ matrix.action.command }} ${{ matrix.action.args }} diff --git a/libcoap-sys/examples/esp-idf/.gitignore b/libcoap-sys/examples/esp-idf/.gitignore new file mode 100644 index 00000000..aa1d4bf7 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/.gitignore @@ -0,0 +1,5 @@ +/.vscode +/.embuild +/target +/Cargo.lock +/vars.log \ No newline at end of file diff --git a/libcoap-sys/examples/esp-idf/Cargo.toml b/libcoap-sys/examples/esp-idf/Cargo.toml new file mode 100644 index 00000000..8f1b550c --- /dev/null +++ b/libcoap-sys/examples/esp-idf/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "libcoap-sys-esp-idf-example" +version = "0.1.0" +authors = ["Hugo Hakim Damer "] +edition = "2021" +resolver = "2" +rust-version = "1.77" + +[[bin]] +name = "libcoap-sys-esp-idf-example" +harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors + +[profile.release] +opt-level = "s" + +[profile.dev] +debug = true # Symbols are nice and they don't increase the size on Flash +opt-level = "z" + +[features] +default = [] + +experimental = ["esp-idf-svc/experimental"] + +[dependencies] +log = "0.4" +esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync"] } +esp-idf-sys = { version = "0.36.1" } +libcoap-sys = { version = "*", path = "../../" } + +[build-dependencies] +embuild = "0.33" + +[workspace] diff --git a/libcoap-sys/examples/esp-idf/README.md b/libcoap-sys/examples/esp-idf/README.md new file mode 100644 index 00000000..25f49b59 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/README.md @@ -0,0 +1,6 @@ +This folder contains a slightly modified version of the ESP-IDF Rust project template that can be found +[here](https://github.com/esp-rs/esp-idf-template/tree/master/cargo), with slight modifications to +`sdkconfig.defaults`, `Cargo.toml` and `main.rs` to add support for `libcoap-sys`. + +It is mainly used to test the compilation and binding generation process for ESP-IDF builds of `libcoap-sys`, +but you may also use it as a reference for your own projects. \ No newline at end of file diff --git a/libcoap-sys/examples/esp-idf/build.rs b/libcoap-sys/examples/esp-idf/build.rs new file mode 100644 index 00000000..112ec3f7 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/build.rs @@ -0,0 +1,3 @@ +fn main() { + embuild::espidf::sysenv::output(); +} diff --git a/libcoap-sys/examples/esp-idf/components_esp32c3.lock b/libcoap-sys/examples/esp-idf/components_esp32c3.lock new file mode 100644 index 00000000..b6391610 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/components_esp32c3.lock @@ -0,0 +1,20 @@ +dependencies: + espressif/coap: + component_hash: a5d1b781b15980d9af136b5e63315dd5f5ec00215cc755f395ad2fb4977c7668 + dependencies: + - name: idf + require: private + version: '>=4.4' + source: + registry_url: https://components.espressif.com/ + type: service + version: 4.3.5~3 + idf: + source: + type: idf + version: 5.2.3 +direct_dependencies: +- espressif/coap +manifest_hash: 13fcf89b865b0f073a765a0dfdcf062a0063b4af7a631d3d37ef94c920538a25 +target: esp32c3 +version: 2.0.0 diff --git a/libcoap-sys/examples/esp-idf/rust-toolchain.toml b/libcoap-sys/examples/esp-idf/rust-toolchain.toml new file mode 100644 index 00000000..f70d2254 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rust-src"] diff --git a/libcoap-sys/examples/esp-idf/sdkconfig.defaults b/libcoap-sys/examples/esp-idf/sdkconfig.defaults new file mode 100644 index 00000000..1c2bfab4 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/sdkconfig.defaults @@ -0,0 +1,21 @@ +# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 + +# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). +# This allows to use 1 ms granularity for thread sleeps (10 ms by default). +#CONFIG_FREERTOS_HZ=1000 + +# Workaround for https://github.com/espressif/esp-idf/issues/7631 +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n + +CONFIG_COAP_CLIENT_SUPPORT=y +CONFIG_COAP_SERVER_SUPPORT=y +CONFIG_COAP_TCP_SUPPORT=y +CONFIG_COAP_OSCORE_SUPPORT=y +CONFIG_COAP_Q_BLOCK=y +CONFIG_COAP_SERVER_SUPPORT=y +CONFIG_COAP_THREAD_RECURSIVE_CHECK=y +CONFIG_COAP_THREAD_SAFE=y +CONFIG_COAP_OBSERVE_PERSIST=y +CONFIG_COAP_WEBSOCKETS=y diff --git a/libcoap-sys/examples/esp-idf/src/main.rs b/libcoap-sys/examples/esp-idf/src/main.rs new file mode 100644 index 00000000..42fa7063 --- /dev/null +++ b/libcoap-sys/examples/esp-idf/src/main.rs @@ -0,0 +1,12 @@ +use libcoap_sys as _; + +fn main() { + // It is necessary to call this function once. Otherwise some patches to the runtime + // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 + esp_idf_svc::sys::link_patches(); + + // Bind the log crate to the ESP Logging facilities + esp_idf_svc::log::EspLogger::initialize_default(); + + log::info!("Hello, world!"); +} diff --git a/libcoap-sys/examples/esp-idf/vars.log b/libcoap-sys/examples/esp-idf/vars.log new file mode 100644 index 00000000..e69de29b diff --git a/libcoap-sys/src/lib.rs b/libcoap-sys/src/lib.rs index 1444fdf8..1add89fc 100644 --- a/libcoap-sys/src/lib.rs +++ b/libcoap-sys/src/lib.rs @@ -14,107 +14,334 @@ //! made in this library are generated automatically using bindgen, for further documentation on how //! to use them, refer to the [libcoap documentation](https://libcoap.net/documentation.html). //! -//! In most cases you probably want to use the safe wrapper provided by the libcoap crate (or -//! another coap library written in pure rust such as [coap-rs](https://github.com/covertness/coap-rs)) +//! In most cases you probably want to use the safe wrapper provided by the libcoap-rs crate or +//! another coap library written in pure Rust such as [coap-rs](https://github.com/covertness/coap-rs) //! instead. //! -//! Cargo Features +//! The TLDR for building libcoap-sys (and resolving the most common Build Issues) +//! ------------------------------------------------------------------------------ +//! It is strongly recommended that you read the remainder of this page in order to fully understand +//! the build process and possible causes of errors, especially if you're cross-compiling or +//! building for embedded targets. +//! +//! However, if you lack the time to do so, the following instructions should work in most cases: +//! +//! 1. Add a dependency to this crate and add all features you need for your crate to work. +//! Call [`coap_startup_with_feature_checks()`] instead of [`coap_startup()`] during +//! initialization to ensure that all of these features are actually available in the linked +//! version of `libcoap`. +//! +//! 2. If you require DTLS support and run into `Required feature "dtls-(psk|pki|rpk|...)" is not +//! supported by libcoap` errors, manually select a DTLS library that supports all of your +//! required DTLS features by setting the `LIBCOAP_RS_DTLS_BACKEND` environment variable to your +//! desired choice (the library name in all-lowercase should work). +//! +//! 3. If you're building a binary crate (or tests, examples, ...) and are getting non-DTLS-related +//! `Required feature "" is not supported by libcoap` errors, enable the `vendored` +//! feature to build and statically link a version of libcoap that supports exactly the features +//! you requested. +//! +//! 4. Inspect your dependency tree to determine whether you already have a DTLS library's sys-crate +//! (`openssl-sys`, `tinydtls-sys` or `mbedtls-sys-auto`) in your dependency tree. +//! If this is the case, enable the `dtls--sys` feature for all of them. +//! This may resolve issues related to linking multiple versions of the same library at once, and +//! could also help in reducing binary size. +//! +//! If you're still unable to compile `libcoap-sys`, refer to the documentation below. +//! If the documentation below does not solve your issue, feel free to open an issue +//! [on GitHub](https://github.com/namib-project/libcoap-rs/) and ask for help. +//! +//! Optional Features //! -------------- -//! We currently define a number of features that affect the functionality provided by this wrapper -//! and required by the linked libcoap library. -//! -//! Features affecting functionality: -//! - `dtls`: Enable usage of DTLS for transport security. Supports a number of different backends. -//! -//! Note that while not specified here due to limitations in Cargo's syntax, the DTLS feature -//! depends on one of the DTLS backends being enabled, and failing to enable a DTLS backend will -//! result in a build failure. -//! -//! If you are developing a library based on libcoap-sys and do not care about the DTLS backend, -//! enable the dtls feature and let the user decide on the backend to use, either by -//! re-exporting these features (see [the Cargo Book](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features)) -//! or by assuming that the user will use libcoap-sys as a dependency and enable the -//! corresponding backend feature themselves, relying on Cargo's feature unification to enable -//! it for your crate as well. -//! -//! Also note that the backends are **mutually exclusive** due to the C library having these -//! backends as mutually exclusive features. If multiple backends are enabled (e.g. because -//! multiple dependencies use libcoap-sys and use different backends), we select one based on -//! the auto-detection order specified in [the libcoap configure script](https://github.com/obgm/libcoap/blob/develop/configure.ac#L494) -//! (gnutls > openssl > mbedtls > tinydtls). -//! - `dtls_backend_(openssl|gnutls|mbedtls|tinydtls)`: Enable the corresponding DTLS backend. -//! -//! Note that enabling the OpenSSL, GnuTLS, TinyDTLS or MbedTLS backend will also require the -//! appropriate library to be available (hence the dependency on the corresponding sys-crate). -//! The TinyDTLS backend is built using a vendored (and statically linked) version of TinyDTLS -//! by default, see the tinydtls-sys crate for more info. -//! Choosing a DTLS backend also means that the license terms of these libraries may apply to -//! you. See the relevant parts of the [libcoap license file](https://github.com/obgm/libcoap/blob/develop/LICENSE) -//! for more information. -//! - `tcp` (default): Enable CoAP over TCP support -//! - `async` (default): Enable async functionality. -//! -//! Note that this async functionality is not translated to Rust's async language functionality, -//! but instead adds functionality to the underlying C library to allow for making asynchronous -//! requests (i.e. function calls that return before the response has arrived). -//! -//! Integrating libcoap into Rusts async language features is out of scope for this crate, but -//! might be implemented later on in the libcoap (safe abstraction) crate. -//! - `server` (default): Enable code related to server functionality -//! - `client` (default): Enable code related to client functionality -//! - `epoll` (default): Allow the underlying C library to perform IO operations using epoll. -//! -//! Other features: -//! - `vendored` (default): Use a vendored version of libcoap instead of the system-provided one. -//! Note that `vendored` implies `static`. -//! - `static` (default): Perform static linking to the libcoap C library. -//! -//! ### Note on features affecting functionality -//! The features that add or remove functionality do not change the generated bindings as libcoap's -//! headers (unlike the source files themselves) are not affected by the corresponding `#define`s. -//! -//! For library users that link to a shared version of libcoap, this means that the feature flags -//! do not have any effect and the supported features will correspond directly to the features -//! enabled during the build of the shared libcoap instance (using the configure-script). -//! -//! For users of the vendored version of libcoap (see the `vendored` feature), the supported -//! features of the vendored libcoap will be set to match the cargo features during build. +//! Most features specified in this crate's Cargo.toml directly correspond to a feature that can be +//! enabled or disabled in libcoap's configure-script and/or CMake configuration, refer to the +//! libcoap documentation for more details on these features. +//! +//! The `default` feature should match the default features enabled in the configure script of the +//! minimum supported version of libcoap. +//! +//! Depending on the build system and linked version of libcoap, the features actually provided may +//! differ from the ones indicated by the crate features. +//! If you want to ensure that all features that are enabled for this crate are actually supported +//! by the linked version of libcoap, you may call [`coap_startup_with_feature_checks()`]. +//! +//! Aside from the features relating to libcoap functionality, the following features may also be +//! enabled for this crate: +//! - `vendored`: Build and statically link against a version of libcoap bundled with this crate +//! instead of using a system-provided one[^1]. +//! - `dtls--sys`: Allows the [vendored](#vendored-build-system) libcoap version to link against the +//! same version of a DTLS library that is used by the corresponding -sys +//! crate[^2]. +//! Note, however, that this does not imply that this DTLS library *will* be used, refer to +//! the [documentation below](#dtls-library-selection) for more information. +//! +//! If a different build system than `vendored` is used, this feature is effectively a no-op. +//! - `dtls--sys-vendored` instructs the sys-crate of the DTLS library corresponding +//! to the feature name to use a vendored version of the underlying library (implies +//! `dtls--sys`). +//! - `dtls-(cid|psk|pki|pkcs11|rpk)`: Require support for specific DTLS features in `libcoap`. +//! These features can not be enabled explicitly while building `libcoap`, support for them is +//! automatically made available based on the used DTLS library (see +//! [the corresponding section below](#dtls-library-selection)). +//! +//! Enabling these features will add appropriate checks during compile- and/or runtime +//! initialization to ensure these features are available in the used DTLS library (or panic +//! otherwise). +//! +//! [^1]: Note that when building for the ESP-IDF, this feature will be a no-op, as the version +//! provided by the ESP-IDF will always be used. +//! [^2]: In the case of `mbedtls`, `mbedtls-sys-auto` is used instead, as `mbedtls-sys` is +//! unmaintained. +//! +//! Build Process +//! ------------- +//! In general, `libcoap-sys` supports four different build systems, which will be explained in more +//! detail in the following sections: +//! - `vendored`: Build libcoap from source using a bundled version of the library (requires the +//! `vendored` feature to be enabled. +//! - `pkgconfig`: Link against a system-provided version of libcoap, obtaining the library and +//! include paths using the `pkg-config` utility. +//! - `manual`: Provide include and library directories+compiler/linker flags via environment +//! variables. +//! - `espidf`: Build for the ESP family of microcontrollers using the ESP-IDF framework (used +//! instead of the regular `vendored` build if the build system is set to `vendored` and +//! building for an ESP-IDF Rust target). +//! +//! The build system that should be used can be specified manually by setting the +//! `LIBCOAP_RS_BUILD_SYSTEM` environment variable to the corresponding value. +//! +//! If you have explicitly specified a build system and building using that system fails, no other +//! system will be tried. +//! +//! If you do not explicitly provide a build system to use, the build script will follow these +//! steps to determine a suitable build system: +//! +//! 1. If the `vendored` crate feature is enabled, or we are building for the ESP-IDF, act as if the +//! build system is set to `vendored`. If a vendored build is attempted and fails, return with an +//! error and do not try anything else. +//! 2. Otherwise, try `pkgconfig` first. +//! 3. If `pkgconfig` doesn't work, fall back to `manual` (which will fail if the environment +//! variables aren't set). +//! 4. If `manual` doesn't work, return an error indicating the issues with all previously attempted +//! build systems. +//! +//! ## Generic Information (applies to all build systems) +//! The following information applies to all build systems (although some specifics may be detailed +//! in the respective build system's section). +//! +//! ### C Standard Library Functions +//! Some `libcoap` functions utilize types from the C standard library (e.g., `sockaddr_in` in +//! `coap_address_t`). +//! +//! For most targets, the data types defined in the [`libc`](https://docs.rs/libc/latest/libc/) +//! crate will be used to provide those data types. +//! However, some targets (especially embedded ones such as `espidf`) will use a different library +//! instead, which may cause compilation issues in code that assumes `libc` data types to be +//! compatible. +//! +//! For your convenience, this crate re-exports the used standard library crate as the `c_stdlib` +//! module. For best interoperability, you should `use` this module instead of using the actual +//! crates directly to import the required data types. +//! +//! ### DTLS Library Selection +//! +//! In order to provide DTLS support, `libcoap` must be combined with a DTLS library/backend. +//! DTLS libraries are mutually exclusive, and multiple versions of `libcoap` linked against +//! different DTLS libraries may be installed in a system simultaneously, so `libcoap-sys` must +//! decide on a variant of `libcoap` to link against during build. +//! +//! While the default mechanism for determining a DTLS library differs between build systems, you +//! may select a DTLS library explicitly by setting the `LIBCOAP_RS_DTLS_BACKEND` environment +//! variable to any of the supported values (`gnutls`, `openssl`, `mbedtls`, `tinydtls`, or +//! `wolfssl`). Refer to the build-system-specific documentation for information about supported +//! DTLS libraries and specifics. +//! +//! Note that some DTLS-related features (such as `dtls-(cid|psk|pki|pkcs11|rpk)`) are dependent on +//! the used DTLS backend, refer to [the `coap_encryption(3)` man page](https://libcoap.net/doc/reference/4.3.5/man_coap_encryption.html) +//! for information on supported features for each DTLS library. +//! +//! ### Feature Support and Compile-Time/Initialization Checks +//! During compilation, each build system will attempt to ensure that the used version of `libcoap` +//! does in fact support all [features](#optional-features) that were enabled in `Cargo.toml`. +//! The exact method differs based on each build system, but most will attempt to parse the +//! `coap3/coap_defines.h` header file in order to determine missing features (with `espidf` being a +//! notable exception). +//! +//! If a build system detects that a requested feature is missing, an appropriate error message will +//! be returned. In most cases, these errors must be resolved by linking to a different version of +//! `libcoap`. +//! +//! Unfortunately, for various reasons, this compile-time feature check may produce false +//! positive and/or false negative results (especially when cross compiling[^3]) +//! is not available for all features (especially ones dependent on the DTLS library) and may not +//! even be available at all on some platforms. +//! +//! Therefore, library users should assume that the compile-time checks may not provide accurate +//! results, and should call [`coap_startup_with_feature_checks()`] during initialization to perform +//! run-time checks for all requested features. This run-time check will always work and be +//! accurate. +//! +//! Lastly, if you encounter a false positive error (i.e., a compile time error that indicates that +//! some feature is missing, even though you are 100% certain that it is available), you may bypass +//! the compile-time checks by setting `LIBCOAP_RS_BYPASS_COMPILE_FEATURE_CHECKS` to any non-zero +//! value. +//! Note, however, that this might lead to cryptic errors if your assumption was wrong and the +//! feature is not available after all. +//! +//! In most cases, a false positive might be caused by the include paths/header files used for +//! binding generation refering to a different version of `libcoap` than the one that is linked +//! against, which could also cause difficult-to-debug issues and indicates a more severe problem +//! with the build process. +//! +//! [^3]: For this reason, using this method [is noted to be unsafe in `libcoap`'s documentation](https://libcoap.net/doc/reference/4.3.5/man_coap_supported.html). +//! +//! ## Vendored Build System +//! +//! The vendored build system uses a bundled version of libcoap (usually the latest stable version +//! at the time of release) to build and statically link against. +//! Under the hood, it uses the [`autotools`](https://docs.rs/autotools/latest/autotools/) crate to +//! configure and run the build process, and you may therefore customize the build's compiler and +//! linker flags by setting the environment variables used in `libcoap`'s `configure` script. +//! +//! This build system will enable only those features in `libcoap` that are requested as +//! `Cargo.toml` features, and will explicitly disable all other ones. +//! +//! If a DTLS library is explicitly selected by the user, it will instruct libcoap to link against +//! that library by setting the corresponding `--with-` configure flag. +//! +//! If you enable the `dtls--sys` features and do not set the `_CFLAGS` +//! or `_LIBS` environment variables, this build system will set these environment +//! variables to ensure that if this DTLS library is the one that libcoap uses, we link against +//! exactly the same version as used in the `-sys` crate. +//! This is especially relevant if those crates also provide a `vendored` version in order to avoid +//! multiple versions of the same library being in use. +//! If a different DTLS library is used, this feature should have no effect (it will set the +//! environment variable, but `libcoap` will ignore it). +//! +//! If you do not specify a DTLS library, this build system will follow the same default order that +//! libcoap does (gnutls > openssl > wolfssl > mbedtls > tinydtls), unless you enabled one of the +//! `dtls--sys` features, in which case those will have priority. +//! If multiple of these features are enabled, they are prioritized in the same order as used by +//! `libcoap` (openssl > mbedtls > tinydtls). +//! +//! ## `pkg-config` Build System +//! +//! The `pkg-config` build system utilizes the `pkg-config` utility available on most Unix-like +//! systems to link against a system-provided version of `libcoap`. +//! To do so, it uses the [`pkg_config`](https://docs.rs/pkg-config/latest/pkg_config/) crate, and +//! you may therefore customize the build process by setting the environment variables described in +//! that library's documentation (which may be of special relevance if you try to cross compile). +//! +//! By default, it will probe `pkg-config` for a library with the name `libcoap-3`, which will +//! usually symlink to the DTLS library variant of libcoap that was installed most recently. +//! If you have explicitly requested use of a specific DTLS library, this build system will attempt +//! to find the `libcoap-3-` library instead. +//! +//! However, library selection does not take into account any other requested features (i.e., it +//! will not check for feature support before generating the bindings), but will use header-based +//! compile-time feature checks (see the general section) to ensure support for all required +//! features after binding generation. +//! +//! The `dtls--sys` features have no effect on this build system, but note that static +//! linking against a system-provided version of `libcoap` may cause issues if it causes multiple +//! versions of the same DTLS library to be statically linked into the same Rust binary. +//! +//! ## `manual` Build System +//! +//! This build system is intended as a fallback solution if all other options fail. It will attempt +//! to generate bindings and link against the version of `libcoap` that is described by the +//! following environment variables: +//! +//! - `LIBCOAP_RS_INCLUDE_DIRS`: Paths that should be added to `clang`'s include path to search for +//! header files (e.g., `/usr/local/include`, _not_ +//! `/usr/local/include/coap3`). Multiple values are separated by +//! colons (`:`). +//! - `LIBCOAP_RS_LIB_DIRS`: Paths that should be added to `rustc`'s library path to search for +//! object files to link against. Multiple values are separated by +//! colons (`:`). +//! - `LIBCOAP_RS_STATIC`: Set to any non-zero and non-empty value to instruct `rustc` to use +//! static linking instead of dynamic linking for `libcoap`. +//! - `LIBCOAP_RS_ADDITIONAL_LIBRARIES`: Additional libraries (such as DTLS libraries) that should +//! be linked against, separated by colons (`:`). +//! Note that these will be added after `libcoap`, and that the order +//! in which they are specified matters for most linkers. +//! You may also request static linking by prepending `static=` to the +//! library name. +//! +//! ## `espidf` Build System +//! +//! This build system will be used instead of the regular `vendored` build if you are building for +//! targets that are based on the `ESP-IDF`. +//! +//! If `libcoap-sys` is a direct dependency, it will automatically enable the +//! [`espressif/coap`](https://components.espressif.com/components/espressif/coap) component in +//! order to instruct `esp-idf-sys` to compile and link `libcoap` and generate bindings for it. +//! +//! If you encounter errors that indicate that the `espressif/coap` component may not be enabled in +//! the ESP-IDF, this could have the following reasons: +//! - You may have to run `cargo clean`, as the `esp-idf-sys` build script does not always detect +//! changes in requested extra components properly. +//! - `libcoap-sys` is a transient dependency: the `esp-idf-sys` build script +//! [only considers metadata from the root crate and its direct dependencies](https://docs.esp-rs.org/esp-idf-sys/esp_idf_sys/#extra-esp-idf-components) +//! to determine which components to install. +//! In order to solve this, you can either add `libcoap-sys` as a direct dependency, or copy +//! this crate's `src/wrapper.h` file and add the following snippet to your own `Cargo.toml`. +//! ```cargo +//! [[package.metadata.esp-idf-sys.extra_components]] +//! remote_component = { name = "espressif/coap", version = "4.3.5~3" } +//! bindings_header = "src/wrapper.h" +//! ``` +//! Afterward, run `cargo clean` (see the issue mentioned above) and try again. +//! +//! It will then parse the generated bindings file and re-export all symbols in `esp-idf-sys` that +//! are related to `libcoap`. +//! +//! Note that `esp-idf-sys` may use a different version of `bindgen` than the other build systems +//! and that bindings might differ slightly as a result. +//! +//! Unlike most other targets, this one will use `esp-idf-sys` instead of `libc` to provide its +//! standard library types (see the generic information section above). // Bindgen translates the C headers, clippy's and rustfmt's recommendations are not applicable here. #![allow(clippy::all)] #![allow(non_camel_case_types)] #![allow(deref_nullptr)] #![allow(non_snake_case)] +#![allow(non_upper_case_globals)] -use std::ffi::c_void; +use core::ffi::c_void; -#[allow(unused_imports)] -use libc::{fd_set, memcmp, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, socklen_t, time_t}; -#[allow(unused_imports)] +use c_stdlib::{epoll_event, fd_set, memcmp, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, socklen_t, time_t}; +/// Re-export of the crate that provides libc data types used by libcoap. +/// +/// In most cases, this will be libc, but on the ESP-IDF, it will be esp_idf_sys. +#[cfg(target_os = "espidf")] +pub use esp_idf_sys as c_stdlib; +/// Re-export of the crate that provides libc data types used by libcoap. +/// +/// In most cases, this will be libc, but on the ESP-IDF, it will be esp_idf_sys. #[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. +pub use libc as c_stdlib; +// use dtls backend libraries in cases where they set our linker flags, otherwise rustc will +// optimize them out, resulting in missing symbols. #[allow(unused_imports)] -#[cfg(feature = "dtls_backend_mbedtls_vendored")] +#[cfg(used_dtls_crate = "mbedtls")] use mbedtls_sys as _; #[allow(unused_imports)] -#[cfg(feature = "dtls_backend_openssl")] +#[cfg(used_dtls_crate = "openssl")] use openssl_sys as _; #[allow(unused_imports)] -#[cfg(feature = "dtls_backend_tinydtls")] +#[cfg(used_dtls_crate = "tinydtls")] use tinydtls_sys as _; -#[cfg(target_family = "windows")] -include!(concat!(env!("OUT_DIR"), "\\bindings.rs")); -#[cfg(not(target_family = "windows"))] -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +// Add check whether the libcoap component is enabled when building for the ESP-IDF. +#[cfg(all(target_os = "espidf", not(esp_idf_comp_espressif__coap_enabled)))] +compile_error!(concat!( + "You are building libcoap-sys for an ESP-IDF target, but have not added the\n", + "espressif/coap remote component (see the libcoap-sys documentation for more information)" +)); -#[inline] -#[cfg(not(non_inlined_coap_send_rst))] -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) -} +include!(env!("BINDINGS_FILE")); /// Compares instances of coap_str_const_t and/or coap_string_t. /// @@ -148,145 +375,130 @@ pub unsafe fn coap_string_equal_internal( && (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) + && memcmp(str1_ptr as *const c_void, str2_ptr as *const c_void, str1_len as _) == 0) +} + +/// Execute feature check function and panic with an error message if the feature is not supported. +/// +/// SAFETY: check_fn will be called, make sure to wrap this macro in unsafe if the function is +/// unsafe. +#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))] +macro_rules! feature_check { + ($feature:literal, $check_fn:ident) => { + #[cfg(feature = $feature)] + // SAFETY: Function is always safe to call. + if $check_fn() != 1 { + panic!("Required feature \"{}\" is not supported by libcoap", $feature) + } + }; + ( $(($feature:literal, $check_fn:ident)),* ) => { + $( + feature_check!($feature, $check_fn); + )* + } +} + +#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))] +macro_rules! dtls_backend_string { + ($backend:ident) => { + match $backend { + coap_tls_library_t_COAP_TLS_LIBRARY_OPENSSL => "openssl", + coap_tls_library_t_COAP_TLS_LIBRARY_GNUTLS => "gnutls", + coap_tls_library_t_COAP_TLS_LIBRARY_MBEDTLS => "mbedtls", + coap_tls_library_t_COAP_TLS_LIBRARY_TINYDTLS => "tinydtls", + coap_tls_library_t_COAP_TLS_LIBRARY_WOLFSSL => "wolfssl", + coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS => "notls", + _ => "unknown", + } + }; +} + +#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))] +macro_rules! panic_wrong_dtls { + ($presumed_backend:ident, $detected_backend:ident) => { + panic!( + concat!( + "compile-time detected DTLS backend \"{}\" does not match run-time detected DTLS backend \"{}\".\n", + "This almost certainly means that libcoap-sys was linked against a different version of \n", + "libcoap than the one whose headers were used for binding generation." + ), + dtls_backend_string!($presumed_backend), + dtls_backend_string!($detected_backend) + ) + }; } /// Initialize the CoAP library and additionally perform runtime checks to ensure that required -/// features (as enabled in `Cargo.toml`) are available. +/// features (as enabled in `Cargo.toml`) are available and that the used DTLS library matches the +/// one that was determined during compile-time. /// -/// You *should* prefer using this function over [coap_startup], as without calling this function +/// You *should* prefer using this function over [`coap_startup()`], as without calling this function /// some of the features enabled using the Cargo features may not actually be available. -/// (NOTE: However, see the below section on safety). /// -/// Either this function or [coap_startup] must be run once before any libcoap function is called. +/// Either this function or [`coap_startup()`] must be run once before any libcoap function is called. /// /// If you are absolutely 100% certain that all features you require are always available (or are /// prepared to deal with error return values/different behavior on your own if they aren't), you -/// may use [coap_startup] instead. -/// -/// # SAFETY WARNING -/// In order to preserve backwards compatibility, this method may (for now) skip the feature checks -/// altogether if they aren't provided by libcoap (which may be the case for libcoap < 4.3.5rc3). -/// -/// If you want to be absolutely certain that a given feature is available, you *must* also check -/// the libcoap version to ensure that it is at least 4.3.5rc3. -/// -/// This behavior will be changed as soon as libcoap 4.3.5 is released, after which libcoap 4.3.5 -/// will become the minimum supported version and these checks will be mandatory. +/// may use [`coap_startup()`] instead. +// Make sure that if we're compiling for the ESP-IDF, this function is only compiled if the +// libcoap component is installed in the ESP-IDF. +// This way, these function calls will not cause missing function or struct definition errors that +// clutter up the error log, and the only error message will be the way more descriptive +// compile_error macro invocation at the start of this file. +#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))] pub fn coap_startup_with_feature_checks() { - // only compile checks if they are available for the given libcoap version. - #[cfg(feature_checks_available)] - { - #[cfg(feature = "af-unix")] - // SAFETY: Function is always safe to call. - if unsafe { coap_af_unix_is_supported() != 1 } { - panic!("Required feature \"af-unix\" is not supported by libcoap") - } - #[cfg(feature = "async")] - // SAFETY: Function is always safe to call. - if unsafe { coap_async_is_supported() != 1 } { - panic!("Required feature \"async\" is not supported by libcoap") - } - #[cfg(feature = "client")] - // SAFETY: Function is always safe to call. - if unsafe { coap_client_is_supported() != 1 } { - panic!("Required feature \"ipv4\" is not supported by libcoap") - } - #[cfg(feature = "dtls")] - // SAFETY: Function is always safe to call. - if unsafe { coap_dtls_is_supported() != 1 } { - panic!("Required feature \"dtls\" is not supported by libcoap") - } - #[cfg(feature = "dtls-cid")] - // SAFETY: Function is always safe to call. - if unsafe { coap_dtls_cid_is_supported() != 1 } { - panic!("Required feature \"dtls-cid\" is not supported by libcoap") - } - #[cfg(feature = "dtls-psk")] - // SAFETY: Function is always safe to call. - if unsafe { coap_dtls_psk_is_supported() != 1 } { - panic!("Required feature \"dtls-psk\" is not supported by libcoap") - } - #[cfg(feature = "dtls-pki")] - // SAFETY: Function is always safe to call. - if unsafe { coap_dtls_pki_is_supported() != 1 } { - panic!("Required feature \"dtls-pki\" is not supported by libcoap") - } - #[cfg(feature = "dtls-pkcs11")] - // SAFETY: Function is always safe to call. - if !unsafe { coap_dtls_pkcs11_is_supported() == 1 } { - panic!("Required feature \"dtls-pkcs11\" is not supported by libcoap") - } - #[cfg(feature = "dtls-rpk")] - // SAFETY: Function is always safe to call. - if unsafe { coap_dtls_rpk_is_supported() != 1 } { - panic!("Required feature \"dtls-rpk\" is not supported by libcoap") - } - #[cfg(feature = "epoll")] - // SAFETY: Function is always safe to call. - if unsafe { coap_epoll_is_supported() != 1 } { - panic!("Required feature \"epoll\" is not supported by libcoap") - } - #[cfg(feature = "ipv4")] - // SAFETY: Function is always safe to call. - if !unsafe { coap_ipv4_is_supported() == 1 } { - panic!("Required feature \"ipv4\" is not supported by libcoap") - } - #[cfg(feature = "ipv6")] - // SAFETY: Function is always safe to call. - if !unsafe { coap_ipv6_is_supported() == 1 } { - panic!("Required feature \"ipv6\" is not supported by libcoap") - } - #[cfg(feature = "observe-persist")] - // SAFETY: Function is always safe to call. - if unsafe { coap_observe_persist_is_supported() != 1 } { - panic!("Required feature \"ipv4\" is not supported by libcoap") - } - #[cfg(feature = "oscore")] - // SAFETY: Function is always safe to call. - if unsafe { coap_oscore_is_supported() != 1 } { - panic!("Required feature \"oscore\" is not supported by libcoap") - } - #[cfg(feature = "q-block")] - // SAFETY: Function is always safe to call. - if unsafe { coap_q_block_is_supported() != 1 } { - panic!("Required feature \"q-block\" is not supported by libcoap") - } - #[cfg(feature = "server")] - // SAFETY: Function is always safe to call. - if unsafe { coap_server_is_supported() != 1 } { - panic!("Required feature \"ipv4\" is not supported by libcoap") - } - #[cfg(feature = "tcp")] - // SAFETY: Function is always safe to call. - if unsafe { coap_tcp_is_supported() != 1 } { - panic!("Required feature \"tcp\" is not supported by libcoap") - } - #[cfg(feature = "thread-safe")] - // SAFETY: Function is always safe to call. - if unsafe { coap_threadsafe_is_supported() != 1 } { - panic!("Required feature \"thread-safe\" is not supported by libcoap") - } - #[cfg(feature = "tls")] - // SAFETY: Function is always safe to call. - if unsafe { coap_tls_is_supported() != 1 } { - panic!("Required feature \"tls\" is not supported by libcoap") - } - #[cfg(feature = "websockets")] - // SAFETY: Function is always safe to call. - if unsafe { coap_ws_is_supported() != 1 } { - panic!("Required feature \"websockets\" is not supported by libcoap") - } - #[cfg(feature = "secure-websockets")] - // SAFETY: Function is always safe to call. - if unsafe { coap_wss_is_supported() != 1 } { - panic!("Required feature \"websockets\" is not supported by libcoap") - } + unsafe { + feature_check!( + ("af-unix", coap_af_unix_is_supported), + ("async", coap_async_is_supported), + ("client", coap_client_is_supported), + ("dtls", coap_dtls_is_supported), + ("dtls-cid", coap_dtls_cid_is_supported), + ("dtls-psk", coap_dtls_psk_is_supported), + ("dtls-pki", coap_dtls_pki_is_supported), + ("dtls-pkcs11", coap_dtls_pkcs11_is_supported), + ("dtls-rpk", coap_dtls_rpk_is_supported), + ("epoll", coap_epoll_is_supported), + ("ipv4", coap_ipv4_is_supported), + ("ipv6", coap_ipv6_is_supported), + ("observe-persist", coap_observe_persist_is_supported), + ("oscore", coap_oscore_is_supported), + ("q-block", coap_q_block_is_supported), + ("server", coap_server_is_supported), + ("tcp", coap_tcp_is_supported), + ("thread-safe", coap_threadsafe_is_supported), + ("tls", coap_tls_is_supported), + ("websockets", coap_ws_is_supported), + ("secure-websockets", coap_wss_is_supported) + ); } - #[cfg(not(feature_checks_available))] + + // ESP-IDF is missing the coap_tls_library_t type. + #[cfg(not(target_os = "espidf"))] { - println!("WARNING: coap_startup_with_feature_checks() could not assert the availability of features because the linked version of libcoap is too old (< 4.3.5rc3)!") + let presumed_dtls_backend = if cfg!(dtls_backend = "openssl") { + coap_tls_library_t_COAP_TLS_LIBRARY_OPENSSL + } else if cfg!(dtls_backend = "gnutls") { + coap_tls_library_t_COAP_TLS_LIBRARY_GNUTLS + } else if cfg!(dtls_backend = "mbedtls") { + coap_tls_library_t_COAP_TLS_LIBRARY_MBEDTLS + } else if cfg!(dtls_backend = "tinydtls") { + coap_tls_library_t_COAP_TLS_LIBRARY_TINYDTLS + } else if cfg!(dtls_backend = "wolfssl") { + coap_tls_library_t_COAP_TLS_LIBRARY_WOLFSSL + } else { + coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS + }; + + let actual_dtls_backend = unsafe { *coap_get_tls_library_version() }.type_; + + if presumed_dtls_backend != coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS + && actual_dtls_backend != presumed_dtls_backend + { + panic_wrong_dtls!(presumed_dtls_backend, actual_dtls_backend); + } } + // SAFETY: Function is always safe to call. unsafe { coap_startup() } } @@ -300,14 +512,7 @@ mod tests { sync::{Arc, Barrier}, }; - use libc::{AF_INET, AF_INET6, in6_addr, in_addr, sa_family_t, size_t}; - - use crate::{ - coap_pdu_code_t::{COAP_REQUEST_CODE_GET, COAP_RESPONSE_CODE_CONTENT}, - coap_proto_t::COAP_PROTO_UDP, - coap_request_t::COAP_REQUEST_GET, - coap_response_t::COAP_RESPONSE_OK, - }; + use libc::{in6_addr, in_addr, sa_family_t, size_t, AF_INET, AF_INET6}; use super::*; @@ -326,7 +531,7 @@ mod tests { size: std::mem::size_of::() as socklen_t, addr: std::mem::zeroed(), }; - *coap_addr.addr.sin.as_mut() = sockaddr_in { + coap_addr.addr.sin = sockaddr_in { sin_family: AF_INET as sa_family_t, sin_port: addr.port().to_be(), sin_addr: in_addr { @@ -346,7 +551,7 @@ mod tests { size: std::mem::size_of::() as socklen_t, addr: std::mem::zeroed(), }; - *coap_addr.addr.sin6.as_mut() = sockaddr_in6 { + coap_addr.addr.sin6 = sockaddr_in6 { sin6_family: AF_INET6 as sa_family_t, sin6_port: addr.port().to_be(), sin6_addr: in6_addr { @@ -388,7 +593,7 @@ mod tests { COAP_TEST_RESOURCE_RESPONSE.as_ptr(), ); coap_set_app_data(coap_session_get_context(session), (&true) as *const bool as *mut c_void); - coap_pdu_set_code(response_pdu, COAP_RESPONSE_CODE_CONTENT); + coap_pdu_set_code(response_pdu, coap_pdu_code_t_COAP_RESPONSE_CODE_CONTENT); } /// Response handler for the CoAP client/server test (client-side) @@ -404,7 +609,7 @@ mod tests { received: *const coap_pdu_t, _mid: coap_mid_t, ) -> coap_response_t { - assert_eq!(coap_pdu_get_code(received), COAP_RESPONSE_CODE_CONTENT); + assert_eq!(coap_pdu_get_code(received), coap_pdu_code_t_COAP_RESPONSE_CODE_CONTENT); let mut len: size_t = 0; let mut data: *const u8 = std::ptr::null(); assert_ne!(coap_get_data(received, &mut len, &mut data), 0); @@ -412,7 +617,7 @@ mod tests { assert_eq!(data, COAP_TEST_RESOURCE_RESPONSE.as_bytes()); coap_set_app_data(coap_session_get_context(session), (&true) as *const bool as *mut c_void); - return COAP_RESPONSE_OK; + return coap_response_t_COAP_RESPONSE_OK; } /// Creates a CoAP server that provides a single resource under COAP_TEST_RESOURCE_URI over the @@ -425,7 +630,7 @@ mod tests { let address: coap_address_t = coap_address_from_socketaddr(addr); // SAFETY: We asserted that context != null, listen_addr is a reference and can therefore not be null. - let endpoint = unsafe { coap_new_endpoint(context, &address, coap_proto_t::COAP_PROTO_UDP) }; + let endpoint = unsafe { coap_new_endpoint(context, &address, coap_proto_t_COAP_PROTO_UDP) }; assert!(!endpoint.is_null()); // SAFETY: Since we use a string constant here, the arguments to the function are all valid. @@ -442,7 +647,11 @@ mod tests { // struct allows for mutable pointers to be set there, so that applications can use this to // modify some application specific state. unsafe { - coap_register_request_handler(test_resource, COAP_REQUEST_GET, Some(test_resource_handler)); + coap_register_request_handler( + test_resource, + coap_request_t_COAP_REQUEST_GET, + Some(test_resource_handler), + ); coap_add_resource(context, test_resource); coap_set_app_data(context, (&false) as *const bool as *mut c_void); } @@ -471,6 +680,7 @@ mod tests { /// Test case that creates a basic coap server and makes a request to it from a separate context #[test] fn test_coap_client_server_basic() { + coap_startup_with_feature_checks(); // This will give us a SocketAddress with a port in the local port range automatically // assigned by the operating system. // Because the UdpSocket goes out of scope, the Port will be free for usage by libcoap. @@ -504,7 +714,7 @@ mod tests { // SAFETY: null pointer is valid argument for local_if, server_address is guaranteed to be // a correct value (conversion cannot fail), validity of context was asserted before. let client_session = - unsafe { coap_new_client_session(context, std::ptr::null(), &server_address, COAP_PROTO_UDP) }; + unsafe { coap_new_client_session(context, std::ptr::null(), &server_address, coap_proto_t_COAP_PROTO_UDP) }; // SAFETY: context and client_session were asserted to be valid. // Casting *const to *mut is fine because we don't mutate the value pointed to and the @@ -515,8 +725,11 @@ mod tests { unsafe { coap_register_response_handler(context, Some(test_response_handler)); coap_set_app_data(context, (&false) as *const bool as *mut c_void); - let coap_request_pdu = - coap_new_pdu(coap_pdu_type_t::COAP_MESSAGE_NON, COAP_REQUEST_CODE_GET, client_session); + let coap_request_pdu = coap_new_pdu( + coap_pdu_type_t_COAP_MESSAGE_NON, + coap_pdu_code_t_COAP_REQUEST_CODE_GET, + client_session, + ); assert!(!coap_request_pdu.is_null()); assert_ne!( coap_add_option( diff --git a/libcoap/Cargo.toml b/libcoap/Cargo.toml index 0949d459..189bfaa5 100644 --- a/libcoap/Cargo.toml +++ b/libcoap/Cargo.toml @@ -16,29 +16,30 @@ authors = ["Hugo Hakim Damer "] categories = ["api-bindings", "network-programming", "embedded"] keywords = ["coap", "libcoap"] resolver = "2" -# Current reason for MSRV (please update when increasing MSRV): Transient dependency "home" requires Rust 1.81. -rust-version = "1.81.0" +# Current reason for MSRV (please update when increasing MSRV): bindgen generates unsafe extern "C" blocks, which are +# not supported on Rust < 1.82, and Rust < 1.84 is not MSRV aware, so we can't just increase libcoap-sys's MSRV. +# See also: https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-extern.html +rust-version = "1.82.0" [features] -default = ["dtls-psk", "tcp", "dtls_openssl", "vendored", "libcoap-sys/default"] -dtls_tinydtls = ["libcoap-sys/dtls_backend_tinydtls"] -dtls_tinydtls_vendored = ["dtls_tinydtls", "libcoap-sys/dtls_backend_tinydtls_vendored"] -dtls_openssl = ["libcoap-sys/dtls_backend_openssl"] -dtls_openssl_vendored = ["dtls_openssl", "libcoap-sys/dtls_backend_openssl_vendored"] -dtls_gnutls = ["libcoap-sys/dtls_backend_gnutls"] -dtls_mbedtls = ["libcoap-sys/dtls_backend_mbedtls"] -dtls_mbedtls_vendored = ["dtls_mbedtls", "libcoap-sys/dtls_backend_mbedtls_vendored"] -dtls-psk = ["libcoap-sys/dtls", "libcoap-sys/dtls-psk"] -dtls-pki = ["libcoap-sys/dtls", "libcoap-sys/dtls-pki"] -dtls-rpk = ["libcoap-sys/dtls", "libcoap-sys/dtls-rpk"] +default = ["dtls-psk", "tcp"] +dtls = ["libcoap-sys/dtls"] +dtls-psk = ["dtls", "libcoap-sys/dtls-psk"] +dtls-pki = ["dtls", "libcoap-sys/dtls-pki"] +dtls-rpk = ["dtls", "libcoap-sys/dtls-rpk"] +dtls-cid = ["dtls-psk", "libcoap-sys/dtls-cid"] tcp = ["libcoap-sys/tcp"] tls = ["libcoap-sys/tls"] rand = ["dep:rand", "dep:rand_core"] vendored = ["libcoap-sys/vendored"] +dtls-openssl-sys = ["libcoap-sys/dtls-openssl-sys"] +dtls-mbedtls-sys = ["libcoap-sys/dtls-mbedtls-sys"] +dtls-tinydtls-sys = ["libcoap-sys/dtls-tinydtls-sys"] +dtls-openssl-sys-vendored = ["libcoap-sys/dtls-openssl-sys-vendored"] +dtls-tinydtls-sys-vendored = ["libcoap-sys/dtls-tinydtls-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-traits = { version = "^0.2.14" } url = { version = "^2.2", optional = true } @@ -48,6 +49,18 @@ thiserror = "^1.0" [build-dependencies] version-compare = "0.2.0" +anyhow = "1.0.95" [package.metadata.docs.rs] features = ["dtls", "dtls_openssl", "vendored", "url"] + +[target.'cfg(target_os="espidf")'.dependencies] +esp-idf-sys = { version = "0.36.1" } + +# For ESP-IDF builds, we need to add the espressif/coap component ourselves here, +# as esp-idf-sys only inspects the metadata for direct dependencies. +# Otherwise, users of this library would always also have to depend on libcoap-sys +# or add this snippet themselves. +[[package.metadata.esp-idf-sys.extra_components]] +remote_component = { name = "espressif/coap", version = "4.3.5~3" } +bindings_header = "src/wrapper.h" diff --git a/libcoap/build.rs b/libcoap/build.rs index 22c63928..0f883095 100644 --- a/libcoap/build.rs +++ b/libcoap/build.rs @@ -1,32 +1,69 @@ // SPDX-License-Identifier: BSD-2-CLAUSE -use version_compare::{Cmp, Version}; - -fn main() { - println!("cargo::rustc-check-cfg=cfg(dtls_ec_jpake_support)"); - println!("cargo::rustc-check-cfg=cfg(dtls_cid_support)"); - println!("cargo::rustc-check-cfg=cfg(coap_uri_buf_unused)"); - println!("cargo::rustc-check-cfg=cfg(dtls)"); - if let Ok(libcoap_version) = std::env::var("DEP_COAP_3_LIBCOAP_VERSION") { - let version = Version::from(libcoap_version.as_ref()).expect("invalid libcoap version"); - // libcoap >= 4.3.5rc2 no longer uses the buf and buflen parameters in - // coap_uri_into_options(), so we can optimize them out and save some memory. - match version.compare(Version::from("4.3.5rc2").unwrap()) { - Cmp::Gt | Cmp::Eq => { - println!("cargo:rustc-cfg=coap_uri_buf_unused"); - }, - _ => {}, - } - // libcoap >= 4.3.5rc3 supports DTLS EC JPAKE and connection ID extensions, which adds - // additional fields to some DTLS configuration structs. - match version.compare(Version::from("4.3.5rc3").unwrap()) { - Cmp::Gt | Cmp::Eq => { - println!("cargo:rustc-cfg=dtls_ec_jpake_support"); - println!("cargo:rustc-cfg=dtls_cid_support"); - }, - _ => {}, - } +use std::env::VarError; + +use anyhow::{bail, Result}; +use version_compare::Version; + +/// The minimal version of libcoap that is expected to work with libcoap-rs. +/// +/// Does not necessarily match the minimum supported version of libcoap-sys, and should be increased +/// whenever we make changes to the safe wrapper that can not feasibly be supported on older +/// versions of libcoap. +const MINIMUM_LIBCOAP_VERSION: &str = "4.3.5"; + +fn main() -> Result<()> { + println!("cargo::rustc-check-cfg=cfg(dtls_backend, values(\"gnutls\", \"mbedtls\", \"tinydtls\", \"openssl\", \"wolfssl\"))"); + println!("cargo::rustc-check-cfg=cfg(libcoap_version, values(any()))"); + + // If at all possible, you should not write code that is conditional on the DTLS backend (use + // cargo features instead). + // If there is no other way (e.g., if there is no way to determine feature support), you must + // at least write code that can also deal with the variable not being there. + match std::env::var("DEP_COAP_3_DTLS_BACKEND") { + Ok(dtls_backend) => { + println!("cargo::rustc-cfg=dtls_backend=\"{}\"", dtls_backend) + }, + Err(VarError::NotUnicode(_)) => { + panic!("DEP_COAP_3_DTLS_BACKEND is not valid unicode") + }, + Err(VarError::NotPresent) => {}, } - #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk", feature = "dtls-psk"))] - println!("cargo:rustc-cfg=dtls") + + match std::env::var("DEP_COAP_3_LIBCOAP_VERSION") { + Ok(libcoap_version) => { + let version = Version::from(libcoap_version.as_ref()).expect("invalid libcoap version"); + println!("cargo::rustc-cfg=libcoap_version=\"{}\"", version.as_str()); + + if version < Version::from(MINIMUM_LIBCOAP_VERSION).unwrap() { + // Unlike libcoap-sys, we do return an error here and not just a warning, as + // unsupported libcoap versions might have semantic differences that break the + // safety guarantees this wrapper is supposed to provide. + + bail!("The linked version of libcoap is lower than the minimal version required for libcoap-rs ({}), can not build.", MINIMUM_LIBCOAP_VERSION); + } + + // Uncomment and adjust this in order to create version-dependent cfg-flags. + // When updating the minimum supported libcoap version, one should also remove all + // cfg-flags that are only relevant for versions lower than the new minimum + // supported one, and rewrite code gated on these flags to assume that the minimum + // version . + // Note: In most cases, you probably want to check for the presence of a given feature instead. + // Matching based on the libcoap version usually only makes sense in order to either + // enable optional optimizations possible with newer versions, or to add struct fields + // that were added into existing structs without breaking backward compatibility. + + /*if version > Version::from("4.3.5").unwrap() { + println!("cargo:rustc-cfg=[INSERT FLAG NAME HERE]") + }*/ + }, + Err(VarError::NotUnicode(_)) => { + panic!("DEP_COAP_3_LIBCOAP_VERSION is not valid unicode") + }, + Err(VarError::NotPresent) => { + println!("cargo:warning=Unable to automatically detect the linked version of libcoap, please manually ensure that the used version is at least {} for libcoap-rs to work as expected.", MINIMUM_LIBCOAP_VERSION); + }, + } + + Ok(()) } diff --git a/libcoap/examples/esp-idf/.cargo/config.toml b/libcoap/examples/esp-idf/.cargo/config.toml new file mode 100644 index 00000000..11494290 --- /dev/null +++ b/libcoap/examples/esp-idf/.cargo/config.toml @@ -0,0 +1,16 @@ +[build] +target = "riscv32imc-esp-espidf" + +[target.riscv32imc-esp-espidf] +linker = "ldproxy" +runner = "espflash flash --monitor" +rustflags = [ "--cfg", "espidf_time64"] + +[unstable] +build-std = ["std", "panic_abort"] + +[env] +MCU="esp32c3" +# Note: this variable is not used by the pio builder (`cargo build --features pio`) +ESP_IDF_VERSION = "v5.2.3" + diff --git a/libcoap/examples/esp-idf/.github/workflows/rust_ci.yml b/libcoap/examples/esp-idf/.github/workflows/rust_ci.yml new file mode 100644 index 00000000..e7e9b1ec --- /dev/null +++ b/libcoap/examples/esp-idf/.github/workflows/rust_ci.yml @@ -0,0 +1,41 @@ +name: Continuous Integration + +on: + push: + paths-ignore: + - "**/README.md" + pull_request: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + rust-checks: + name: Rust Checks + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + action: + - command: build + args: --release + - command: fmt + args: --all -- --check --color always + - command: clippy + args: --all-targets --all-features --workspace -- -D warnings + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Rust + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: nightly + components: rust-src rustfmt clippy + - name: Enable caching + uses: Swatinem/rust-cache@v2 + - name: Install ldproxy + run: cargo install ldproxy + - name: Run command + run: cargo ${{ matrix.action.command }} ${{ matrix.action.args }} diff --git a/libcoap/examples/esp-idf/.gitignore b/libcoap/examples/esp-idf/.gitignore new file mode 100644 index 00000000..aa1d4bf7 --- /dev/null +++ b/libcoap/examples/esp-idf/.gitignore @@ -0,0 +1,5 @@ +/.vscode +/.embuild +/target +/Cargo.lock +/vars.log \ No newline at end of file diff --git a/libcoap/examples/esp-idf/Cargo.toml b/libcoap/examples/esp-idf/Cargo.toml new file mode 100644 index 00000000..92046ac0 --- /dev/null +++ b/libcoap/examples/esp-idf/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "libcoap-esp-idf-example" +version = "0.1.0" +authors = ["Hugo Hakim Damer "] +edition = "2021" +resolver = "2" +rust-version = "1.77" + +[[bin]] +name = "libcoap-esp-idf-example" +harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors + +[profile.release] +opt-level = "s" + +[profile.dev] +debug = true # Symbols are nice and they don't increase the size on Flash +opt-level = "z" + +[features] +default = [] + +experimental = ["esp-idf-svc/experimental"] + +[dependencies] +log = "0.4" +esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync"] } +esp-idf-sys = { version = "0.36.1" } +libcoap-rs = { version = "*", path = "../../" } + +[build-dependencies] +embuild = "0.33" + +[workspace] diff --git a/libcoap/examples/esp-idf/README.md b/libcoap/examples/esp-idf/README.md new file mode 100644 index 00000000..ba3f678b --- /dev/null +++ b/libcoap/examples/esp-idf/README.md @@ -0,0 +1,6 @@ +This folder contains a slightly modified version of the ESP-IDF Rust project template that can be found +[here](https://github.com/esp-rs/esp-idf-template/tree/master/cargo), with slight modifications to +`sdkconfig.defaults`, `Cargo.toml` and `main.rs` to add support for `libcoap-sys` and `libcoap-rs`. + +It is mainly used to test the compilation and binding generation process for ESP-IDF builds of `libcoap-rs`, +but you may also use it as a reference for your own projects. \ No newline at end of file diff --git a/libcoap/examples/esp-idf/build.rs b/libcoap/examples/esp-idf/build.rs new file mode 100644 index 00000000..112ec3f7 --- /dev/null +++ b/libcoap/examples/esp-idf/build.rs @@ -0,0 +1,3 @@ +fn main() { + embuild::espidf::sysenv::output(); +} diff --git a/libcoap/examples/esp-idf/components_esp32c3.lock b/libcoap/examples/esp-idf/components_esp32c3.lock new file mode 100644 index 00000000..b6391610 --- /dev/null +++ b/libcoap/examples/esp-idf/components_esp32c3.lock @@ -0,0 +1,20 @@ +dependencies: + espressif/coap: + component_hash: a5d1b781b15980d9af136b5e63315dd5f5ec00215cc755f395ad2fb4977c7668 + dependencies: + - name: idf + require: private + version: '>=4.4' + source: + registry_url: https://components.espressif.com/ + type: service + version: 4.3.5~3 + idf: + source: + type: idf + version: 5.2.3 +direct_dependencies: +- espressif/coap +manifest_hash: 13fcf89b865b0f073a765a0dfdcf062a0063b4af7a631d3d37ef94c920538a25 +target: esp32c3 +version: 2.0.0 diff --git a/libcoap/examples/esp-idf/rust-toolchain.toml b/libcoap/examples/esp-idf/rust-toolchain.toml new file mode 100644 index 00000000..f70d2254 --- /dev/null +++ b/libcoap/examples/esp-idf/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rust-src"] diff --git a/libcoap/examples/esp-idf/sdkconfig.defaults b/libcoap/examples/esp-idf/sdkconfig.defaults new file mode 100644 index 00000000..1c2bfab4 --- /dev/null +++ b/libcoap/examples/esp-idf/sdkconfig.defaults @@ -0,0 +1,21 @@ +# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 + +# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). +# This allows to use 1 ms granularity for thread sleeps (10 ms by default). +#CONFIG_FREERTOS_HZ=1000 + +# Workaround for https://github.com/espressif/esp-idf/issues/7631 +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n + +CONFIG_COAP_CLIENT_SUPPORT=y +CONFIG_COAP_SERVER_SUPPORT=y +CONFIG_COAP_TCP_SUPPORT=y +CONFIG_COAP_OSCORE_SUPPORT=y +CONFIG_COAP_Q_BLOCK=y +CONFIG_COAP_SERVER_SUPPORT=y +CONFIG_COAP_THREAD_RECURSIVE_CHECK=y +CONFIG_COAP_THREAD_SAFE=y +CONFIG_COAP_OBSERVE_PERSIST=y +CONFIG_COAP_WEBSOCKETS=y diff --git a/libcoap/examples/esp-idf/src/main.rs b/libcoap/examples/esp-idf/src/main.rs new file mode 100644 index 00000000..c5f1bcc6 --- /dev/null +++ b/libcoap/examples/esp-idf/src/main.rs @@ -0,0 +1,14 @@ +use libcoap_rs::CoapContext; + +fn main() { + // It is necessary to call this function once. Otherwise some patches to the runtime + // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 + esp_idf_svc::sys::link_patches(); + + // Bind the log crate to the ESP Logging facilities + esp_idf_svc::log::EspLogger::initialize_default(); + + log::info!("Hello, world!"); + + let context = CoapContext::new().unwrap(); +} diff --git a/libcoap/examples/esp-idf/vars.log b/libcoap/examples/esp-idf/vars.log new file mode 100644 index 00000000..e69de29b diff --git a/libcoap/src/context.rs b/libcoap/src/context.rs index c8be5ec1..ee6e2c01 100644 --- a/libcoap/src/context.rs +++ b/libcoap/src/context.rs @@ -9,15 +9,15 @@ //! Module containing context-internal types and traits. +use core::ffi::c_uint; #[cfg(feature = "dtls-pki")] use std::ffi::CString; -#[cfg(dtls)] +#[cfg(feature = "dtls")] use std::ptr::NonNull; use std::{any::Any, ffi::c_void, fmt::Debug, net::SocketAddr, ops::Sub, sync::Once, time::Duration}; #[cfg(all(feature = "dtls-pki", unix))] use std::{os::unix::ffi::OsStrExt, path::Path}; -use libc::c_uint; #[cfg(feature = "dtls-pki")] use libcoap_sys::coap_context_set_pki_root_cas; use libcoap_sys::{ @@ -25,9 +25,22 @@ use libcoap_sys::{ coap_context_get_max_handshake_sessions, coap_context_get_max_idle_sessions, coap_context_get_session_timeout, coap_context_set_block_mode, coap_context_set_csm_max_message_size, coap_context_set_csm_timeout, coap_context_set_keepalive, coap_context_set_max_handshake_sessions, coap_context_set_max_idle_sessions, - coap_context_set_session_timeout, coap_context_t, coap_event_t, coap_free_context, coap_get_app_data, - coap_io_process, coap_new_context, coap_proto_t, coap_register_event_handler, coap_register_response_handler, - coap_set_app_data, coap_startup_with_feature_checks, COAP_BLOCK_SINGLE_BODY, COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT, + coap_context_set_session_timeout, coap_context_t, coap_event_t, coap_event_t_COAP_EVENT_BAD_PACKET, + coap_event_t_COAP_EVENT_DTLS_CLOSED, coap_event_t_COAP_EVENT_DTLS_CONNECTED, coap_event_t_COAP_EVENT_DTLS_ERROR, + coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE, coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE, + coap_event_t_COAP_EVENT_MSG_RETRANSMITTED, coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR, + coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE, coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR, + coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED, coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD, + coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY, coap_event_t_COAP_EVENT_PARTIAL_BLOCK, + coap_event_t_COAP_EVENT_SERVER_SESSION_DEL, coap_event_t_COAP_EVENT_SERVER_SESSION_NEW, + coap_event_t_COAP_EVENT_SESSION_CLOSED, coap_event_t_COAP_EVENT_SESSION_CONNECTED, + coap_event_t_COAP_EVENT_SESSION_FAILED, coap_event_t_COAP_EVENT_TCP_CLOSED, coap_event_t_COAP_EVENT_TCP_CONNECTED, + coap_event_t_COAP_EVENT_TCP_FAILED, coap_event_t_COAP_EVENT_WS_CLOSED, coap_event_t_COAP_EVENT_WS_CONNECTED, + coap_event_t_COAP_EVENT_WS_PACKET_SIZE, coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL, coap_free_context, + coap_get_app_data, coap_io_process, coap_new_context, coap_proto_t, coap_proto_t_COAP_PROTO_DTLS, + coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP, coap_register_event_handler, + coap_register_response_handler, coap_set_app_data, coap_startup_with_feature_checks, COAP_BLOCK_SINGLE_BODY, + COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT, }; #[cfg(any(feature = "dtls-rpk", feature = "dtls-pki"))] @@ -148,49 +161,52 @@ impl<'a> CoapContext<'a> { let inner_ref = &mut *self.inner.borrow_mut(); // Call event handler for event. if let Some(handler) = &mut inner_ref.event_handler { + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] match event { - coap_event_t::COAP_EVENT_DTLS_CLOSED => handler.handle_dtls_closed(&mut session), - coap_event_t::COAP_EVENT_DTLS_CONNECTED => handler.handle_dtls_connected(&mut session), - coap_event_t::COAP_EVENT_DTLS_RENEGOTIATE => handler.handle_dtls_renegotiate(&mut session), - coap_event_t::COAP_EVENT_DTLS_ERROR => handler.handle_dtls_error(&mut session), - coap_event_t::COAP_EVENT_TCP_CONNECTED => handler.handle_tcp_connected(&mut session), - coap_event_t::COAP_EVENT_TCP_CLOSED => handler.handle_tcp_closed(&mut session), - coap_event_t::COAP_EVENT_TCP_FAILED => handler.handle_tcp_failed(&mut session), - coap_event_t::COAP_EVENT_SESSION_CONNECTED => handler.handle_session_connected(&mut session), - coap_event_t::COAP_EVENT_SESSION_CLOSED => handler.handle_session_closed(&mut session), - coap_event_t::COAP_EVENT_SESSION_FAILED => handler.handle_session_failed(&mut session), - coap_event_t::COAP_EVENT_PARTIAL_BLOCK => handler.handle_partial_block(&mut session), - coap_event_t::COAP_EVENT_SERVER_SESSION_NEW => { + coap_event_t_COAP_EVENT_DTLS_CLOSED => handler.handle_dtls_closed(&mut session), + coap_event_t_COAP_EVENT_DTLS_CONNECTED => handler.handle_dtls_connected(&mut session), + coap_event_t_COAP_EVENT_DTLS_RENEGOTIATE => handler.handle_dtls_renegotiate(&mut session), + coap_event_t_COAP_EVENT_DTLS_ERROR => handler.handle_dtls_error(&mut session), + coap_event_t_COAP_EVENT_TCP_CONNECTED => handler.handle_tcp_connected(&mut session), + coap_event_t_COAP_EVENT_TCP_CLOSED => handler.handle_tcp_closed(&mut session), + coap_event_t_COAP_EVENT_TCP_FAILED => handler.handle_tcp_failed(&mut session), + coap_event_t_COAP_EVENT_SESSION_CONNECTED => handler.handle_session_connected(&mut session), + coap_event_t_COAP_EVENT_SESSION_CLOSED => handler.handle_session_closed(&mut session), + coap_event_t_COAP_EVENT_SESSION_FAILED => handler.handle_session_failed(&mut session), + coap_event_t_COAP_EVENT_PARTIAL_BLOCK => handler.handle_partial_block(&mut session), + coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => { if let CoapSession::Server(server_session) = &mut session { handler.handle_server_session_new(server_session) } else { panic!("server-side session event fired for non-server-side session"); } }, - coap_event_t::COAP_EVENT_SERVER_SESSION_DEL => { + coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => { if let CoapSession::Server(server_session) = &mut session { handler.handle_server_session_del(server_session) } else { panic!("server-side session event fired for non-server-side session"); } }, - coap_event_t::COAP_EVENT_XMIT_BLOCK_FAIL => handler.handle_xmit_block_fail(&mut session), - coap_event_t::COAP_EVENT_BAD_PACKET => handler.handle_bad_packet(&mut session), - coap_event_t::COAP_EVENT_MSG_RETRANSMITTED => handler.handle_msg_retransmitted(&mut session), - coap_event_t::COAP_EVENT_OSCORE_DECRYPTION_FAILURE => { + coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL => handler.handle_xmit_block_fail(&mut session), + coap_event_t_COAP_EVENT_BAD_PACKET => handler.handle_bad_packet(&mut session), + coap_event_t_COAP_EVENT_MSG_RETRANSMITTED => handler.handle_msg_retransmitted(&mut session), + coap_event_t_COAP_EVENT_OSCORE_DECRYPTION_FAILURE => { handler.handle_oscore_decryption_failure(&mut session) }, - coap_event_t::COAP_EVENT_OSCORE_NOT_ENABLED => handler.handle_oscore_not_enabled(&mut session), - coap_event_t::COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD => { + coap_event_t_COAP_EVENT_OSCORE_NOT_ENABLED => handler.handle_oscore_not_enabled(&mut session), + coap_event_t_COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD => { handler.handle_oscore_no_protected_payload(&mut session) }, - coap_event_t::COAP_EVENT_OSCORE_NO_SECURITY => handler.handle_oscore_no_security(&mut session), - coap_event_t::COAP_EVENT_OSCORE_INTERNAL_ERROR => handler.handle_oscore_internal_error(&mut session), - coap_event_t::COAP_EVENT_OSCORE_DECODE_ERROR => handler.handle_oscore_decode_error(&mut session), - coap_event_t::COAP_EVENT_WS_PACKET_SIZE => handler.handle_ws_packet_size(&mut session), - coap_event_t::COAP_EVENT_WS_CONNECTED => handler.handle_ws_connected(&mut session), - coap_event_t::COAP_EVENT_WS_CLOSED => handler.handle_ws_closed(&mut session), - coap_event_t::COAP_EVENT_KEEPALIVE_FAILURE => handler.handle_keepalive_failure(&mut session), + coap_event_t_COAP_EVENT_OSCORE_NO_SECURITY => handler.handle_oscore_no_security(&mut session), + coap_event_t_COAP_EVENT_OSCORE_INTERNAL_ERROR => handler.handle_oscore_internal_error(&mut session), + coap_event_t_COAP_EVENT_OSCORE_DECODE_ERROR => handler.handle_oscore_decode_error(&mut session), + coap_event_t_COAP_EVENT_WS_PACKET_SIZE => handler.handle_ws_packet_size(&mut session), + coap_event_t_COAP_EVENT_WS_CONNECTED => handler.handle_ws_connected(&mut session), + coap_event_t_COAP_EVENT_WS_CLOSED => handler.handle_ws_closed(&mut session), + coap_event_t_COAP_EVENT_KEEPALIVE_FAILURE => handler.handle_keepalive_failure(&mut session), _ => { // TODO probably a log message is justified here. }, @@ -198,9 +214,12 @@ impl<'a> CoapContext<'a> { } // For server-side sessions: Ensure that server-side session wrappers are either kept in memory or dropped when needed. if let CoapSession::Server(serv_sess) = session { + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] match event { - coap_event_t::COAP_EVENT_SERVER_SESSION_NEW => inner_ref.server_sessions.push(serv_sess), - coap_event_t::COAP_EVENT_SERVER_SESSION_DEL => { + coap_event_t_COAP_EVENT_SERVER_SESSION_NEW => inner_ref.server_sessions.push(serv_sess), + coap_event_t_COAP_EVENT_SERVER_SESSION_DEL => { std::mem::drop(inner_ref.server_sessions.remove( inner_ref.server_sessions.iter().position(|v| v.eq(&serv_sess)).expect( "attempted to remove session wrapper from context that was never associated with it", @@ -369,29 +388,29 @@ impl CoapContext<'_> { /// Creates a new UDP endpoint that is bound to the given address. pub fn add_endpoint_udp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> { - self.add_endpoint(addr, coap_proto_t::COAP_PROTO_UDP) + self.add_endpoint(addr, coap_proto_t_COAP_PROTO_UDP) } /// Creates a new TCP endpoint that is bound to the given address. #[cfg(feature = "tcp")] pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> { - self.add_endpoint(addr, coap_proto_t::COAP_PROTO_TCP) + self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TCP) } /// Creates a new DTLS endpoint that is bound to the given address. /// /// Note that in order to actually connect to DTLS clients, you need to set a crypto provider /// using [CoapContext::set_psk_context] and/or [CoapContext::set_pki_rpk_context]. - #[cfg(dtls)] + #[cfg(feature = "dtls")] pub fn add_endpoint_dtls(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> { - self.add_endpoint(addr, coap_proto_t::COAP_PROTO_DTLS) + self.add_endpoint(addr, coap_proto_t_COAP_PROTO_DTLS) } // /// TODO // #[cfg(all(feature = "tcp", dtls))] // pub fn add_endpoint_tls(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> { // todo!() - // // TODO: self.add_endpoint(addr, coap_proto_t::COAP_PROTO_TLS) + // // TODO: self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TLS) // } /// Adds the given resource to the resource pool of this context. diff --git a/libcoap/src/crypto/pki_rpk/key.rs b/libcoap/src/crypto/pki_rpk/key.rs index 036200d5..acdc20c0 100644 --- a/libcoap/src/crypto/pki_rpk/key.rs +++ b/libcoap/src/crypto/pki_rpk/key.rs @@ -7,16 +7,23 @@ * See the README as well as the LICENSE file for more information. */ +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +use std::{ffi::CString, fmt::Debug, path::Path}; + use libcoap_sys::{ - coap_asn1_privatekey_type_t, coap_const_char_ptr_t, coap_dtls_key_t, coap_dtls_pki_t, coap_pki_define_t, + coap_asn1_privatekey_type_t, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_CMAC, + coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DH, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DHX, + coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA1, + coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA2, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA3, + coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA4, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_EC, + coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_HKDF, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_HMAC, + coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_NONE, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_RSA, + coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_RSA2, coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_TLS1_PRF, + coap_const_char_ptr_t, coap_dtls_key_t, coap_dtls_pki_t, coap_pki_define_t, }; use num_derive::FromPrimitive; use num_traits::FromPrimitive; -use std::ffi::CString; -use std::fmt::Debug; -#[cfg(unix)] -use std::os::unix::ffi::OsStrExt; -use std::path::Path; /// Trait for marker structs that describe different types of asymmetric DTLS keys (RPK or PKI). #[allow(private_bounds)] @@ -210,41 +217,41 @@ impl> From for EngineKeyComponent { #[derive(Copy, Clone, FromPrimitive, Debug, PartialEq, Eq, Hash, Default)] pub enum Asn1PrivateKeyType { #[default] - None = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_NONE as isize, - Rsa = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_RSA as isize, - Rsa2 = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_RSA2 as isize, - Dsa = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA as isize, - Dsa1 = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA1 as isize, - Dsa2 = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA2 as isize, - Dsa3 = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA3 as isize, - Dsa4 = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA4 as isize, - Dh = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DH as isize, - Dhx = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DHX as isize, - Ec = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_EC as isize, - Hmac = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_HMAC as isize, - Cmac = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_CMAC as isize, - Tls1Prf = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_TLS1_PRF as isize, - Hkdf = coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_HKDF as isize, + None = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_NONE as isize, + Rsa = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_RSA as isize, + Rsa2 = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_RSA2 as isize, + Dsa = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA as isize, + Dsa1 = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA1 as isize, + Dsa2 = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA2 as isize, + Dsa3 = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA3 as isize, + Dsa4 = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA4 as isize, + Dh = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DH as isize, + Dhx = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DHX as isize, + Ec = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_EC as isize, + Hmac = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_HMAC as isize, + Cmac = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_CMAC as isize, + Tls1Prf = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_TLS1_PRF as isize, + Hkdf = coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_HKDF as isize, } impl From for coap_asn1_privatekey_type_t { fn from(value: Asn1PrivateKeyType) -> Self { match value { - Asn1PrivateKeyType::None => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_NONE, - Asn1PrivateKeyType::Rsa => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_RSA, - Asn1PrivateKeyType::Rsa2 => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_RSA2, - Asn1PrivateKeyType::Dsa => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA, - Asn1PrivateKeyType::Dsa1 => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA1, - Asn1PrivateKeyType::Dsa2 => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA2, - Asn1PrivateKeyType::Dsa3 => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA3, - Asn1PrivateKeyType::Dsa4 => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DSA4, - Asn1PrivateKeyType::Dh => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DH, - Asn1PrivateKeyType::Dhx => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_DHX, - Asn1PrivateKeyType::Ec => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_EC, - Asn1PrivateKeyType::Hmac => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_HMAC, - Asn1PrivateKeyType::Cmac => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_CMAC, - Asn1PrivateKeyType::Tls1Prf => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_TLS1_PRF, - Asn1PrivateKeyType::Hkdf => coap_asn1_privatekey_type_t::COAP_ASN1_PKEY_HKDF, + Asn1PrivateKeyType::None => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_NONE, + Asn1PrivateKeyType::Rsa => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_RSA, + Asn1PrivateKeyType::Rsa2 => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_RSA2, + Asn1PrivateKeyType::Dsa => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA, + Asn1PrivateKeyType::Dsa1 => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA1, + Asn1PrivateKeyType::Dsa2 => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA2, + Asn1PrivateKeyType::Dsa3 => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA3, + Asn1PrivateKeyType::Dsa4 => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DSA4, + Asn1PrivateKeyType::Dh => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DH, + Asn1PrivateKeyType::Dhx => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_DHX, + Asn1PrivateKeyType::Ec => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_EC, + Asn1PrivateKeyType::Hmac => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_HMAC, + Asn1PrivateKeyType::Cmac => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_CMAC, + Asn1PrivateKeyType::Tls1Prf => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_TLS1_PRF, + Asn1PrivateKeyType::Hkdf => coap_asn1_privatekey_type_t_COAP_ASN1_PKEY_HKDF, } } } diff --git a/libcoap/src/crypto/pki_rpk/mod.rs b/libcoap/src/crypto/pki_rpk/mod.rs index f35f8f57..5d0918b6 100644 --- a/libcoap/src/crypto/pki_rpk/mod.rs +++ b/libcoap/src/crypto/pki_rpk/mod.rs @@ -295,27 +295,31 @@ mod pki; #[cfg(feature = "dtls-rpk")] mod rpk; -#[cfg(feature = "dtls-pki")] -pub use pki::*; -#[cfg(feature = "dtls-rpk")] -pub use rpk::*; +use std::{ + cell::RefCell, + ffi::{c_char, c_int, c_uint, c_void, CStr, CString, NulError}, + fmt::{Debug, Formatter}, + marker::PhantomData, + ptr::NonNull, + rc::{Rc, Weak}, +}; pub use key::*; - -use crate::error::{ContextConfigurationError, SessionCreationError}; -use crate::session::CoapSession; -use crate::types::CoapAddress; -use crate::CoapContext; use libcoap_sys::{ coap_context_set_pki, coap_context_t, coap_dtls_key_t, coap_dtls_pki_t, coap_new_client_session_pki, coap_proto_t, coap_session_t, COAP_DTLS_PKI_SETUP_VERSION, }; -use std::cell::RefCell; -use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString, NulError}; -use std::fmt::{Debug, Formatter}; -use std::marker::PhantomData; -use std::ptr::NonNull; -use std::rc::{Rc, Weak}; +#[cfg(feature = "dtls-pki")] +pub use pki::*; +#[cfg(feature = "dtls-rpk")] +pub use rpk::*; + +use crate::{ + error::{ContextConfigurationError, SessionCreationError}, + session::CoapSession, + types::CoapAddress, + CoapContext, +}; /// A context configuration for server-side PKI or RPK based DTLS encryption. #[derive(Clone, Debug)] diff --git a/libcoap/src/crypto/pki_rpk/pki.rs b/libcoap/src/crypto/pki_rpk/pki.rs index bebf05f7..4223db49 100644 --- a/libcoap/src/crypto/pki_rpk/pki.rs +++ b/libcoap/src/crypto/pki_rpk/pki.rs @@ -7,21 +7,33 @@ * See the README as well as the LICENSE file for more information. */ -use crate::crypto::pki_rpk; -use crate::crypto::pki_rpk::key::{KeyComponentSealed, KeyTypeSealed}; -use crate::crypto::pki_rpk::{ - Asn1PrivateKeyType, CertVerificationMode, CertVerifying, CnCallback, DerFileKeyComponent, DerMemoryKeyComponent, - EngineKeyComponent, KeyComponent, KeyDef, KeyDefSealed, NonCertVerifying, PemFileKeyComponent, - PemMemoryKeyComponent, Pkcs11KeyComponent, PkiRpkContext, PkiRpkContextBuilder, ServerPkiRpkCryptoContext, +use std::{ + ffi::{c_uint, CStr, CString}, + fmt::Debug, }; -use crate::crypto::ClientCryptoContext; -use crate::session::CoapSession; + use libcoap_sys::{ coap_const_char_ptr_t, coap_dtls_key_t, coap_dtls_key_t__bindgen_ty_1, coap_dtls_pki_t, coap_pki_define_t, - coap_pki_key_define_t, coap_pki_key_t, + coap_pki_define_t_COAP_PKI_KEY_DEF_DER, coap_pki_define_t_COAP_PKI_KEY_DEF_DER_BUF, + coap_pki_define_t_COAP_PKI_KEY_DEF_ENGINE, coap_pki_define_t_COAP_PKI_KEY_DEF_PEM, + coap_pki_define_t_COAP_PKI_KEY_DEF_PEM_BUF, coap_pki_define_t_COAP_PKI_KEY_DEF_PKCS11, coap_pki_key_define_t, + coap_pki_key_t_COAP_PKI_KEY_DEFINE, +}; + +use crate::{ + crypto::{ + pki_rpk, + pki_rpk::{ + key::{KeyComponentSealed, KeyTypeSealed}, + Asn1PrivateKeyType, CertVerificationMode, CertVerifying, CnCallback, DerFileKeyComponent, + DerMemoryKeyComponent, EngineKeyComponent, KeyComponent, KeyDef, KeyDefSealed, NonCertVerifying, + PemFileKeyComponent, PemMemoryKeyComponent, Pkcs11KeyComponent, PkiRpkContext, PkiRpkContextBuilder, + ServerPkiRpkCryptoContext, + }, + ClientCryptoContext, + }, + session::CoapSession, }; -use std::ffi::{c_uint, CStr, CString}; -use std::fmt::Debug; /// (Marker) key type for keys with a certificate signed by a trusted CA. #[derive(Debug, Clone, Copy)] @@ -314,7 +326,7 @@ impl, PK: KeyComponent, SK: KeyComponent> KeyDef let (private_key, private_key_len) = self.private_key.as_raw_key_component(); coap_dtls_key_t { - key_type: coap_pki_key_t::COAP_PKI_KEY_DEFINE, + key_type: coap_pki_key_t_COAP_PKI_KEY_DEFINE, key: coap_dtls_key_t__bindgen_ty_1 { define: coap_pki_key_define_t { ca, @@ -339,25 +351,25 @@ impl, PK: KeyComponent, SK: KeyComponent> KeyDef } impl KeyComponentSealed for PemFileKeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_PEM; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_PEM; } impl KeyComponentSealed for PemMemoryKeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_PEM_BUF; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_PEM_BUF; } impl KeyComponentSealed for DerFileKeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_DER; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_DER; } impl KeyComponentSealed for DerMemoryKeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_DER_BUF; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_DER_BUF; } impl KeyComponentSealed for Pkcs11KeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_PKCS11; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_PKCS11; } impl KeyComponentSealed for EngineKeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_ENGINE; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_ENGINE; } diff --git a/libcoap/src/crypto/pki_rpk/rpk.rs b/libcoap/src/crypto/pki_rpk/rpk.rs index 1642bc75..486f6a9b 100644 --- a/libcoap/src/crypto/pki_rpk/rpk.rs +++ b/libcoap/src/crypto/pki_rpk/rpk.rs @@ -7,20 +7,27 @@ * See the README as well as the LICENSE file for more information. */ -use crate::crypto::pki_rpk; -use crate::crypto::pki_rpk::key::{KeyComponentSealed, KeyTypeSealed}; -use crate::crypto::pki_rpk::{ - Asn1PrivateKeyType, CnCallback, KeyComponent, KeyDef, KeyDefSealed, NonCertVerifying, PemMemoryKeyComponent, - Pkcs11KeyComponent, PkiRpkContext, PkiRpkContextBuilder, ServerPkiRpkCryptoContext, -}; -use crate::crypto::ClientCryptoContext; -use crate::session::CoapSession; +use std::{ffi::CString, fmt::Debug}; + use libcoap_sys::{ coap_const_char_ptr_t, coap_dtls_key_t, coap_dtls_key_t__bindgen_ty_1, coap_dtls_pki_t, coap_pki_define_t, - coap_pki_key_define_t, coap_pki_key_t, + coap_pki_define_t_COAP_PKI_KEY_DEF_PEM, coap_pki_define_t_COAP_PKI_KEY_DEF_PKCS11_RPK, + coap_pki_define_t_COAP_PKI_KEY_DEF_RPK_BUF, coap_pki_key_define_t, coap_pki_key_t, + coap_pki_key_t_COAP_PKI_KEY_DEFINE, +}; + +use crate::{ + crypto::{ + pki_rpk, + pki_rpk::{ + key::{KeyComponentSealed, KeyTypeSealed}, + Asn1PrivateKeyType, CnCallback, KeyComponent, KeyDef, KeyDefSealed, NonCertVerifying, + PemMemoryKeyComponent, Pkcs11KeyComponent, PkiRpkContext, PkiRpkContextBuilder, ServerPkiRpkCryptoContext, + }, + ClientCryptoContext, + }, + session::CoapSession, }; -use std::ffi::CString; -use std::fmt::Debug; /// (Marker) key type for asymmetric DTLS keys not signed by a CA (raw public keys). #[derive(Debug, Clone, Copy)] @@ -170,7 +177,7 @@ impl, SK: KeyComponent> KeyDefSealed for RpkKeyDef, SK: KeyComponent> KeyDefSealed for RpkKeyDef>::DEFINE_TYPE, private_key_def: >::DEFINE_TYPE, private_key_type: self.asn1_private_key_type.into(), @@ -197,9 +204,9 @@ impl, SK: KeyComponent> KeyDef for RpkKeyDef } impl KeyComponentSealed for PemMemoryKeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_RPK_BUF; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_RPK_BUF; } impl KeyComponentSealed for Pkcs11KeyComponent { - const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t::COAP_PKI_KEY_DEF_PKCS11_RPK; + const DEFINE_TYPE: coap_pki_define_t = coap_pki_define_t_COAP_PKI_KEY_DEF_PKCS11_RPK; } diff --git a/libcoap/src/crypto/psk/client.rs b/libcoap/src/crypto/psk/client.rs index e8b09d1a..ec907be9 100644 --- a/libcoap/src/crypto/psk/client.rs +++ b/libcoap/src/crypto/psk/client.rs @@ -7,20 +7,22 @@ * See the README as well as the LICENSE file for more information. */ -use crate::crypto::psk::key::PskKey; -use crate::error::SessionCreationError; -use crate::session::CoapClientSession; -use crate::types::CoapAddress; -use crate::CoapContext; +use std::{ + cell::RefCell, + ffi::{c_char, c_void, CString, NulError}, + fmt::Debug, + ptr::NonNull, + rc::{Rc, Weak}, +}; + use libcoap_sys::{ coap_dtls_cpsk_info_t, coap_dtls_cpsk_t, coap_new_client_session_psk2, coap_proto_t, coap_session_t, coap_str_const_t, COAP_DTLS_CPSK_SETUP_VERSION, }; -use std::cell::RefCell; -use std::ffi::{c_char, c_void, CString, NulError}; -use std::fmt::Debug; -use std::ptr::NonNull; -use std::rc::{Rc, Weak}; + +use crate::{ + crypto::psk::key::PskKey, error::SessionCreationError, session::CoapClientSession, types::CoapAddress, CoapContext, +}; /// Builder for a client-side DTLS encryption context for use with pre-shared keys (PSK). #[derive(Debug)] @@ -41,9 +43,7 @@ impl<'a> ClientPskContextBuilder<'a> { raw_cfg: Box::new(coap_dtls_cpsk_t { version: COAP_DTLS_CPSK_SETUP_VERSION as u8, reserved: Default::default(), - #[cfg(dtls_ec_jpake_support)] ec_jpake: 0, - #[cfg(dtls_cid_support)] use_cid: 0, validate_ih_call_back: None, ih_call_back_arg: std::ptr::null_mut(), @@ -96,10 +96,12 @@ impl ClientPskContextBuilder<'_> { /// Enables or disables support for EC JPAKE ([RFC 8236](https://datatracker.ietf.org/doc/html/rfc8236)) /// key exchanges in (D)TLS. /// + /// Note: At the time of writing (based on libcoap 4.3.5), this is only supported on MbedTLS, + /// enabling EC JPAKE on other DTLS backends has no effect. + /// /// # Implementation details (informative, not covered by semver guarantees) /// /// Equivalent to setting `ec_jpake` in the underlying [`coap_dtls_cpsk_t`] structure. - #[cfg(dtls_ec_jpake_support)] pub fn ec_jpake(mut self, ec_jpake: bool) -> Self { self.ctx.raw_cfg.ec_jpake = ec_jpake.into(); self @@ -110,7 +112,7 @@ impl ClientPskContextBuilder<'_> { /// # Implementation details (informative, not covered by semver guarantees) /// /// Equivalent to setting `use_cid` in the underlying [`coap_dtls_cpsk_t`] structure. - #[cfg(dtls_cid_support)] + #[cfg(feature = "dtls-cid")] pub fn use_cid(mut self, use_cid: bool) -> Self { self.ctx.raw_cfg.use_cid = use_cid.into(); self diff --git a/libcoap/src/crypto/psk/key.rs b/libcoap/src/crypto/psk/key.rs index eebf94de..11503451 100644 --- a/libcoap/src/crypto/psk/key.rs +++ b/libcoap/src/crypto/psk/key.rs @@ -7,10 +7,9 @@ * See the README as well as the LICENSE file for more information. */ +use std::{borrow::Cow, marker::PhantomData, ptr::NonNull}; + use libcoap_sys::{coap_bin_const_t, coap_dtls_cpsk_info_t, coap_dtls_spsk_info_t}; -use std::borrow::Cow; -use std::marker::PhantomData; -use std::ptr::NonNull; /// A pre-shared DTLS key. #[derive(Debug, Clone)] diff --git a/libcoap/src/crypto/psk/server.rs b/libcoap/src/crypto/psk/server.rs index eeb61bb7..5f281e83 100644 --- a/libcoap/src/crypto/psk/server.rs +++ b/libcoap/src/crypto/psk/server.rs @@ -7,22 +7,24 @@ * See the README as well as the LICENSE file for more information. */ -use crate::crypto::psk::key::PskKey; -use crate::error::ContextConfigurationError; -use crate::session::CoapServerSession; +use std::{ + borrow::Borrow, + cell::RefCell, + collections::{BTreeMap, HashMap}, + ffi::{c_void, CStr}, + fmt::Debug, + hash::Hash, + os::raw::c_char, + ptr::NonNull, + rc::{Rc, Weak}, +}; + use libcoap_sys::{ coap_bin_const_t, coap_context_set_psk2, coap_context_t, coap_dtls_spsk_info_t, coap_dtls_spsk_t, coap_session_t, COAP_DTLS_SPSK_SETUP_VERSION, }; -use std::borrow::Borrow; -use std::cell::RefCell; -use std::collections::{BTreeMap, HashMap}; -use std::ffi::{c_void, CStr}; -use std::fmt::Debug; -use std::hash::Hash; -use std::os::raw::c_char; -use std::ptr::NonNull; -use std::rc::{Rc, Weak}; + +use crate::{crypto::psk::key::PskKey, error::ContextConfigurationError, session::CoapServerSession}; /// Builder for a server-side DTLS encryption context for use with pre-shared keys (PSK). #[derive(Debug)] @@ -46,7 +48,6 @@ impl<'a> ServerPskContextBuilder<'a> { raw_cfg: Box::new(coap_dtls_spsk_t { version: COAP_DTLS_SPSK_SETUP_VERSION as u8, reserved: Default::default(), - #[cfg(dtls_ec_jpake_support)] ec_jpake: 0, validate_id_call_back: None, id_call_back_arg: std::ptr::null_mut(), @@ -105,10 +106,12 @@ impl ServerPskContextBuilder<'_> { /// Enables or disables support for EC JPAKE ([RFC 8236](https://datatracker.ietf.org/doc/html/rfc8236)) /// key exchanges in (D)TLS. /// + /// Note: At the time of writing (based on libcoap 4.3.5), this is only supported on MbedTLS, + /// enabling EC JPAKE on other DTLS backends has no effect. + /// /// # Implementation details (informative, not covered by semver guarantees) /// /// Equivalent to setting `ec_jpake` in the underlying [`coap_dtls_spsk_t`] structure. - #[cfg(dtls_ec_jpake_support)] pub fn ec_jpake(mut self, ec_jpake: bool) -> Self { self.ctx.raw_cfg.ec_jpake = ec_jpake.into(); self diff --git a/libcoap/src/error.rs b/libcoap/src/error.rs index 14fcf6f5..6c1630dd 100644 --- a/libcoap/src/error.rs +++ b/libcoap/src/error.rs @@ -9,9 +9,7 @@ //! Error types -use std::ffi::NulError; -use std::string::FromUtf8Error; -use std::sync::PoisonError; +use std::{ffi::NulError, string::FromUtf8Error, sync::PoisonError}; use thiserror::Error; diff --git a/libcoap/src/event.rs b/libcoap/src/event.rs index 07573119..fb716763 100644 --- a/libcoap/src/event.rs +++ b/libcoap/src/event.rs @@ -11,13 +11,15 @@ use std::fmt::Debug; -use libcoap_sys::{coap_event_t, coap_session_get_context, coap_session_t}; -use libcoap_sys::{coap_session_get_type, coap_session_type_t}; +use libcoap_sys::{ + coap_event_t, coap_event_t_COAP_EVENT_SERVER_SESSION_NEW, coap_event_t_COAP_EVENT_TCP_CONNECTED, + coap_session_get_context, coap_session_get_type, coap_session_t, coap_session_type_t_COAP_SESSION_TYPE_SERVER, +}; -use crate::context::CoapContext; -use crate::session::CoapSession; - -use crate::session::CoapServerSession; +use crate::{ + context::CoapContext, + session::{CoapServerSession, CoapSession}, +}; /// Trait for CoAP event handlers. /// @@ -147,7 +149,6 @@ pub trait CoapEventHandler: Debug { #[allow(unused_variables)] fn handle_oscore_decode_error(&mut self, session: &mut CoapSession) {} - /// Handle an oversized WebSocket packet event. #[allow(unused_variables)] fn handle_ws_packet_size(&mut self, session: &mut CoapSession) {} @@ -171,9 +172,9 @@ pub trait CoapEventHandler: Debug { pub(crate) unsafe extern "C" fn event_handler_callback(raw_session: *mut coap_session_t, event: coap_event_t) -> i32 { let raw_session_type = coap_session_get_type(raw_session); - let session: CoapSession = if event == coap_event_t::COAP_EVENT_SERVER_SESSION_NEW - || (event == coap_event_t::COAP_EVENT_TCP_CONNECTED - && raw_session_type == coap_session_type_t::COAP_SESSION_TYPE_SERVER) + let session: CoapSession = if event == coap_event_t_COAP_EVENT_SERVER_SESSION_NEW + || (event == coap_event_t_COAP_EVENT_TCP_CONNECTED + && raw_session_type == coap_session_type_t_COAP_SESSION_TYPE_SERVER) { CoapServerSession::initialize_raw(raw_session).into() } else { diff --git a/libcoap/src/lib.rs b/libcoap/src/lib.rs index 9dcda7ab..bad62a8a 100644 --- a/libcoap/src/lib.rs +++ b/libcoap/src/lib.rs @@ -36,16 +36,20 @@ //! - [x] Notifying observers as a server //! //! # Building -//! libcoap-rs can be linked to either an included version of libcoap or to a version provided by -//! the environment. -//! By default, it will use the vendored version, which can be disabled by disabling the default -//! feature `vendored`. +//! libcoap-rs is based on libcoap-sys, which provide many different ways to obtain and link against +//! a system-provided or vendored version of the libcoap C library. //! -//! In order to use DTLS, a DTLS library must be chosen, see the later section on using -//! cryptography for more information. +//! Refer to [its documentation](https://docs.rs/libcoap-sys) for detailed instructions on how to +//! build libcoap-sys as well as this library. //! -//! Some (but not all) of the available DTLS libraries may also be vendored using the -//! `dtls_[LIBRARY]_vendored` feature. +//! Most of these instructions can be applied to libcoap-rs directly, although libcoap-rs does +//! abstract away some of the features. +//! +//! For your convenience, libcoap-rs "re-exports" some features that do not have any influence on +//! the safe wrapper, but may have to be set in libcoap-sys to enable building (e.g., the +//! `dtls--sys` features). +//! This way, you don't need to add libcoap-sys as a dependency yourself, and may just enable the +//! feature in this crate instead. //! //! ## Building on the ESP32 //! @@ -207,7 +211,7 @@ pub use event::CoapEventHandler; pub use resource::{CoapRequestHandler, CoapResource}; mod context; -#[cfg(dtls)] +#[cfg(feature = "dtls")] pub mod crypto; pub mod error; mod event; diff --git a/libcoap/src/mem.rs b/libcoap/src/mem.rs index 64097377..5fb078d4 100644 --- a/libcoap/src/mem.rs +++ b/libcoap/src/mem.rs @@ -9,11 +9,13 @@ //! Code related to memory handling, especially for passing objects through FFI -use std::cell::{Ref, RefCell, RefMut}; -use std::ffi::c_void; -use std::fmt::{Debug, Formatter}; -use std::ops::{Deref, DerefMut}; -use std::rc::{Rc, Weak}; +use std::{ + cell::{Ref, RefCell, RefMut}, + ffi::c_void, + fmt::{Debug, Formatter}, + ops::{Deref, DerefMut}, + rc::{Rc, Weak}, +}; /// Trait implemented by libcoap wrapper structs that contain an inner value that may be dropped /// exclusively, i.e., that can be dropped with the additional check that there are no further diff --git a/libcoap/src/message/mod.rs b/libcoap/src/message/mod.rs index fd31b57c..2c294f48 100644 --- a/libcoap/src/message/mod.rs +++ b/libcoap/src/message/mod.rs @@ -15,10 +15,7 @@ //! process of creating requests and responses and setting the appropriate options ([CoapRequest] //! and [CoapResponse]). -use std::{ffi::c_void, mem::MaybeUninit, slice::Iter}; -use std::fmt::Write; - -use num_traits::FromPrimitive; +use std::{ffi::c_void, fmt::Write, mem::MaybeUninit, slice::Iter}; use libcoap_sys::{ coap_add_data, coap_add_data_large_request, coap_add_optlist_pdu, coap_add_token, coap_delete_optlist, @@ -27,23 +24,23 @@ use libcoap_sys::{ coap_pdu_get_mid, coap_pdu_get_token, coap_pdu_get_type, coap_pdu_init, coap_pdu_set_code, coap_pdu_set_type, coap_pdu_t, coap_session_t, }; +use num_traits::FromPrimitive; pub use request::CoapRequest; pub use response::CoapResponse; use crate::{ + context::ensure_coap_started, error::{MessageConversionError, OptionValueError}, protocol::{ - Block, CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionNum, CoapOptionType, ContentFormat, ETag, - HopLimit, MaxAge, NoResponse, Observe, ProxyScheme, ProxyUri, Size, UriHost, UriPath, UriPort, UriQuery, + Block, CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionNum, CoapOptionType, ContentFormat, ETag, Echo, + HopLimit, MaxAge, NoResponse, Observe, Oscore, ProxyScheme, ProxyUri, RequestTag, Size, UriHost, UriPath, + UriPort, UriQuery, }, session::CoapSessionCommon, - types::CoapMessageId, -}; -use crate::context::ensure_coap_started; -use crate::protocol::{Echo, Oscore, RequestTag}; -use crate::types::{ - decode_var_len_u16, decode_var_len_u32, decode_var_len_u8, encode_var_len_u16, encode_var_len_u32, - encode_var_len_u8, + types::{ + decode_var_len_u16, decode_var_len_u32, decode_var_len_u8, encode_var_len_u16, encode_var_len_u32, + encode_var_len_u8, CoapMessageId, + }, }; pub mod request; diff --git a/libcoap/src/message/request.rs b/libcoap/src/message/request.rs index 862984f8..85854872 100644 --- a/libcoap/src/message/request.rs +++ b/libcoap/src/message/request.rs @@ -10,17 +10,15 @@ use std::str::FromStr; use crate::{ - error::{MessageConversionError, MessageTypeError}, - message::{CoapMessage, CoapMessageCommon, CoapOption}, + error::{MessageConversionError, MessageTypeError, OptionValueError}, + message::{construct_path_string, construct_query_string, CoapMessage, CoapMessageCommon, CoapOption}, protocol::{ CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionType, CoapRequestCode, ContentFormat, ETag, HopLimit, NoResponse, Observe, }, + session::CoapSessionCommon, types::{CoapUri, CoapUriScheme}, }; -use crate::error::OptionValueError; -use crate::message::{construct_path_string, construct_query_string}; -use crate::session::CoapSessionCommon; /// Representation of a CoAP request message. /// diff --git a/libcoap/src/message/response.rs b/libcoap/src/message/response.rs index c3a0cc9a..844c3dc5 100644 --- a/libcoap/src/message/response.rs +++ b/libcoap/src/message/response.rs @@ -7,12 +7,14 @@ * See the README as well as the LICENSE file for more information. */ -use crate::error::{MessageConversionError, MessageTypeError, OptionValueError}; -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::{ + error::{MessageConversionError, MessageTypeError, OptionValueError}, + message::{construct_path_string, construct_query_string, CoapMessage, CoapMessageCommon, CoapOption}, + protocol::{ + CoapMessageCode, CoapMessageType, CoapOptionType, CoapResponseCode, ContentFormat, ETag, Echo, MaxAge, Observe, + }, + types::CoapUri, }; -use crate::types::CoapUri; #[derive(Debug, Clone, Eq, PartialEq)] pub struct CoapResponse { diff --git a/libcoap/src/prng.rs b/libcoap/src/prng.rs index 65f3f54e..53aa2354 100644 --- a/libcoap/src/prng.rs +++ b/libcoap/src/prng.rs @@ -15,22 +15,20 @@ //! [rand] crate that allow using the libcoap PRNG as a [rand::Rng] or setting the libcoap PRNG to //! an existing [rand::Rng]. -use std::ffi::{c_uint, c_void}; #[cfg(feature = "rand")] use std::ffi::c_int; -use std::sync::Mutex; +use std::{ + ffi::{c_uint, c_void}, + sync::Mutex, +}; #[cfg(feature = "rand")] -use libc::size_t; -#[cfg(feature = "rand")] -use rand::{CryptoRng, RngCore}; - +use libcoap_sys::coap_set_prng; use libcoap_sys::{coap_prng, coap_prng_init}; #[cfg(feature = "rand")] -use libcoap_sys::coap_set_prng; +use rand::{CryptoRng, RngCore}; -use crate::context::ensure_coap_started; -use crate::error::RngError; +use crate::{context::ensure_coap_started, error::RngError}; // TODO If we can assert that libcoap's own thread-safety features are enabled at some point, we // don't need these mutexes. @@ -186,7 +184,7 @@ pub fn set_coap_prng(rng: RNG) /// This function is intended as a [libcoap_sys::coap_rand_func_t], therefore `out` should be valid /// and point to the start of an area of memory that can be filled with `len` bytes. #[cfg(feature = "rand")] -unsafe extern "C" fn prng_callback(out: *mut c_void, len: size_t) -> c_int { +unsafe extern "C" fn prng_callback(out: *mut c_void, len: usize) -> c_int { let out_slice = std::slice::from_raw_parts_mut(out as *mut u8, len); match COAP_RNG_FN_MUTEX.lock() { Ok(mut rng_fn) => rng_fn diff --git a/libcoap/src/protocol.rs b/libcoap/src/protocol.rs index 6bb8f926..7b9b27d0 100644 --- a/libcoap/src/protocol.rs +++ b/libcoap/src/protocol.rs @@ -14,31 +14,49 @@ use std::{ fmt::{Display, Formatter}, }; -use num_derive::FromPrimitive; -use num_traits::FromPrimitive; - use libcoap_sys::{ + coap_option_num_t, coap_pdu_code_t, coap_pdu_code_t_COAP_EMPTY_CODE, coap_pdu_code_t_COAP_REQUEST_CODE_DELETE, + coap_pdu_code_t_COAP_REQUEST_CODE_FETCH, coap_pdu_code_t_COAP_REQUEST_CODE_GET, + coap_pdu_code_t_COAP_REQUEST_CODE_IPATCH, coap_pdu_code_t_COAP_REQUEST_CODE_PATCH, + coap_pdu_code_t_COAP_REQUEST_CODE_POST, coap_pdu_code_t_COAP_REQUEST_CODE_PUT, + coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_GATEWAY, coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_OPTION, + coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_REQUEST, coap_pdu_code_t_COAP_RESPONSE_CODE_CHANGED, + coap_pdu_code_t_COAP_RESPONSE_CODE_CONFLICT, coap_pdu_code_t_COAP_RESPONSE_CODE_CONTENT, + coap_pdu_code_t_COAP_RESPONSE_CODE_CONTINUE, coap_pdu_code_t_COAP_RESPONSE_CODE_CREATED, + coap_pdu_code_t_COAP_RESPONSE_CODE_DELETED, coap_pdu_code_t_COAP_RESPONSE_CODE_FORBIDDEN, + coap_pdu_code_t_COAP_RESPONSE_CODE_GATEWAY_TIMEOUT, coap_pdu_code_t_COAP_RESPONSE_CODE_HOP_LIMIT_REACHED, + coap_pdu_code_t_COAP_RESPONSE_CODE_INCOMPLETE, coap_pdu_code_t_COAP_RESPONSE_CODE_INTERNAL_ERROR, + coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_ACCEPTABLE, coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_ALLOWED, + coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_FOUND, coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_IMPLEMENTED, + coap_pdu_code_t_COAP_RESPONSE_CODE_PRECONDITION_FAILED, coap_pdu_code_t_COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED, + coap_pdu_code_t_COAP_RESPONSE_CODE_REQUEST_TOO_LARGE, coap_pdu_code_t_COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE, + coap_pdu_code_t_COAP_RESPONSE_CODE_TOO_MANY_REQUESTS, coap_pdu_code_t_COAP_RESPONSE_CODE_UNAUTHORIZED, + coap_pdu_code_t_COAP_RESPONSE_CODE_UNPROCESSABLE, coap_pdu_code_t_COAP_RESPONSE_CODE_UNSUPPORTED_CONTENT_FORMAT, + coap_pdu_code_t_COAP_RESPONSE_CODE_VALID, coap_pdu_type_t, coap_pdu_type_t_COAP_MESSAGE_ACK, + coap_pdu_type_t_COAP_MESSAGE_CON, coap_pdu_type_t_COAP_MESSAGE_NON, coap_pdu_type_t_COAP_MESSAGE_RST, + coap_request_t, coap_request_t_COAP_REQUEST_DELETE, coap_request_t_COAP_REQUEST_FETCH, + coap_request_t_COAP_REQUEST_GET, coap_request_t_COAP_REQUEST_IPATCH, coap_request_t_COAP_REQUEST_PATCH, + coap_request_t_COAP_REQUEST_POST, coap_request_t_COAP_REQUEST_PUT, coap_response_phrase, COAP_MEDIATYPE_APPLICATION_ACE_CBOR, COAP_MEDIATYPE_APPLICATION_CBOR, COAP_MEDIATYPE_APPLICATION_COAP_GROUP_JSON, - COAP_MEDIATYPE_APPLICATION_COSE_ENCRYPT, - COAP_MEDIATYPE_APPLICATION_COSE_ENCRYPT0, COAP_MEDIATYPE_APPLICATION_COSE_KEY, COAP_MEDIATYPE_APPLICATION_COSE_KEY_SET, COAP_MEDIATYPE_APPLICATION_COSE_MAC, - COAP_MEDIATYPE_APPLICATION_COSE_MAC0, COAP_MEDIATYPE_APPLICATION_COSE_SIGN, - COAP_MEDIATYPE_APPLICATION_COSE_SIGN1, COAP_MEDIATYPE_APPLICATION_CWT, - COAP_MEDIATYPE_APPLICATION_DOTS_CBOR, COAP_MEDIATYPE_APPLICATION_EXI, COAP_MEDIATYPE_APPLICATION_JSON, - COAP_MEDIATYPE_APPLICATION_LINK_FORMAT, COAP_MEDIATYPE_APPLICATION_MB_CBOR_SEQ, COAP_MEDIATYPE_APPLICATION_OCTET_STREAM, - COAP_MEDIATYPE_APPLICATION_OSCORE, COAP_MEDIATYPE_APPLICATION_RDF_XML, COAP_MEDIATYPE_APPLICATION_SENML_CBOR, - COAP_MEDIATYPE_APPLICATION_SENML_EXI, COAP_MEDIATYPE_APPLICATION_SENML_JSON, - COAP_MEDIATYPE_APPLICATION_SENML_XML, COAP_MEDIATYPE_APPLICATION_SENSML_CBOR, COAP_MEDIATYPE_APPLICATION_SENSML_EXI, - COAP_MEDIATYPE_APPLICATION_SENSML_JSON, COAP_MEDIATYPE_APPLICATION_SENSML_XML, COAP_MEDIATYPE_APPLICATION_XML, - COAP_MEDIATYPE_TEXT_PLAIN, COAP_OPTION_ACCEPT, - COAP_OPTION_BLOCK1, COAP_OPTION_BLOCK2, - COAP_OPTION_CONTENT_FORMAT, COAP_OPTION_ECHO, COAP_OPTION_ETAG, - COAP_OPTION_HOP_LIMIT, COAP_OPTION_IF_MATCH, COAP_OPTION_IF_NONE_MATCH, COAP_OPTION_LOCATION_PATH, COAP_OPTION_LOCATION_QUERY, - COAP_OPTION_MAXAGE, COAP_OPTION_NORESPONSE, coap_option_num_t, COAP_OPTION_OBSERVE, - COAP_OPTION_OSCORE, COAP_OPTION_PROXY_SCHEME, COAP_OPTION_PROXY_URI, COAP_OPTION_Q_BLOCK1, + COAP_MEDIATYPE_APPLICATION_COSE_ENCRYPT, COAP_MEDIATYPE_APPLICATION_COSE_ENCRYPT0, + COAP_MEDIATYPE_APPLICATION_COSE_KEY, COAP_MEDIATYPE_APPLICATION_COSE_KEY_SET, COAP_MEDIATYPE_APPLICATION_COSE_MAC, + COAP_MEDIATYPE_APPLICATION_COSE_MAC0, COAP_MEDIATYPE_APPLICATION_COSE_SIGN, COAP_MEDIATYPE_APPLICATION_COSE_SIGN1, + COAP_MEDIATYPE_APPLICATION_CWT, COAP_MEDIATYPE_APPLICATION_DOTS_CBOR, COAP_MEDIATYPE_APPLICATION_EXI, + COAP_MEDIATYPE_APPLICATION_JSON, COAP_MEDIATYPE_APPLICATION_LINK_FORMAT, COAP_MEDIATYPE_APPLICATION_MB_CBOR_SEQ, + COAP_MEDIATYPE_APPLICATION_OCTET_STREAM, COAP_MEDIATYPE_APPLICATION_OSCORE, COAP_MEDIATYPE_APPLICATION_RDF_XML, + COAP_MEDIATYPE_APPLICATION_SENML_CBOR, COAP_MEDIATYPE_APPLICATION_SENML_EXI, COAP_MEDIATYPE_APPLICATION_SENML_JSON, + COAP_MEDIATYPE_APPLICATION_SENML_XML, COAP_MEDIATYPE_APPLICATION_SENSML_CBOR, + COAP_MEDIATYPE_APPLICATION_SENSML_EXI, COAP_MEDIATYPE_APPLICATION_SENSML_JSON, + COAP_MEDIATYPE_APPLICATION_SENSML_XML, COAP_MEDIATYPE_APPLICATION_XML, COAP_MEDIATYPE_TEXT_PLAIN, + COAP_OPTION_ACCEPT, COAP_OPTION_BLOCK1, COAP_OPTION_BLOCK2, COAP_OPTION_CONTENT_FORMAT, COAP_OPTION_ECHO, + COAP_OPTION_ETAG, COAP_OPTION_HOP_LIMIT, COAP_OPTION_IF_MATCH, COAP_OPTION_IF_NONE_MATCH, + COAP_OPTION_LOCATION_PATH, COAP_OPTION_LOCATION_QUERY, COAP_OPTION_MAXAGE, COAP_OPTION_NORESPONSE, + COAP_OPTION_OBSERVE, COAP_OPTION_OSCORE, COAP_OPTION_PROXY_SCHEME, COAP_OPTION_PROXY_URI, COAP_OPTION_Q_BLOCK1, COAP_OPTION_Q_BLOCK2, COAP_OPTION_RTAG, COAP_OPTION_SIZE1, COAP_OPTION_SIZE2, COAP_OPTION_URI_HOST, - COAP_OPTION_URI_PATH, COAP_OPTION_URI_PORT, COAP_OPTION_URI_QUERY, coap_pdu_code_t, coap_pdu_type_t, - coap_pdu_type_t::{COAP_MESSAGE_ACK, COAP_MESSAGE_CON, COAP_MESSAGE_NON, COAP_MESSAGE_RST}, coap_request_t, coap_response_phrase, + COAP_OPTION_URI_PATH, COAP_OPTION_URI_PORT, COAP_OPTION_URI_QUERY, }; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; use crate::error::{MessageCodeError, UnknownOptionError}; @@ -286,7 +304,7 @@ impl CoapMessageCode { /// [coap_pdu_t](libcoap_sys::coap_pdu_t). pub fn to_raw_pdu_code(self) -> coap_pdu_code_t { match self { - CoapMessageCode::Empty => coap_pdu_code_t::COAP_EMPTY_CODE, + CoapMessageCode::Empty => coap_pdu_code_t_COAP_EMPTY_CODE, CoapMessageCode::Request(req) => req.to_raw_pdu_code(), CoapMessageCode::Response(rsp) => rsp.to_raw_pdu_code(), } @@ -309,8 +327,11 @@ impl TryFrom for CoapMessageCode { type Error = MessageCodeError; fn try_from(code: coap_pdu_code_t) -> Result { + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] match code { - coap_pdu_code_t::COAP_EMPTY_CODE => Ok(CoapMessageCode::Empty), + coap_pdu_code_t_COAP_EMPTY_CODE => Ok(CoapMessageCode::Empty), code => CoapRequestCode::try_from(code) .map(CoapMessageCode::Request) .or_else(|_| CoapResponseCode::try_from(code).map(CoapMessageCode::Response)), @@ -326,30 +347,30 @@ impl TryFrom for CoapMessageCode { #[non_exhaustive] #[derive(FromPrimitive, Clone, Copy, Eq, PartialEq, Hash, Debug)] pub enum CoapRequestCode { - Get = coap_pdu_code_t::COAP_REQUEST_CODE_GET as u8, - Put = coap_pdu_code_t::COAP_REQUEST_CODE_PUT as u8, - Delete = coap_pdu_code_t::COAP_REQUEST_CODE_DELETE as u8, - Post = coap_pdu_code_t::COAP_REQUEST_CODE_POST as u8, - Fetch = coap_pdu_code_t::COAP_REQUEST_CODE_FETCH as u8, - IPatch = coap_pdu_code_t::COAP_REQUEST_CODE_IPATCH as u8, - Patch = coap_pdu_code_t::COAP_REQUEST_CODE_PATCH as u8, + Get = coap_pdu_code_t_COAP_REQUEST_CODE_GET as u8, + Put = coap_pdu_code_t_COAP_REQUEST_CODE_PUT as u8, + Delete = coap_pdu_code_t_COAP_REQUEST_CODE_DELETE as u8, + Post = coap_pdu_code_t_COAP_REQUEST_CODE_POST as u8, + Fetch = coap_pdu_code_t_COAP_REQUEST_CODE_FETCH as u8, + IPatch = coap_pdu_code_t_COAP_REQUEST_CODE_IPATCH as u8, + Patch = coap_pdu_code_t_COAP_REQUEST_CODE_PATCH as u8, } impl CoapRequestCode { /// Returns the [coap_request_t](coap_request_t) corresponding to this request code. /// - /// Note that this is *not* the code that should be set inside of a [coap_pdu_t](libcoap_sys::coap_pdu_t), + /// Note that this is *not* the code that should be set inside a [coap_pdu_t](libcoap_sys::coap_pdu_t), /// but a value used internally by the libcoap C library. See [to_raw_pdu_code()](CoapRequestCode::to_raw_pdu_code()) /// for the standardized value used in messages. pub fn to_raw_request(self) -> coap_request_t { match self { - CoapRequestCode::Get => coap_request_t::COAP_REQUEST_GET, - CoapRequestCode::Put => coap_request_t::COAP_REQUEST_PUT, - CoapRequestCode::Delete => coap_request_t::COAP_REQUEST_FETCH, - CoapRequestCode::Post => coap_request_t::COAP_REQUEST_POST, - CoapRequestCode::Fetch => coap_request_t::COAP_REQUEST_FETCH, - CoapRequestCode::IPatch => coap_request_t::COAP_REQUEST_IPATCH, - CoapRequestCode::Patch => coap_request_t::COAP_REQUEST_PATCH, + CoapRequestCode::Get => coap_request_t_COAP_REQUEST_GET, + CoapRequestCode::Put => coap_request_t_COAP_REQUEST_PUT, + CoapRequestCode::Delete => coap_request_t_COAP_REQUEST_DELETE, + CoapRequestCode::Post => coap_request_t_COAP_REQUEST_POST, + CoapRequestCode::Fetch => coap_request_t_COAP_REQUEST_FETCH, + CoapRequestCode::IPatch => coap_request_t_COAP_REQUEST_IPATCH, + CoapRequestCode::Patch => coap_request_t_COAP_REQUEST_PATCH, } } @@ -357,28 +378,13 @@ impl CoapRequestCode { /// request code. pub fn to_raw_pdu_code(self) -> coap_pdu_code_t { match self { - CoapRequestCode::Get => coap_pdu_code_t::COAP_REQUEST_CODE_GET, - CoapRequestCode::Put => coap_pdu_code_t::COAP_REQUEST_CODE_PUT, - CoapRequestCode::Delete => coap_pdu_code_t::COAP_REQUEST_CODE_FETCH, - CoapRequestCode::Post => coap_pdu_code_t::COAP_REQUEST_CODE_POST, - CoapRequestCode::Fetch => coap_pdu_code_t::COAP_REQUEST_CODE_FETCH, - CoapRequestCode::IPatch => coap_pdu_code_t::COAP_REQUEST_CODE_IPATCH, - CoapRequestCode::Patch => coap_pdu_code_t::COAP_REQUEST_CODE_PATCH, - } - } -} - -impl From for CoapRequestCode { - fn from(req: coap_request_t) -> Self { - match req { - coap_request_t::COAP_REQUEST_GET => CoapRequestCode::Get, - coap_request_t::COAP_REQUEST_POST => CoapRequestCode::Post, - coap_request_t::COAP_REQUEST_PUT => CoapRequestCode::Put, - coap_request_t::COAP_REQUEST_DELETE => CoapRequestCode::Delete, - coap_request_t::COAP_REQUEST_FETCH => CoapRequestCode::Fetch, - coap_request_t::COAP_REQUEST_PATCH => CoapRequestCode::Patch, - coap_request_t::COAP_REQUEST_IPATCH => CoapRequestCode::IPatch, - _ => panic!("unknown request type"), + CoapRequestCode::Get => coap_pdu_code_t_COAP_REQUEST_CODE_GET, + CoapRequestCode::Put => coap_pdu_code_t_COAP_REQUEST_CODE_PUT, + CoapRequestCode::Delete => coap_pdu_code_t_COAP_REQUEST_CODE_FETCH, + CoapRequestCode::Post => coap_pdu_code_t_COAP_REQUEST_CODE_POST, + CoapRequestCode::Fetch => coap_pdu_code_t_COAP_REQUEST_CODE_FETCH, + CoapRequestCode::IPatch => coap_pdu_code_t_COAP_REQUEST_CODE_IPATCH, + CoapRequestCode::Patch => coap_pdu_code_t_COAP_REQUEST_CODE_PATCH, } } } @@ -386,8 +392,8 @@ impl From for CoapRequestCode { impl TryFrom for CoapRequestCode { type Error = MessageCodeError; - fn try_from(req: coap_pdu_code_t) -> Result { - ::from_u32(req as u32).ok_or(MessageCodeError::NotARequestCode) + fn try_from(value: coap_pdu_code_t) -> Result { + ::from_u32(value as u32).ok_or(MessageCodeError::NotARequestCode) } } @@ -399,33 +405,33 @@ impl TryFrom for CoapRequestCode { #[non_exhaustive] #[derive(Clone, Copy, FromPrimitive, Debug, Eq, PartialEq, Hash)] pub enum CoapResponseCode { - Content = coap_pdu_code_t::COAP_RESPONSE_CODE_CONTENT as u8, - BadGateway = coap_pdu_code_t::COAP_RESPONSE_CODE_BAD_GATEWAY as u8, - Continue = coap_pdu_code_t::COAP_RESPONSE_CODE_CONTINUE as u8, - Conflict = coap_pdu_code_t::COAP_RESPONSE_CODE_CONFLICT as u8, - BadRequest = coap_pdu_code_t::COAP_RESPONSE_CODE_BAD_REQUEST as u8, - BadOption = coap_pdu_code_t::COAP_RESPONSE_CODE_BAD_OPTION as u8, - Changed = coap_pdu_code_t::COAP_RESPONSE_CODE_CHANGED as u8, - Created = coap_pdu_code_t::COAP_RESPONSE_CODE_CREATED as u8, - Deleted = coap_pdu_code_t::COAP_RESPONSE_CODE_DELETED as u8, - Forbidden = coap_pdu_code_t::COAP_RESPONSE_CODE_FORBIDDEN as u8, - GatewayTimeout = coap_pdu_code_t::COAP_RESPONSE_CODE_GATEWAY_TIMEOUT as u8, - HopLimitReached = coap_pdu_code_t::COAP_RESPONSE_CODE_HOP_LIMIT_REACHED as u8, - Incomplete = coap_pdu_code_t::COAP_RESPONSE_CODE_INCOMPLETE as u8, - InternalError = coap_pdu_code_t::COAP_RESPONSE_CODE_INTERNAL_ERROR as u8, - NotAcceptable = coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_ACCEPTABLE as u8, - NotAllowed = coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_ALLOWED as u8, - NotFound = coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_FOUND as u8, - NotImplemented = coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_IMPLEMENTED as u8, - PreconditionFailed = coap_pdu_code_t::COAP_RESPONSE_CODE_PRECONDITION_FAILED as u8, - ProxyingNotSupported = coap_pdu_code_t::COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED as u8, - RequestTooLarge = coap_pdu_code_t::COAP_RESPONSE_CODE_REQUEST_TOO_LARGE as u8, - ServiceUnavailable = coap_pdu_code_t::COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE as u8, - TooManyRequests = coap_pdu_code_t::COAP_RESPONSE_CODE_TOO_MANY_REQUESTS as u8, - Unauthorized = coap_pdu_code_t::COAP_RESPONSE_CODE_UNAUTHORIZED as u8, - Unprocessable = coap_pdu_code_t::COAP_RESPONSE_CODE_UNPROCESSABLE as u8, - UnsupportedContentFormat = coap_pdu_code_t::COAP_RESPONSE_CODE_UNSUPPORTED_CONTENT_FORMAT as u8, - Valid = coap_pdu_code_t::COAP_RESPONSE_CODE_VALID as u8, + Content = coap_pdu_code_t_COAP_RESPONSE_CODE_CONTENT as u8, + BadGateway = coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_GATEWAY as u8, + Continue = coap_pdu_code_t_COAP_RESPONSE_CODE_CONTINUE as u8, + Conflict = coap_pdu_code_t_COAP_RESPONSE_CODE_CONFLICT as u8, + BadRequest = coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_REQUEST as u8, + BadOption = coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_OPTION as u8, + Changed = coap_pdu_code_t_COAP_RESPONSE_CODE_CHANGED as u8, + Created = coap_pdu_code_t_COAP_RESPONSE_CODE_CREATED as u8, + Deleted = coap_pdu_code_t_COAP_RESPONSE_CODE_DELETED as u8, + Forbidden = coap_pdu_code_t_COAP_RESPONSE_CODE_FORBIDDEN as u8, + GatewayTimeout = coap_pdu_code_t_COAP_RESPONSE_CODE_GATEWAY_TIMEOUT as u8, + HopLimitReached = coap_pdu_code_t_COAP_RESPONSE_CODE_HOP_LIMIT_REACHED as u8, + Incomplete = coap_pdu_code_t_COAP_RESPONSE_CODE_INCOMPLETE as u8, + InternalError = coap_pdu_code_t_COAP_RESPONSE_CODE_INTERNAL_ERROR as u8, + NotAcceptable = coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_ACCEPTABLE as u8, + NotAllowed = coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_ALLOWED as u8, + NotFound = coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_FOUND as u8, + NotImplemented = coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_IMPLEMENTED as u8, + PreconditionFailed = coap_pdu_code_t_COAP_RESPONSE_CODE_PRECONDITION_FAILED as u8, + ProxyingNotSupported = coap_pdu_code_t_COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED as u8, + RequestTooLarge = coap_pdu_code_t_COAP_RESPONSE_CODE_REQUEST_TOO_LARGE as u8, + ServiceUnavailable = coap_pdu_code_t_COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE as u8, + TooManyRequests = coap_pdu_code_t_COAP_RESPONSE_CODE_TOO_MANY_REQUESTS as u8, + Unauthorized = coap_pdu_code_t_COAP_RESPONSE_CODE_UNAUTHORIZED as u8, + Unprocessable = coap_pdu_code_t_COAP_RESPONSE_CODE_UNPROCESSABLE as u8, + UnsupportedContentFormat = coap_pdu_code_t_COAP_RESPONSE_CODE_UNSUPPORTED_CONTENT_FORMAT as u8, + Valid = coap_pdu_code_t_COAP_RESPONSE_CODE_VALID as u8, } impl CoapResponseCode { @@ -433,35 +439,33 @@ impl CoapResponseCode { /// request code. pub fn to_raw_pdu_code(self) -> coap_pdu_code_t { match self { - CoapResponseCode::Content => coap_pdu_code_t::COAP_RESPONSE_CODE_CONTENT, - CoapResponseCode::BadGateway => coap_pdu_code_t::COAP_RESPONSE_CODE_BAD_GATEWAY, - CoapResponseCode::Continue => coap_pdu_code_t::COAP_RESPONSE_CODE_CONTINUE, - CoapResponseCode::Conflict => coap_pdu_code_t::COAP_RESPONSE_CODE_CONFLICT, - CoapResponseCode::BadRequest => coap_pdu_code_t::COAP_RESPONSE_CODE_BAD_REQUEST, - CoapResponseCode::BadOption => coap_pdu_code_t::COAP_RESPONSE_CODE_BAD_OPTION, - CoapResponseCode::Changed => coap_pdu_code_t::COAP_RESPONSE_CODE_CHANGED, - CoapResponseCode::Created => coap_pdu_code_t::COAP_RESPONSE_CODE_CREATED, - CoapResponseCode::Deleted => coap_pdu_code_t::COAP_RESPONSE_CODE_DELETED, - CoapResponseCode::Forbidden => coap_pdu_code_t::COAP_RESPONSE_CODE_FORBIDDEN, - CoapResponseCode::GatewayTimeout => coap_pdu_code_t::COAP_RESPONSE_CODE_GATEWAY_TIMEOUT, - CoapResponseCode::HopLimitReached => coap_pdu_code_t::COAP_RESPONSE_CODE_HOP_LIMIT_REACHED, - CoapResponseCode::Incomplete => coap_pdu_code_t::COAP_RESPONSE_CODE_INCOMPLETE, - CoapResponseCode::InternalError => coap_pdu_code_t::COAP_RESPONSE_CODE_INTERNAL_ERROR, - CoapResponseCode::NotAcceptable => coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_ACCEPTABLE, - CoapResponseCode::NotAllowed => coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_ALLOWED, - CoapResponseCode::NotFound => coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_FOUND, - CoapResponseCode::NotImplemented => coap_pdu_code_t::COAP_RESPONSE_CODE_NOT_IMPLEMENTED, - CoapResponseCode::PreconditionFailed => coap_pdu_code_t::COAP_RESPONSE_CODE_PRECONDITION_FAILED, - CoapResponseCode::ProxyingNotSupported => coap_pdu_code_t::COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED, - CoapResponseCode::RequestTooLarge => coap_pdu_code_t::COAP_RESPONSE_CODE_REQUEST_TOO_LARGE, - CoapResponseCode::ServiceUnavailable => coap_pdu_code_t::COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE, - CoapResponseCode::TooManyRequests => coap_pdu_code_t::COAP_RESPONSE_CODE_TOO_MANY_REQUESTS, - CoapResponseCode::Unauthorized => coap_pdu_code_t::COAP_RESPONSE_CODE_UNAUTHORIZED, - CoapResponseCode::Unprocessable => coap_pdu_code_t::COAP_RESPONSE_CODE_UNPROCESSABLE, - CoapResponseCode::UnsupportedContentFormat => { - coap_pdu_code_t::COAP_RESPONSE_CODE_UNSUPPORTED_CONTENT_FORMAT - }, - CoapResponseCode::Valid => coap_pdu_code_t::COAP_RESPONSE_CODE_VALID, + CoapResponseCode::Content => coap_pdu_code_t_COAP_RESPONSE_CODE_CONTENT, + CoapResponseCode::BadGateway => coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_GATEWAY, + CoapResponseCode::Continue => coap_pdu_code_t_COAP_RESPONSE_CODE_CONTINUE, + CoapResponseCode::Conflict => coap_pdu_code_t_COAP_RESPONSE_CODE_CONFLICT, + CoapResponseCode::BadRequest => coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_REQUEST, + CoapResponseCode::BadOption => coap_pdu_code_t_COAP_RESPONSE_CODE_BAD_OPTION, + CoapResponseCode::Changed => coap_pdu_code_t_COAP_RESPONSE_CODE_CHANGED, + CoapResponseCode::Created => coap_pdu_code_t_COAP_RESPONSE_CODE_CREATED, + CoapResponseCode::Deleted => coap_pdu_code_t_COAP_RESPONSE_CODE_DELETED, + CoapResponseCode::Forbidden => coap_pdu_code_t_COAP_RESPONSE_CODE_FORBIDDEN, + CoapResponseCode::GatewayTimeout => coap_pdu_code_t_COAP_RESPONSE_CODE_GATEWAY_TIMEOUT, + CoapResponseCode::HopLimitReached => coap_pdu_code_t_COAP_RESPONSE_CODE_HOP_LIMIT_REACHED, + CoapResponseCode::Incomplete => coap_pdu_code_t_COAP_RESPONSE_CODE_INCOMPLETE, + CoapResponseCode::InternalError => coap_pdu_code_t_COAP_RESPONSE_CODE_INTERNAL_ERROR, + CoapResponseCode::NotAcceptable => coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_ACCEPTABLE, + CoapResponseCode::NotAllowed => coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_ALLOWED, + CoapResponseCode::NotFound => coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_FOUND, + CoapResponseCode::NotImplemented => coap_pdu_code_t_COAP_RESPONSE_CODE_NOT_IMPLEMENTED, + CoapResponseCode::PreconditionFailed => coap_pdu_code_t_COAP_RESPONSE_CODE_PRECONDITION_FAILED, + CoapResponseCode::ProxyingNotSupported => coap_pdu_code_t_COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED, + CoapResponseCode::RequestTooLarge => coap_pdu_code_t_COAP_RESPONSE_CODE_REQUEST_TOO_LARGE, + CoapResponseCode::ServiceUnavailable => coap_pdu_code_t_COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE, + CoapResponseCode::TooManyRequests => coap_pdu_code_t_COAP_RESPONSE_CODE_TOO_MANY_REQUESTS, + CoapResponseCode::Unauthorized => coap_pdu_code_t_COAP_RESPONSE_CODE_UNAUTHORIZED, + CoapResponseCode::Unprocessable => coap_pdu_code_t_COAP_RESPONSE_CODE_UNPROCESSABLE, + CoapResponseCode::UnsupportedContentFormat => coap_pdu_code_t_COAP_RESPONSE_CODE_UNSUPPORTED_CONTENT_FORMAT, + CoapResponseCode::Valid => coap_pdu_code_t_COAP_RESPONSE_CODE_VALID, } } } @@ -497,13 +501,13 @@ impl TryFrom for CoapResponseCode { #[derive(Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, Debug)] pub enum CoapMessageType { /// Confirmable message, i.e. a message whose reception should be confirmed by the peer. - Con = COAP_MESSAGE_CON as u8, + Con = coap_pdu_type_t_COAP_MESSAGE_CON as u8, /// Non-confirmable message, i.e. a message whose reception should not be confirmed by the peer. - Non = COAP_MESSAGE_NON as u8, + Non = coap_pdu_type_t_COAP_MESSAGE_NON as u8, /// Acknowledgement for a previous message. - Ack = COAP_MESSAGE_ACK as u8, + Ack = coap_pdu_type_t_COAP_MESSAGE_ACK as u8, /// Non-acknowledgement for a previous message. - Rst = COAP_MESSAGE_RST as u8, + Rst = coap_pdu_type_t_COAP_MESSAGE_RST as u8, } impl CoapMessageType { @@ -511,10 +515,10 @@ impl CoapMessageType { /// this message type. pub fn to_raw_pdu_type(&self) -> coap_pdu_type_t { match self { - CoapMessageType::Con => COAP_MESSAGE_CON, - CoapMessageType::Non => COAP_MESSAGE_NON, - CoapMessageType::Ack => COAP_MESSAGE_ACK, - CoapMessageType::Rst => COAP_MESSAGE_RST, + CoapMessageType::Con => coap_pdu_type_t_COAP_MESSAGE_CON, + CoapMessageType::Non => coap_pdu_type_t_COAP_MESSAGE_NON, + CoapMessageType::Ack => coap_pdu_type_t_COAP_MESSAGE_ACK, + CoapMessageType::Rst => coap_pdu_type_t_COAP_MESSAGE_RST, } } } diff --git a/libcoap/src/resource.rs b/libcoap/src/resource.rs index 6a752b9f..8c01f67f 100644 --- a/libcoap/src/resource.rs +++ b/libcoap/src/resource.rs @@ -9,33 +9,29 @@ //! Resource and resource handler descriptions +use core::ffi::c_int; use std::{ any::Any, - cell::Ref, - cell::RefMut, + cell::{Ref, RefMut}, fmt::{Debug, Formatter}, marker::PhantomData, }; -use libc::c_int; - use libcoap_sys::{ - coap_delete_resource, coap_new_str_const, coap_pdu_t, coap_register_request_handler, COAP_RESOURCE_FLAGS_NOTIFY_CON, - COAP_RESOURCE_FLAGS_NOTIFY_NON, COAP_RESOURCE_FLAGS_RELEASE_URI, coap_resource_get_uri_path, coap_resource_get_userdata, - coap_resource_init, coap_resource_notify_observers, coap_resource_set_get_observable, coap_resource_set_mode, coap_resource_set_userdata, coap_resource_t, - coap_send_rst, coap_session_t, coap_string_t, + coap_delete_resource, coap_new_str_const, coap_pdu_t, coap_register_request_handler, coap_resource_get_uri_path, + coap_resource_get_userdata, coap_resource_init, coap_resource_notify_observers, coap_resource_set_get_observable, + coap_resource_set_mode, coap_resource_set_userdata, coap_resource_t, coap_send_rst, coap_session_t, coap_string_t, + COAP_RESOURCE_FLAGS_NOTIFY_CON, COAP_RESOURCE_FLAGS_NOTIFY_NON, COAP_RESOURCE_FLAGS_RELEASE_URI, }; -use crate::{error::MessageConversionError, message::CoapMessage, protocol::CoapRequestCode}; -use crate::context::ensure_coap_started; -use crate::mem::{CoapFfiRcCell, DropInnerExclusively}; -use crate::message::CoapMessageCommon; -use crate::message::request::CoapRequest; -use crate::message::response::CoapResponse; -use crate::protocol::CoapMessageCode; -use crate::protocol::CoapMessageType; -use crate::session::CoapServerSession; -use crate::session::CoapSessionCommon; +use crate::{ + context::ensure_coap_started, + error::MessageConversionError, + mem::{CoapFfiRcCell, DropInnerExclusively}, + message::{request::CoapRequest, response::CoapResponse, CoapMessage, CoapMessageCommon}, + protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode}, + session::{CoapServerSession, CoapSessionCommon}, +}; // Trait aliases are experimental //trait CoapMethodHandlerFn = FnMut(&D, &mut CoapSession, &CoapRequestMessage, &mut CoapResponseMessage); diff --git a/libcoap/src/session/client.rs b/libcoap/src/session/client.rs index 47f241c2..6da6fffc 100644 --- a/libcoap/src/session/client.rs +++ b/libcoap/src/session/client.rs @@ -7,28 +7,35 @@ * See the README as well as the LICENSE file for more information. */ -use std::cell::{Ref, RefMut}; -use std::net::SocketAddr; +use std::{ + cell::{Ref, RefMut}, + net::SocketAddr, +}; use libcoap_sys::{ - coap_new_client_session, coap_proto_t, coap_register_event_handler, coap_session_get_app_data, - coap_session_get_context, coap_session_get_type, coap_session_init_token, coap_session_release, - coap_session_set_app_data, coap_session_t, coap_session_type_t, COAP_TOKEN_DEFAULT_MAX, + coap_new_client_session, coap_proto_t_COAP_PROTO_DTLS, coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP, + coap_register_event_handler, coap_session_get_app_data, coap_session_get_context, coap_session_get_type, + coap_session_init_token, coap_session_release, coap_session_set_app_data, coap_session_t, + coap_session_type_t_COAP_SESSION_TYPE_CLIENT, coap_session_type_t_COAP_SESSION_TYPE_HELLO, + coap_session_type_t_COAP_SESSION_TYPE_NONE, coap_session_type_t_COAP_SESSION_TYPE_SERVER, COAP_TOKEN_DEFAULT_MAX, }; use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider}; -use crate::event::event_handler_callback; -use crate::mem::{CoapFfiRcCell, DropInnerExclusively}; -use crate::prng::coap_prng_try_fill; -use crate::{context::CoapContext, error::SessionCreationError, types::CoapAddress}; - -#[cfg(dtls)] +#[cfg(feature = "dtls")] use crate::crypto::ClientCryptoContext; +use crate::{ + context::CoapContext, + error::SessionCreationError, + event::event_handler_callback, + mem::{CoapFfiRcCell, DropInnerExclusively}, + prng::coap_prng_try_fill, + types::CoapAddress, +}; #[derive(Debug)] struct CoapClientSessionInner<'a> { inner: CoapSessionInner<'a>, - #[cfg(dtls)] + #[cfg(feature = "dtls")] // This field is actually referred to be libcoap, so it isn't actually unused. #[allow(unused)] crypto_ctx: Option>, @@ -53,7 +60,7 @@ impl<'a> CoapClientSessionInner<'a> { let inner_session = CoapFfiRcCell::new(CoapClientSessionInner { inner: CoapSessionInner::new(raw_session), - #[cfg(dtls)] + #[cfg(feature = "dtls")] crypto_ctx: None, }); @@ -70,7 +77,7 @@ impl<'a> CoapClientSessionInner<'a> { /// # Safety /// The provided pointer for `raw_session` must be valid and point to the newly constructed raw /// session. - #[cfg(dtls)] + #[cfg(feature = "dtls")] unsafe fn new_with_crypto_ctx( raw_session: *mut coap_session_t, crypto_ctx: ClientCryptoContext<'a>, @@ -100,7 +107,7 @@ impl CoapClientSession<'_> { /// # Errors /// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely /// because it was not possible to bind to a port). - #[cfg(dtls)] + #[cfg(feature = "dtls")] pub fn connect_dtls<'a>( ctx: &mut CoapContext<'a>, addr: SocketAddr, @@ -115,15 +122,15 @@ impl CoapClientSession<'_> { match &crypto_ctx { #[cfg(feature = "dtls-psk")] ClientCryptoContext::Psk(psk_ctx) => { - psk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t::COAP_PROTO_DTLS)? + psk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)? }, #[cfg(feature = "dtls-pki")] ClientCryptoContext::Pki(pki_ctx) => { - pki_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t::COAP_PROTO_DTLS)? + pki_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)? }, #[cfg(feature = "dtls-rpk")] ClientCryptoContext::Rpk(rpk_ctx) => { - rpk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t::COAP_PROTO_DTLS)? + rpk_ctx.create_raw_session(ctx, &addr.into(), coap_proto_t_COAP_PROTO_DTLS)? }, } }; @@ -149,7 +156,7 @@ impl CoapClientSession<'_> { ctx.as_mut_raw_context(), std::ptr::null(), CoapAddress::from(addr).as_raw_address(), - coap_proto_t::COAP_PROTO_UDP, + coap_proto_t_COAP_PROTO_UDP, ) }; if session.is_null() { @@ -176,7 +183,7 @@ impl CoapClientSession<'_> { ctx.as_mut_raw_context(), std::ptr::null(), CoapAddress::from(addr).as_raw_address(), - coap_proto_t::COAP_PROTO_TCP, + coap_proto_t_COAP_PROTO_TCP, ) }; if session.is_null() { @@ -208,15 +215,18 @@ impl CoapClientSession<'_> { pub(crate) unsafe fn from_raw<'a>(raw_session: *mut coap_session_t) -> CoapClientSession<'a> { assert!(!raw_session.is_null(), "provided raw session was null"); let raw_session_type = coap_session_get_type(raw_session); + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] match raw_session_type { - coap_session_type_t::COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), - coap_session_type_t::COAP_SESSION_TYPE_CLIENT => { + coap_session_type_t_COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), + coap_session_type_t_COAP_SESSION_TYPE_CLIENT => { let raw_app_data_ptr = coap_session_get_app_data(raw_session); assert!(!raw_app_data_ptr.is_null(), "provided raw session has no app data"); let inner = CoapFfiRcCell::clone_raw_rc(raw_app_data_ptr); CoapClientSession { inner } }, - coap_session_type_t::COAP_SESSION_TYPE_SERVER | coap_session_type_t::COAP_SESSION_TYPE_HELLO => { + coap_session_type_t_COAP_SESSION_TYPE_SERVER | coap_session_type_t_COAP_SESSION_TYPE_HELLO => { panic!("attempted to create CoapClientSession from raw server session") }, _ => unreachable!("unknown session type"), @@ -267,6 +277,7 @@ impl<'a> CoapSessionInnerProvider<'a> for CoapClientSession<'a> { fn inner_ref<'b>(&'b self) -> Ref<'b, CoapSessionInner<'a>> { Ref::map(self.inner.borrow(), |v| &v.inner) } + fn inner_mut<'b>(&'b self) -> RefMut<'b, CoapSessionInner<'a>> { RefMut::map(self.inner.borrow_mut(), |v| &mut v.inner) } diff --git a/libcoap/src/session/mod.rs b/libcoap/src/session/mod.rs index 13016e3d..ffb0ac3f 100644 --- a/libcoap/src/session/mod.rs +++ b/libcoap/src/session/mod.rs @@ -19,12 +19,17 @@ use std::{ use libcoap_sys::{ coap_context_t, coap_fixed_point_t, coap_mid_t, coap_new_message_id, coap_pdu_get_token, coap_pdu_t, - coap_response_t, coap_send, coap_session_get_ack_random_factor, coap_session_get_ack_timeout, - coap_session_get_addr_local, coap_session_get_addr_remote, coap_session_get_ifindex, - coap_session_get_max_retransmit, coap_session_get_proto, coap_session_get_state, coap_session_get_type, - coap_session_init_token, coap_session_max_pdu_size, coap_session_new_token, coap_session_send_ping, - coap_session_set_ack_random_factor, coap_session_set_ack_timeout, coap_session_set_max_retransmit, - coap_session_set_mtu, coap_session_state_t, coap_session_t, coap_session_type_t, + coap_response_t, coap_response_t_COAP_RESPONSE_FAIL, coap_response_t_COAP_RESPONSE_OK, coap_send, + coap_session_get_ack_random_factor, coap_session_get_ack_timeout, coap_session_get_addr_local, + coap_session_get_addr_remote, coap_session_get_ifindex, coap_session_get_max_retransmit, coap_session_get_proto, + coap_session_get_state, coap_session_get_type, coap_session_init_token, coap_session_max_pdu_size, + coap_session_new_token, coap_session_send_ping, coap_session_set_ack_random_factor, coap_session_set_ack_timeout, + coap_session_set_max_retransmit, coap_session_set_mtu, coap_session_state_t, + coap_session_state_t_COAP_SESSION_STATE_CONNECTING, coap_session_state_t_COAP_SESSION_STATE_CSM, + coap_session_state_t_COAP_SESSION_STATE_ESTABLISHED, coap_session_state_t_COAP_SESSION_STATE_HANDSHAKE, + coap_session_state_t_COAP_SESSION_STATE_NONE, coap_session_t, coap_session_type_t_COAP_SESSION_TYPE_CLIENT, + coap_session_type_t_COAP_SESSION_TYPE_HELLO, coap_session_type_t_COAP_SESSION_TYPE_NONE, + coap_session_type_t_COAP_SESSION_TYPE_SERVER, }; #[cfg(feature = "dtls-psk")] use libcoap_sys::{coap_session_get_psk_hint, coap_session_get_psk_identity, coap_session_get_psk_key}; @@ -45,21 +50,24 @@ pub mod server; /// Representation of the states that a session can be in. #[repr(u32)] pub enum CoapSessionState { - None = coap_session_state_t::COAP_SESSION_STATE_NONE as u32, - Connecting = coap_session_state_t::COAP_SESSION_STATE_CONNECTING as u32, - Handshake = coap_session_state_t::COAP_SESSION_STATE_HANDSHAKE as u32, - Csm = coap_session_state_t::COAP_SESSION_STATE_CSM as u32, - Established = coap_session_state_t::COAP_SESSION_STATE_ESTABLISHED as u32, + None = coap_session_state_t_COAP_SESSION_STATE_NONE as u32, + Connecting = coap_session_state_t_COAP_SESSION_STATE_CONNECTING as u32, + Handshake = coap_session_state_t_COAP_SESSION_STATE_HANDSHAKE as u32, + Csm = coap_session_state_t_COAP_SESSION_STATE_CSM as u32, + Established = coap_session_state_t_COAP_SESSION_STATE_ESTABLISHED as u32, } impl From for CoapSessionState { fn from(raw_state: coap_session_state_t) -> Self { + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] match raw_state { - coap_session_state_t::COAP_SESSION_STATE_NONE => CoapSessionState::None, - coap_session_state_t::COAP_SESSION_STATE_CONNECTING => CoapSessionState::Connecting, - coap_session_state_t::COAP_SESSION_STATE_HANDSHAKE => CoapSessionState::Handshake, - coap_session_state_t::COAP_SESSION_STATE_CSM => CoapSessionState::Csm, - coap_session_state_t::COAP_SESSION_STATE_ESTABLISHED => CoapSessionState::Established, + coap_session_state_t_COAP_SESSION_STATE_NONE => CoapSessionState::None, + coap_session_state_t_COAP_SESSION_STATE_CONNECTING => CoapSessionState::Connecting, + coap_session_state_t_COAP_SESSION_STATE_HANDSHAKE => CoapSessionState::Handshake, + coap_session_state_t_COAP_SESSION_STATE_CSM => CoapSessionState::Csm, + coap_session_state_t_COAP_SESSION_STATE_ESTABLISHED => CoapSessionState::Established, _ => unreachable!("unknown session state added"), } } @@ -112,7 +120,7 @@ pub trait CoapSessionCommon<'a>: CoapSessionCommonInternal<'a> { } /// Sets the application-specific data stored alongside this session. - fn set_app_data(&self, value: Option) { + fn set_app_data(&self, value: Option) { let mut inner = self.inner_mut(); let new_box: Option> = value.map(|v| Rc::new(v) as Rc); inner.app_data = new_box; @@ -447,12 +455,16 @@ impl<'a> CoapSession<'a> { pub(crate) unsafe fn from_raw(raw_session: *mut coap_session_t) -> CoapSession<'a> { assert!(!raw_session.is_null(), "provided raw session was null"); let raw_session_type = coap_session_get_type(raw_session); + + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] match raw_session_type { - coap_session_type_t::COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), + coap_session_type_t_COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), - coap_session_type_t::COAP_SESSION_TYPE_CLIENT => CoapClientSession::from_raw(raw_session).into(), + coap_session_type_t_COAP_SESSION_TYPE_CLIENT => CoapClientSession::from_raw(raw_session).into(), - coap_session_type_t::COAP_SESSION_TYPE_SERVER | coap_session_type_t::COAP_SESSION_TYPE_HELLO => { + coap_session_type_t_COAP_SESSION_TYPE_SERVER | coap_session_type_t_COAP_SESSION_TYPE_HELLO => { CoapServerSession::from_raw(raw_session).into() }, _ => unreachable!("unknown session type"), @@ -542,7 +554,6 @@ impl CoapRequestHandle { } // This is fine, we don't read the C-type struct, we return it. -#[allow(improper_ctypes_definitions)] pub(crate) unsafe extern "C" fn session_response_handler( session: *mut coap_session_t, _sent: *const coap_pdu_t, @@ -555,12 +566,12 @@ pub(crate) unsafe extern "C" fn session_response_handler( let raw_token = coap_pdu_get_token(received); let token: CoapToken = CoapToken::from(std::slice::from_raw_parts(raw_token.s, raw_token.length)); if !client.is_waiting_for_token(&token) { - return coap_response_t::COAP_RESPONSE_FAIL; + return coap_response_t_COAP_RESPONSE_FAIL; } if let Ok(message) = CoapMessage::from_raw_pdu(received).and_then(CoapResponse::from_message) { client.add_response(message); - coap_response_t::COAP_RESPONSE_OK + coap_response_t_COAP_RESPONSE_OK } else { - coap_response_t::COAP_RESPONSE_FAIL + coap_response_t_COAP_RESPONSE_FAIL } } diff --git a/libcoap/src/session/server.rs b/libcoap/src/session/server.rs index 29335e58..9177648a 100644 --- a/libcoap/src/session/server.rs +++ b/libcoap/src/session/server.rs @@ -11,7 +11,9 @@ use std::cell::{Ref, RefMut}; use libcoap_sys::{ coap_session_get_app_data, coap_session_get_type, coap_session_reference, coap_session_release, - coap_session_set_app_data, coap_session_t, coap_session_type_t, + coap_session_set_app_data, coap_session_t, coap_session_type_t_COAP_SESSION_TYPE_CLIENT, + coap_session_type_t_COAP_SESSION_TYPE_HELLO, coap_session_type_t_COAP_SESSION_TYPE_NONE, + coap_session_type_t_COAP_SESSION_TYPE_SERVER, }; use super::{CoapSessionCommon, CoapSessionInner, CoapSessionInnerProvider}; @@ -65,13 +67,16 @@ impl CoapServerSession<'_> { assert!(!raw_session.is_null(), "provided raw session was null"); let raw_session_type = coap_session_get_type(raw_session); let inner = CoapSessionInner::new(raw_session); + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] let session_inner = match raw_session_type { - coap_session_type_t::COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), - coap_session_type_t::COAP_SESSION_TYPE_CLIENT => { + coap_session_type_t_COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), + coap_session_type_t_COAP_SESSION_TYPE_CLIENT => { panic!("attempted to create server session from raw client session") }, - coap_session_type_t::COAP_SESSION_TYPE_SERVER => CoapServerSessionInner { inner }, - coap_session_type_t::COAP_SESSION_TYPE_HELLO => CoapServerSessionInner { inner }, + coap_session_type_t_COAP_SESSION_TYPE_SERVER => CoapServerSessionInner { inner }, + coap_session_type_t_COAP_SESSION_TYPE_HELLO => CoapServerSessionInner { inner }, _ => unreachable!("unknown session type"), }; let session_ref = CoapFfiRcCell::new(session_inner); @@ -139,9 +144,12 @@ impl CoapServerSession<'_> { pub(crate) unsafe fn from_raw_without_refcount<'a>(raw_session: *mut coap_session_t) -> CoapServerSession<'a> { assert!(!raw_session.is_null(), "provided raw session was null"); let raw_session_type = coap_session_get_type(raw_session); + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] match raw_session_type { - coap_session_type_t::COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), - coap_session_type_t::COAP_SESSION_TYPE_SERVER | coap_session_type_t::COAP_SESSION_TYPE_HELLO => { + coap_session_type_t_COAP_SESSION_TYPE_NONE => panic!("provided session has no type"), + coap_session_type_t_COAP_SESSION_TYPE_SERVER | coap_session_type_t_COAP_SESSION_TYPE_HELLO => { let raw_app_data_ptr = coap_session_get_app_data(raw_session); assert!(!raw_app_data_ptr.is_null(), "provided raw session has no app data"); CoapServerSession { @@ -149,7 +157,7 @@ impl CoapServerSession<'_> { ref_counted: false, } }, - coap_session_type_t::COAP_SESSION_TYPE_CLIENT => { + coap_session_type_t_COAP_SESSION_TYPE_CLIENT => { panic!("attempted to create CoapServerSession from raw client session") }, _ => unreachable!("unknown session type"), diff --git a/libcoap/src/types.rs b/libcoap/src/types.rs index 9967c799..4ac83f27 100644 --- a/libcoap/src/types.rs +++ b/libcoap/src/types.rs @@ -9,41 +9,35 @@ //! Types required for conversion between libcoap C library abstractions and Rust types. -use std::ffi::CString; -use std::fmt::{Display, Formatter}; -use std::marker::PhantomPinned; -use std::pin::Pin; +use core::ffi::c_ushort; use std::{ - fmt::Debug, + ffi::{CStr, CString}, + fmt::{Debug, Display, Formatter}, + marker::PhantomPinned, mem::MaybeUninit, net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}, os::raw::c_int, + pin::Pin, str::FromStr, }; -use libc::{c_ushort, in6_addr, in_addr, sa_family_t, sockaddr_in, sockaddr_in6, socklen_t, AF_INET, AF_INET6}; +use libcoap_sys::{ + c_stdlib::{in6_addr, in_addr, sa_family_t, sockaddr_in, sockaddr_in6, socklen_t, AF_INET, AF_INET6}, + coap_address_t, coap_delete_optlist, coap_mid_t, coap_proto_t, coap_proto_t_COAP_PROTO_DTLS, + coap_proto_t_COAP_PROTO_NONE, coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_TLS, + coap_proto_t_COAP_PROTO_UDP, coap_split_proxy_uri, coap_split_uri, coap_str_const_t, coap_string_equal, + coap_uri_into_optlist, coap_uri_scheme_t, coap_uri_scheme_t_COAP_URI_SCHEME_COAP, + coap_uri_scheme_t_COAP_URI_SCHEME_COAPS, coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_TCP, + coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_WS, coap_uri_scheme_t_COAP_URI_SCHEME_COAP_TCP, + coap_uri_scheme_t_COAP_URI_SCHEME_COAP_WS, coap_uri_scheme_t_COAP_URI_SCHEME_HTTP, + coap_uri_scheme_t_COAP_URI_SCHEME_HTTPS, coap_uri_t, COAP_URI_SCHEME_SECURE_MASK, +}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; #[cfg(feature = "url")] use url::Url; -use libcoap_sys::coap_uri_scheme_t::{COAP_URI_SCHEME_COAPS_WS, COAP_URI_SCHEME_COAP_WS}; -use libcoap_sys::{ - 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_t, - coap_uri_scheme_t::{ - COAP_URI_SCHEME_COAP, COAP_URI_SCHEME_COAPS, COAP_URI_SCHEME_COAPS_TCP, COAP_URI_SCHEME_COAP_TCP, - COAP_URI_SCHEME_HTTP, COAP_URI_SCHEME_HTTPS, - }, - coap_uri_t, COAP_URI_SCHEME_SECURE_MASK, -}; - -use crate::context::ensure_coap_started; -use crate::error::UriParsingError; -use crate::message::CoapOption; -use crate::protocol::UriPort; +use crate::{context::ensure_coap_started, error::UriParsingError, message::CoapOption, protocol::UriPort}; /// Interface index used internally by libcoap to refer to an endpoint. pub type IfIndex = c_int; @@ -92,11 +86,11 @@ impl ToSocketAddrs for CoapAddress { // SAFETY: That the underlying value of addr is a valid sockaddr is an invariant, the only // way the value could be invalid is if as_mut_coap_address_t() (an unsafe function) is used // incorrectly. - let socketaddr = match unsafe { self.0.addr.sa.as_ref().sa_family } as i32 { + let socketaddr = match unsafe { self.0.addr.sa.sa_family as _ } { AF_INET => { // SAFETY: Validity of addr is an invariant, and we checked that the type of the // underlying sockaddr is actually sockaddr_in. - let raw_addr = unsafe { self.0.addr.sin.as_ref() }; + let raw_addr = unsafe { self.0.addr.sin }; SocketAddrV4::new( Ipv4Addr::from(raw_addr.sin_addr.s_addr.to_ne_bytes()), u16::from_be(raw_addr.sin_port), @@ -106,9 +100,17 @@ impl ToSocketAddrs for CoapAddress { AF_INET6 => { // SAFETY: Validity of addr is an invariant, and we checked that the type of the // underlying sockaddr is actually sockaddr_in6. - let raw_addr = unsafe { self.0.addr.sin6.as_ref() }; + let raw_addr = unsafe { self.0.addr.sin6 }; + + // The esp_idf_sys definition of sockaddr_in6 differs slightly. + #[cfg(not(target_os = "espidf"))] + let raw_addr_bytes = raw_addr.sin6_addr.s6_addr; + #[cfg(target_os = "espidf")] + // SAFETY: Both representations are valid. + let raw_addr_bytes = unsafe { raw_addr.sin6_addr.un.u8_addr }; + SocketAddrV6::new( - Ipv6Addr::from(raw_addr.sin6_addr.s6_addr), + Ipv6Addr::from(raw_addr_bytes), u16::from_be(raw_addr.sin6_port), raw_addr.sin6_flowinfo, raw_addr.sin6_scope_id, @@ -135,7 +137,7 @@ impl From for CoapAddress { addr: std::mem::zeroed(), }; - *coap_addr.addr.sin.as_mut() = sockaddr_in { + coap_addr.addr.sin = sockaddr_in { #[cfg(any( target_os = "freebsd", target_os = "dragonfly", @@ -167,7 +169,9 @@ impl From for CoapAddress { addr: std::mem::zeroed(), }; - *coap_addr.addr.sin6.as_mut() = sockaddr_in6 { + // Representation of sockaddr_in6 differs depending on the used OS, therefore + // some fields are a bit different. + coap_addr.addr.sin6 = sockaddr_in6 { #[cfg(any( target_os = "freebsd", target_os = "dragonfly", @@ -182,7 +186,12 @@ impl From for CoapAddress { sin6_family: AF_INET6 as sa_family_t, sin6_port: addr.port().to_be(), sin6_addr: in6_addr { + #[cfg(not(target_os = "espidf"))] s6_addr: addr.ip().octets(), + #[cfg(target_os = "espidf")] + un: libcoap_sys::c_stdlib::in6_addr__bindgen_ty_1 { + u8_addr: addr.ip().octets(), + }, }, sin6_flowinfo: addr.flowinfo(), sin6_scope_id: addr.scope_id(), @@ -217,14 +226,14 @@ impl From<&coap_address_t> for CoapAddress { #[repr(u32)] #[derive(Copy, Clone, FromPrimitive, Debug, PartialEq, Eq, Hash)] pub enum CoapUriScheme { - Coap = COAP_URI_SCHEME_COAP as u32, - Coaps = COAP_URI_SCHEME_COAPS as u32, - CoapTcp = COAP_URI_SCHEME_COAP_TCP as u32, - 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, + Coap = coap_uri_scheme_t_COAP_URI_SCHEME_COAP as u32, + Coaps = coap_uri_scheme_t_COAP_URI_SCHEME_COAPS as u32, + CoapTcp = coap_uri_scheme_t_COAP_URI_SCHEME_COAP_TCP as u32, + CoapsTcp = coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_TCP as u32, + Http = coap_uri_scheme_t_COAP_URI_SCHEME_HTTP as u32, + Https = coap_uri_scheme_t_COAP_URI_SCHEME_HTTPS as u32, + CoapWs = coap_uri_scheme_t_COAP_URI_SCHEME_COAP_WS as u32, + CoapsWs = coap_uri_scheme_t_COAP_URI_SCHEME_COAPS_WS as u32, } impl CoapUriScheme { @@ -589,48 +598,12 @@ impl CoapUri { // 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. - // Set size of temporary buffer for option storage. - // TODO remove when updating minimum libcoap version to 4.3.5, as this buffer is no longer - // used there. - #[cfg(coap_uri_buf_unused)] - let mut buf = []; - #[cfg(not(coap_uri_buf_unused))] - let mut buf = { - let buf_len = - // Length of UriHost option (length of host + max. 5 bytes of option header) - self.host().map(|v| v.len()+5).unwrap_or(0) - // Length of UriPort option (max. 2 bytes for the port number + max. 5 bytes of - // option header) - + self.port().map(|v| 7).unwrap_or(0) - // Length of path segment - + self.path().map(|v| v.len()).unwrap_or(0) - // Length of option headers for path segments. - // Each path segment has its own header, which can be up to 5 bytes in size. - + self.path().map(|v| (v.iter().filter(|c| **c as char == '/').count()+1)*5).unwrap_or(0) - // Length of query segment - + self.query().map(|v| v.len()).unwrap_or(0) - // Length of option headers for query segments. - // Each query segment has its own header, which can be up to 5 bytes in size. - + self.query().map(|v| (v.iter().filter(|c| **c as char == '?' || **c as char == '&').count()+1)*5).unwrap_or(0); - vec![0u8; buf_len] - }; - 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 - { + if unsafe { coap_uri_into_optlist(&self.raw_uri, std::ptr::null(), &mut optlist, 1) } < 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"); @@ -749,7 +722,7 @@ impl CoapUri { length: 0, s: std::ptr::null(), }, - scheme: coap_uri_scheme_t::COAP_URI_SCHEME_COAP, + scheme: coap_uri_scheme_t_COAP_URI_SCHEME_COAP, }, uri_str, is_proxy, @@ -776,7 +749,7 @@ impl CoapUri { if unsafe { parsing_fn( uri.uri_str.0.as_ptr() as *const u8, - libc::strlen(uri.uri_str.0.as_ptr()), + CStr::from_ptr(uri.uri_str.0.as_ptr()).count_bytes(), std::ptr::from_mut(&mut uri.raw_uri), ) } < 0 @@ -833,10 +806,10 @@ impl PartialEq for CoapUri { // 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) - } + 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) + } } } @@ -878,11 +851,11 @@ impl Display for CoapUri { #[non_exhaustive] #[derive(Copy, Clone, FromPrimitive, PartialEq, Eq, Hash)] pub enum CoapProtocol { - None = COAP_PROTO_NONE as u32, - Udp = COAP_PROTO_UDP as u32, - Dtls = COAP_PROTO_DTLS as u32, - Tcp = COAP_PROTO_TCP as u32, - Tls = COAP_PROTO_TLS as u32, + None = coap_proto_t_COAP_PROTO_NONE as u32, + Udp = coap_proto_t_COAP_PROTO_UDP as u32, + Dtls = coap_proto_t_COAP_PROTO_DTLS as u32, + Tcp = coap_proto_t_COAP_PROTO_TCP as u32, + Tls = coap_proto_t_COAP_PROTO_TLS as u32, } impl CoapProtocol { diff --git a/libcoap/src/wrapper.h b/libcoap/src/wrapper.h new file mode 120000 index 00000000..762e29fb --- /dev/null +++ b/libcoap/src/wrapper.h @@ -0,0 +1 @@ +../../libcoap-sys/src/wrapper.h \ No newline at end of file diff --git a/libcoap/tests/common/dtls.rs b/libcoap/tests/common/dtls.rs index 138ab58f..b572df40 100644 --- a/libcoap/tests/common/dtls.rs +++ b/libcoap/tests/common/dtls.rs @@ -1,14 +1,23 @@ -use crate::common; -use libcoap_rs::crypto::pki_rpk::{ - KeyDef, KeyType, NonCertVerifying, PkiRpkContext, PkiRpkContextBuilder, ServerPkiRpkCryptoContext, +use std::{ffi::CStr, path::PathBuf, time::Duration}; + +use libcoap_rs::{ + crypto::{ + pki_rpk::{KeyDef, KeyType, NonCertVerifying, PkiRpkContext, PkiRpkContextBuilder, ServerPkiRpkCryptoContext}, + ClientCryptoContext, + }, + message::CoapMessageCommon, + protocol::{CoapMessageCode, CoapResponseCode}, + session::{CoapClientSession, CoapSessionCommon}, + CoapContext, +}; +use libcoap_sys::{ + coap_get_tls_library_version, coap_package_version, coap_tls_library_t_COAP_TLS_LIBRARY_GNUTLS, + coap_tls_library_t_COAP_TLS_LIBRARY_MBEDTLS, coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS, + coap_tls_library_t_COAP_TLS_LIBRARY_OPENSSL, coap_tls_library_t_COAP_TLS_LIBRARY_TINYDTLS, + coap_tls_library_t_COAP_TLS_LIBRARY_WOLFSSL, }; -use libcoap_rs::crypto::ClientCryptoContext; -use libcoap_rs::message::CoapMessageCommon; -use libcoap_rs::protocol::{CoapMessageCode, CoapResponseCode}; -use libcoap_rs::session::{CoapClientSession, CoapSessionCommon}; -use libcoap_rs::CoapContext; -use std::path::PathBuf; -use std::time::Duration; + +use crate::common; // Is used in some test cases, but not in others (causing a compiler warning) #[allow(unused)] @@ -23,6 +32,24 @@ pub fn dtls_client_server_request_common( ServerPkiRpkCryptoContext<'static>: From>, ClientCryptoContext<'static>: From>, { + // Variant names are named by bindgen, we have no influence on this. + // Ref: https://github.com/rust-lang/rust/issues/39371 + #[allow(non_upper_case_globals)] + let tls_library = match unsafe { *coap_get_tls_library_version() }.type_ { + coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS => "notls", + coap_tls_library_t_COAP_TLS_LIBRARY_TINYDTLS => "tinydtls", + coap_tls_library_t_COAP_TLS_LIBRARY_OPENSSL => "openssl", + coap_tls_library_t_COAP_TLS_LIBRARY_GNUTLS => "gnutls", + coap_tls_library_t_COAP_TLS_LIBRARY_MBEDTLS => "mbedtls", + coap_tls_library_t_COAP_TLS_LIBRARY_WOLFSSL => "wolfssl", + _ => "unknown", + }; + println!( + "Libcoap-Version: {}, DTLS library: {}", + unsafe { CStr::from_ptr(coap_package_version()) }.to_string_lossy(), + tls_library + ); + let server_address = common::get_unused_server_addr(); let client_crypto_ctx = client_ctx_setup(PkiRpkContextBuilder::<'static, KTY, NonCertVerifying>::new(client_key)); let server_handle = common::spawn_test_server(move |mut context: CoapContext| { diff --git a/libcoap/tests/common/mod.rs b/libcoap/tests/common/mod.rs index af109b34..02934c35 100644 --- a/libcoap/tests/common/mod.rs +++ b/libcoap/tests/common/mod.rs @@ -10,18 +10,24 @@ #[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))] pub mod dtls; -use std::net::{SocketAddr, UdpSocket}; -use std::rc::Rc; -use std::sync::{Arc, Condvar, Mutex}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::thread::JoinHandle; -use std::time::Duration; +use std::{ + net::{SocketAddr, UdpSocket}, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Condvar, Mutex, + }, + thread::JoinHandle, + 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; -use libcoap_sys::{coap_dtls_set_log_level, coap_log_t, coap_set_log_level}; +use libcoap_rs::{ + message::{CoapMessageCommon, CoapRequest, CoapResponse}, + protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode}, + session::CoapSessionCommon, + CoapContext, CoapRequestHandler, CoapResource, +}; +use libcoap_sys::{coap_dtls_set_log_level, coap_log_t_COAP_LOG_DEBUG, coap_set_log_level}; pub(crate) fn get_unused_server_addr() -> SocketAddr { // This will give us a SocketAddress with a port in the local port range automatically @@ -51,25 +57,40 @@ pub(crate) fn spawn_test_server) -> CoapContext<' let ready_condition = Arc::new((Mutex::new(false), Condvar::new())); let ready_condition2 = Arc::clone(&ready_condition); - let server_handle = std::thread::spawn(move || { - let (ready_var, ready_cond) = &*ready_condition2; - run_test_server(|context| { - let context = context_configurator(context); - let mut ready_var = ready_var.lock().expect("ready condition mutex is poisoned"); - *ready_var = true; - ready_cond.notify_all(); - context - }); - }); + let server_handle = std::thread::Builder::new() + .name(String::from("test server")) + .spawn(move || { + let (ready_var, ready_cond) = &*ready_condition2; + run_test_server(|context| { + let context = context_configurator(context); + let mut ready_var = ready_var.lock().expect("ready condition mutex is poisoned"); + *ready_var = true; + ready_cond.notify_all(); + context + }); + }) + .expect("unable to spawn test server thread"); let (ready_var, ready_cond) = &*ready_condition; - drop( - ready_cond - .wait_while(ready_var.lock().expect("ready condition mutex is poisoned"), |ready| { - !*ready - }) - .expect("ready condition mutex is poisoned"), - ); + { + let (_guard, timeout_result) = ready_cond + .wait_timeout_while( + ready_var.lock().expect("ready condition mutex is poisoned"), + Duration::from_secs(10), + |ready| !*ready, + ) + .expect("ready condition mutex is poisoned"); + if timeout_result.timed_out() && server_handle.is_finished() { + if let Err(e) = server_handle.join() { + std::panic::resume_unwind(e); + } + panic!("Test server thread is dead and has not reported readiness after 10 seconds, but has also not panicked.") + } + + if timeout_result.timed_out() { + panic!("Test server thread has not reported readiness after 10 seconds, but has also not died (deadlock?).") + } + } server_handle } @@ -77,8 +98,8 @@ pub(crate) fn spawn_test_server) -> CoapContext<' pub(crate) fn run_test_server) -> CoapContext<'static>>(context_configurator: F) { unsafe { libcoap_sys::coap_startup_with_feature_checks(); - coap_dtls_set_log_level(coap_log_t::COAP_LOG_DEBUG); - coap_set_log_level(coap_log_t::COAP_LOG_DEBUG); + coap_dtls_set_log_level(coap_log_t_COAP_LOG_DEBUG); + coap_set_log_level(coap_log_t_COAP_LOG_DEBUG); } let mut context = CoapContext::new().unwrap(); context = context_configurator(context); diff --git a/libcoap/tests/dtls_pki_client_server_test.rs b/libcoap/tests/dtls_pki_client_server_test.rs index bc97761f..3f834e1e 100644 --- a/libcoap/tests/dtls_pki_client_server_test.rs +++ b/libcoap/tests/dtls_pki_client_server_test.rs @@ -9,11 +9,14 @@ #![cfg(feature = "dtls-pki")] -use crate::common::dtls::dtls_client_server_request_common; -use libcoap_rs::crypto::pki_rpk::{Asn1PrivateKeyType, DerFileKeyComponent, NonCertVerifying, PkiRpkContextBuilder}; -use libcoap_rs::crypto::pki_rpk::{Pki, PkiKeyDef}; use std::path::PathBuf; +use libcoap_rs::crypto::pki_rpk::{ + Asn1PrivateKeyType, DerFileKeyComponent, NonCertVerifying, Pki, PkiKeyDef, PkiRpkContextBuilder, +}; + +use crate::common::dtls::dtls_client_server_request_common; + mod common; #[test] @@ -75,9 +78,9 @@ pub fn dtls_pki_asn1_file_client_server_request() { // For some inexplicable reason, setting the CA cert fails _only_ with ASN1 files using the // OpenSSL library. // I'm pretty sure this is a libcoap issue, so we'll not set the CA cert there for now. - #[cfg(not(feature = "dtls_openssl"))] + #[cfg(not(dtls_backend = "openssl"))] Some(key_storage.join("./ca/ca.crt.der")), - #[cfg(feature = "dtls_openssl")] + #[cfg(dtls_backend = "openssl")] None::, key_storage.join("./server/server.crt.der"), key_storage.join("./server/server.key.der"), @@ -93,7 +96,7 @@ pub fn dtls_pki_asn1_file_client_server_request() { #[test] // GnuTLS does not like DER-encoded EC keys from memory (for some reason. Loading them from files as // done in the test above works fine). -#[cfg(not(feature = "dtls_gnutls"))] +#[cfg_attr(dtls_backend = "gnutls", ignore)] pub fn dtls_pki_asn1_memory_client_server_request() { const DER_CA_CERT: &[u8] = include_bytes!("../resources/test-keys/ca/ca.crt.der"); const DER_CLIENT_PUBLIC_CERT: &[u8] = include_bytes!("../resources/test-keys/client/client.crt.der"); diff --git a/libcoap/tests/dtls_psk_client_server_test.rs b/libcoap/tests/dtls_psk_client_server_test.rs index 757e9612..a19dcacf 100644 --- a/libcoap/tests/dtls_psk_client_server_test.rs +++ b/libcoap/tests/dtls_psk_client_server_test.rs @@ -10,13 +10,11 @@ #![cfg(feature = "dtls-psk")] use std::time::Duration; -use libcoap_rs::crypto::psk::PskKey; -use libcoap_rs::crypto::psk::{ClientPskContextBuilder, ServerPskContextBuilder}; -use libcoap_rs::session::CoapClientSession; use libcoap_rs::{ + crypto::psk::{ClientPskContextBuilder, PskKey, ServerPskContextBuilder}, message::CoapMessageCommon, protocol::{CoapMessageCode, CoapResponseCode}, - session::CoapSessionCommon, + session::{CoapClientSession, CoapSessionCommon}, CoapContext, }; diff --git a/libcoap/tests/dtls_rpk_client_server_test.rs b/libcoap/tests/dtls_rpk_client_server_test.rs index 5ed48495..7ca64b04 100644 --- a/libcoap/tests/dtls_rpk_client_server_test.rs +++ b/libcoap/tests/dtls_rpk_client_server_test.rs @@ -9,9 +9,9 @@ #![cfg(feature = "dtls-rpk")] +use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder, Rpk, RpkKeyDef}; + use crate::common::dtls::dtls_client_server_request_common; -use libcoap_rs::crypto::pki_rpk::{NonCertVerifying, PkiRpkContextBuilder}; -use libcoap_rs::crypto::pki_rpk::{Rpk, RpkKeyDef}; mod common; diff --git a/libcoap/tests/tcp_client_server_test.rs b/libcoap/tests/tcp_client_server_test.rs index e5a64258..fe9b8a5b 100644 --- a/libcoap/tests/tcp_client_server_test.rs +++ b/libcoap/tests/tcp_client_server_test.rs @@ -6,16 +6,16 @@ * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved. * See the README as well as the LICENSE file for more information. */ - #![cfg(feature = "tcp")] +#![cfg(feature = "tcp")] + +use std::time::Duration; -use libcoap_rs::session::CoapClientSession; use libcoap_rs::{ message::CoapMessageCommon, protocol::{CoapMessageCode, CoapResponseCode}, - session::CoapSessionCommon, + session::{CoapClientSession, CoapSessionCommon}, CoapContext, }; -use std::time::Duration; mod common; diff --git a/libcoap/tests/udp_client_server_test.rs b/libcoap/tests/udp_client_server_test.rs index c3d9d27e..21226193 100644 --- a/libcoap/tests/udp_client_server_test.rs +++ b/libcoap/tests/udp_client_server_test.rs @@ -7,14 +7,14 @@ * See the README as well as the LICENSE file for more information. */ -use libcoap_rs::session::CoapClientSession; +use std::time::Duration; + use libcoap_rs::{ message::CoapMessageCommon, protocol::{CoapMessageCode, CoapResponseCode}, - session::CoapSessionCommon, + session::{CoapClientSession, CoapSessionCommon}, CoapContext, }; -use std::time::Duration; mod common;