libcoap_sys/
lib.rs

1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * Copyright © The libcoap-rs Contributors, all rights reserved.
4 * This file is part of the libcoap-rs project, see the README file for
5 * general information on this project and the NOTICE.md and LICENSE files
6 * for information regarding copyright ownership and terms of use.
7 *
8 * lib.rs - Main library entry point for raw libcoap bindings.
9 */
10
11//! Auto-generated unsafe bindings to [libcoap](https://github.com/obgm/libcoap), generated using
12//! [bindgen](https://crates.io/crates/bindgen).
13//!
14//! This crate allows direct (but unsafe) usage of the libcoap C library from Rust. The declarations
15//! made in this library are generated automatically using bindgen, for further documentation on how
16//! to use them, refer to the [libcoap documentation](https://libcoap.net/documentation.html).
17//!
18//! In most cases you probably want to use the safe wrapper provided by the
19//! [libcoap_rs](https://github.com/namib-project/libcoap-rs/tree/main/libcoap) crate or another
20//! CoAP library written in pure Rust such as [coap-rs](https://github.com/covertness/coap-rs)
21//! instead.
22//!
23//! The TLDR for building libcoap-sys (and resolving the most common Build Issues)
24//! ------------------------------------------------------------------------------
25//! It is strongly recommended that you read the remainder of this page in order to fully understand
26//! the build process and possible causes of errors, especially if you're cross-compiling or
27//! building for embedded targets.
28//!
29//! However, if you lack the time to do so, the following instructions should work in most cases:
30//!
31//! 1. Add a dependency to this crate and add all features you need for your crate to work.
32//!    Call [`coap_startup_with_feature_checks()`] instead of [`coap_startup()`] during
33//!    initialization to ensure that all of these features are actually available in the linked
34//!    version of `libcoap`.
35//!
36//! 2. If you require DTLS support and run into `Required feature "dtls-(psk|pki|rpk|...)" is not
37//!    supported by libcoap` errors, manually select a DTLS library that supports all of your
38//!    required DTLS features by setting the `LIBCOAP_RS_DTLS_BACKEND` environment variable to your
39//!    desired choice (the library name in all-lowercase should work).
40//!
41//! 3. If you're building a binary crate (or tests, examples, ...) and are getting non-DTLS-related
42//!    `Required feature "<FEATURE>" is not supported by libcoap` errors, enable the `vendored`
43//!    feature to build and statically link a version of libcoap that supports exactly the features
44//!    you requested.
45//!
46//! 4. Inspect your dependency tree to determine whether you already have a DTLS library's sys-crate
47//!    (`openssl-sys`, `tinydtls-sys` or `mbedtls-sys-auto`) in your dependency tree.
48//!    If this is the case, enable the `dtls-<LIBRARY NAME>-sys` feature for all of them.
49//!    This may resolve issues related to linking multiple versions of the same library at once, and
50//!    could also help in reducing binary size.
51//!    
52//! If you're still unable to compile `libcoap-sys`, refer to the documentation below.
53//! If the documentation below does not solve your issue, feel free to open an issue
54//! [on GitHub](https://github.com/namib-project/libcoap-rs/) and ask for help.
55//!
56//! Optional Features
57//! --------------
58//! Most features specified in this crate's Cargo.toml directly correspond to a feature that can be
59//! enabled or disabled in libcoap's configure-script and/or CMake configuration, refer to the
60//! libcoap documentation for more details on these features.
61//!
62//! The `default` feature should match the default features enabled in the configure script of the
63//! minimum supported version of libcoap.
64//!
65//! Depending on the build system and linked version of libcoap, the features actually provided may
66//! differ from the ones indicated by the crate features.
67//! If you want to ensure that all features that are enabled for this crate are actually supported
68//! by the linked version of libcoap, you may call [`coap_startup_with_feature_checks()`].
69//!
70//! Aside from the features relating to libcoap functionality, the following features may also be
71//! enabled for this crate:
72//! - `vendored`: Build and statically link against a version of libcoap bundled with this crate
73//!     instead of using a system-provided one[^1].
74//! - `dtls-<LIBRARY NAME>-sys`: Allows the [vendored](#vendored-build-system) libcoap version to link against the
75//!     same version of a DTLS library that is used by the corresponding <LIBRARY NAME>-sys
76//!     crate[^2].
77//!     Note, however, that this does not imply that this DTLS library *will* be used, refer to
78//!     the [documentation below](#dtls-library-selection) for more information.
79//!
80//!     If a different build system than `vendored` is used, this feature is effectively a no-op.
81//! - `dtls-<LIBRARY NAME>-sys-vendored` instructs the sys-crate of the DTLS library corresponding
82//!     to the feature name to use a vendored version of the underlying library (implies
83//!     `dtls-<LIBRARY NAME>-sys`).
84//! - `dtls-(cid|psk|pki|pkcs11|rpk)`: Require support for specific DTLS features in `libcoap`.
85//!     These features can not be enabled explicitly while building `libcoap`, support for them is
86//!     automatically made available based on the used DTLS library (see
87//!     [the corresponding section below](#dtls-library-selection)).
88//!
89//!     Enabling these features will add appropriate checks during compile- and/or runtime
90//!     initialization to ensure these features are available in the used DTLS library (or panic
91//!     otherwise).
92//!
93//! [^1]: Note that when building for the ESP-IDF, this feature will be a no-op, as the version
94//!       provided by the ESP-IDF will always be used.
95//! [^2]: In the case of `mbedtls`, `mbedtls-sys-auto` is used instead, as `mbedtls-sys` is
96//!       unmaintained.
97//!
98//! Build Process
99//! -------------
100//! In general, `libcoap-sys` supports four different build systems, which will be explained in more
101//! detail in the following sections:
102//! - `vendored`: Build libcoap from source using a bundled version of the library (requires the
103//!               `vendored` feature to be enabled.
104//! - `pkgconfig`: Link against a system-provided version of libcoap, obtaining the library and
105//!                include paths using the `pkg-config` utility.
106//! - `manual`: Provide include and library directories+compiler/linker flags via environment
107//!             variables.
108//! - `espidf`: Build for the ESP family of microcontrollers using the ESP-IDF framework (used
109//!             instead of the regular `vendored` build if the build system is set to `vendored` and
110//!             building for an ESP-IDF Rust target).
111//!
112//! The build system that should be used can be specified manually by setting the
113//! `LIBCOAP_RS_BUILD_SYSTEM` environment variable to the corresponding value.
114//!
115//! If you have explicitly specified a build system and building using that system fails, no other
116//! system will be tried.
117//!
118//! If you do not explicitly provide a build system to use, the build script will follow these
119//! steps to determine a suitable build system:
120//!
121//! 1. If the `vendored` crate feature is enabled, or we are building for the ESP-IDF, act as if the
122//!    build system is set to `vendored`. If a vendored build is attempted and fails, return with an
123//!    error and do not try anything else.
124//! 2. Otherwise, try `pkgconfig` first.
125//! 3. If `pkgconfig` doesn't work, fall back to `manual` (which will fail if the environment
126//!    variables aren't set).
127//! 4. If `manual` doesn't work, return an error indicating the issues with all previously attempted
128//!    build systems.
129//!
130//! ## Generic Information (applies to all build systems)
131//! The following information applies to all build systems (although some specifics may be detailed
132//! in the respective build system's section).
133//!
134//! ### C Standard Library Functions
135//! Some `libcoap` functions utilize types from the C standard library (e.g., `sockaddr_in` in
136//! `coap_address_t`).
137//!
138//! For most targets, the data types defined in the [`libc`](https://docs.rs/libc/latest/libc/)
139//! crate will be used to provide those data types.
140//! However, some targets (especially embedded ones such as `espidf`) will use a different library
141//! instead, which may cause compilation issues in code that assumes `libc` data types to be
142//! compatible.
143//!
144//! For your convenience, this crate re-exports the used standard library crate as the `c_stdlib`
145//! module. For best interoperability, you should `use` this module instead of using the actual
146//! crates directly to import the required data types.
147//!
148//! ### DTLS Library Selection
149//!
150//! In order to provide DTLS support, `libcoap` must be combined with a DTLS library/backend.
151//! DTLS libraries are mutually exclusive, and multiple versions of `libcoap` linked against
152//! different DTLS libraries may be installed in a system simultaneously, so `libcoap-sys` must
153//! decide on a variant of `libcoap` to link against during build.
154//!
155//! While the default mechanism for determining a DTLS library differs between build systems, you
156//! may select a DTLS library explicitly by setting the `LIBCOAP_RS_DTLS_BACKEND` environment
157//! variable to any of the supported values (`gnutls`, `openssl`, `mbedtls`, `tinydtls`, or
158//! `wolfssl`). Refer to the build-system-specific documentation for information about supported
159//! DTLS libraries and specifics.
160//!
161//! Note that some DTLS-related features (such as `dtls-(cid|psk|pki|pkcs11|rpk)`) are dependent on
162//! the used DTLS backend, refer to [the `coap_encryption(3)` man page](https://libcoap.net/doc/reference/4.3.5/man_coap_encryption.html)
163//! for information on supported features for each DTLS library.
164//!
165//! ### Feature Support and Compile-Time/Initialization Checks
166//! During compilation, each build system will attempt to ensure that the used version of `libcoap`
167//! does in fact support all [features](#optional-features) that were enabled in `Cargo.toml`.
168//! The exact method differs based on each build system, but most will attempt to parse the
169//! `coap3/coap_defines.h` header file in order to determine missing features (with `espidf` being a
170//! notable exception).
171//!
172//! If a build system detects that a requested feature is missing, an appropriate error message will
173//! be returned. In most cases, these errors must be resolved by linking to a different version of
174//! `libcoap`.
175//!
176//! Unfortunately, for various reasons, this compile-time feature check may produce false
177//! positive and/or false negative results (especially when cross compiling[^3])
178//! is not available for all features (especially ones dependent on the DTLS library) and may not
179//! even be available at all on some platforms.
180//!
181//! Therefore, library users should assume that the compile-time checks may not provide accurate
182//! results, and should call [`coap_startup_with_feature_checks()`] during initialization to perform
183//! run-time checks for all requested features. This run-time check will always work and be
184//! accurate.
185//!
186//! Lastly, if you encounter a false positive error (i.e., a compile time error that indicates that
187//! some feature is missing, even though you are 100% certain that it is available), you may bypass
188//! the compile-time checks by setting `LIBCOAP_RS_BYPASS_COMPILE_FEATURE_CHECKS` to any non-zero
189//! value.
190//! Note, however, that this might lead to cryptic errors if your assumption was wrong and the
191//! feature is not available after all.
192//!
193//! In most cases, a false positive might be caused by the include paths/header files used for
194//! binding generation refering to a different version of `libcoap` than the one that is linked
195//! against, which could also cause difficult-to-debug issues and indicates a more severe problem
196//! with the build process.
197//!
198//! [^3]: For this reason, using this method while cross-compiling [is noted to be unsafe in `libcoap`'s documentation](https://libcoap.net/doc/reference/4.3.5/man_coap_supported.html).
199//!
200//! ## Vendored Build System
201//!
202//! The vendored build system uses a bundled version of libcoap (usually the latest stable version
203//! at the time of release) to build and statically link against.
204//! Under the hood, it uses the [`autotools`](https://docs.rs/autotools/latest/autotools/) crate to
205//! configure and run the build process, and you may therefore customize the build's compiler and
206//! linker flags by setting the environment variables used in `libcoap`'s `configure` script.
207//!
208//! This build system will enable only those features in `libcoap` that are requested as
209//! `Cargo.toml` features, and will explicitly disable all other ones.
210//!
211//! If a DTLS library is explicitly selected by the user, it will instruct libcoap to link against
212//! that library by setting the corresponding `--with-<LIBRARY NAME>` configure flag.
213//!
214//! If you enable the `dtls-<LIBRARY NAME>-sys` features and do not set the `<LIBRARY NAME>_CFLAGS`
215//! or `<LIBRARY NAME>_LIBS` environment variables, this build system will set these environment
216//! variables to ensure that if this DTLS library is the one that libcoap uses, we link against
217//! exactly the same version as used in the `<LIBRARY NAME>-sys` crate.
218//! This is especially relevant if those crates also provide a `vendored` version in order to avoid
219//! multiple versions of the same library being in use.
220//! If a different DTLS library is used, this feature should have no effect (it will set the
221//! environment variable, but `libcoap` will ignore it).
222//!
223//! If you do not specify a DTLS library, this build system will follow the same default order that
224//! libcoap does (gnutls > openssl > wolfssl > mbedtls > tinydtls), unless you enabled one of the
225//! `dtls-<LIBRARY NAME>-sys` features, in which case those will have priority.
226//! If multiple of these features are enabled, they are prioritized in the same order as used by
227//! `libcoap` (openssl > mbedtls > tinydtls).
228//!
229//! ## `pkg-config` Build System
230//!
231//! The `pkg-config` build system utilizes the `pkg-config` utility available on most Unix-like
232//! systems to link against a system-provided version of `libcoap`.
233//! To do so, it uses the [`pkg_config`](https://docs.rs/pkg-config/latest/pkg_config/) crate, and
234//! you may therefore customize the build process by setting the environment variables described in
235//! that library's documentation (which may be of special relevance if you try to cross compile).
236//!
237//! By default, it will probe `pkg-config` for a library with the name `libcoap-3`, which will
238//! usually symlink to the DTLS library variant of libcoap that was installed most recently.
239//! If you have explicitly requested use of a specific DTLS library, this build system will attempt
240//! to find the `libcoap-3-<LIBRARY NAME>` library instead.
241//!
242//! However, library selection does not take into account any other requested features (i.e., it
243//! will not check for feature support before generating the bindings), but will use header-based
244//! compile-time feature checks (see the general section) to ensure support for all required
245//! features after binding generation.
246//!
247//! The `dtls-<LIBRARY NAME>-sys` features have no effect on this build system, but note that static
248//! linking against a system-provided version of `libcoap` may cause issues if it causes multiple
249//! versions of the same DTLS library to be statically linked into the same Rust binary.
250//!
251//! ## `manual` Build System
252//!
253//! This build system is intended as a fallback solution if all other options fail. It will attempt
254//! to generate bindings and link against the version of `libcoap` that is described by the
255//! following environment variables:
256//!
257//! - `LIBCOAP_RS_INCLUDE_DIRS`: Paths that should be added to `clang`'s include path to search for
258//!                              header files (e.g., `/usr/local/include`, _not_
259//!                              `/usr/local/include/coap3`). Multiple values are separated by
260//!                              colons (`:`).
261//! - `LIBCOAP_RS_LIB_DIRS`:     Paths that should be added to `rustc`'s library path to search for
262//!                              object files to link against. Multiple values are separated by
263//!                              colons (`:`).
264//! - `LIBCOAP_RS_STATIC`:       Set to any non-zero and non-empty value to instruct `rustc` to use
265//!                              static linking instead of dynamic linking for `libcoap`.
266//! - `LIBCOAP_RS_ADDITIONAL_LIBRARIES`: Additional libraries (such as DTLS libraries) that should
267//!                              be linked against, separated by colons (`:`).
268//!                              Note that these will be added after `libcoap`, and that the order
269//!                              in which they are specified matters for most linkers.
270//!                              You may also request static linking by prepending `static=` to the
271//!                              library name.
272//!
273//! ## `espidf` Build System
274//!
275//! This build system will be used instead of the regular `vendored` build if you are building for
276//! targets that are based on the `ESP-IDF`.
277//!
278//! If `libcoap-sys` is a direct dependency, it will automatically enable the
279//! [`espressif/coap`](https://components.espressif.com/components/espressif/coap) component in
280//! order to instruct `esp-idf-sys` to compile and link `libcoap` and generate bindings for it.
281//!
282//! If you encounter errors that indicate that the `espressif/coap` component may not be enabled in
283//! the ESP-IDF, this could have the following reasons:
284//! - You may have to run `cargo clean`, as the `esp-idf-sys` build script does not always detect
285//!     changes in requested extra components properly.
286//! - `libcoap-sys` is a transient dependency: the `esp-idf-sys` build script
287//!     [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)
288//!     to determine which components to install.
289//!     In order to solve this, you can either add `libcoap-sys` as a direct dependency, or copy
290//!     this crate's `src/wrapper.h` file and add the following snippet to your own `Cargo.toml`.
291//!     ```cargo
292//!     [[package.metadata.esp-idf-sys.extra_components]]
293//!     remote_component = { name = "espressif/coap", version = "4.3.5~3" }
294//!     bindings_header = "src/wrapper.h"
295//!     ```
296//!     Afterward, run `cargo clean` (see the issue mentioned above) and try again.
297//!
298//! It will then parse the generated bindings file and re-export all symbols in `esp-idf-sys` that
299//! are related to `libcoap`.
300//!
301//! Note that `esp-idf-sys` may use a different version of `bindgen` than the other build systems
302//! and that bindings might differ slightly as a result.
303//!
304//! Unlike most other targets, this one will use `esp-idf-sys` instead of `libc` to provide its
305//! standard library types (see the generic information section above).
306
307// Bindgen translates the C headers, clippy's and rustfmt's recommendations are not applicable here.
308#![allow(clippy::all)]
309#![allow(non_camel_case_types)]
310#![allow(deref_nullptr)]
311#![allow(non_snake_case)]
312#![allow(non_upper_case_globals)]
313
314use core::ffi::c_void;
315
316use c_stdlib::{epoll_event, fd_set, memcmp, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, socklen_t, time_t};
317/// Re-export of the crate that provides libc data types used by libcoap.
318///
319/// In most cases, this will be libc, but on the ESP-IDF, it will be esp_idf_sys.
320#[cfg(target_os = "espidf")]
321pub use esp_idf_sys as c_stdlib;
322/// Re-export of the crate that provides libc data types used by libcoap.
323///
324/// In most cases, this will be libc, but on the ESP-IDF, it will be esp_idf_sys.
325#[cfg(not(target_os = "espidf"))]
326pub use libc as c_stdlib;
327// use dtls backend libraries in cases where they set our linker flags, otherwise rustc will
328// optimize them out, resulting in missing symbols.
329#[allow(unused_imports)]
330#[cfg(used_dtls_crate = "mbedtls")]
331use mbedtls_sys as _;
332#[allow(unused_imports)]
333#[cfg(used_dtls_crate = "openssl")]
334use openssl_sys as _;
335#[allow(unused_imports)]
336#[cfg(used_dtls_crate = "tinydtls")]
337use tinydtls_sys as _;
338
339// Add check whether the libcoap component is enabled when building for the ESP-IDF.
340#[cfg(all(target_os = "espidf", not(esp_idf_comp_espressif__coap_enabled)))]
341compile_error!(concat!(
342    "You are building libcoap-sys for an ESP-IDF target, but have not added the\n",
343    "espressif/coap remote component (see the libcoap-sys documentation for more information)"
344));
345
346include!(env!("BINDINGS_FILE"));
347
348/// Compares instances of coap_str_const_t and/or coap_string_t.
349///
350/// This macro is a reimplementation of the macro defined in coap_str.h, see
351/// <https://libcoap.net/doc/reference/develop/group__string.html#ga7f43c10b486dc6d45c37fcaf987d711b>.
352#[macro_export]
353macro_rules! coap_string_equal {
354    ( $string1:expr, $string2:expr ) => {{
355        use libcoap_sys::coap_string_equal_internal;
356        let s1 = $string1;
357        let s2 = $string2;
358        coap_string_equal_internal((*s1).s, (*s1).length, (*s2).s, (*s2).length)
359    }};
360}
361
362/// Internal only function for CoAP string comparisons.
363///
364/// *DO NOT USE THIS FUNCTION DIRECTLY.* It is only public because it is used by the
365/// [coap_string_equal] macro, which is the function you probably wanted to call instead.
366///
367/// # Safety
368///
369/// This function should not be called directly, use [coap_string_equal] instead.
370pub unsafe fn coap_string_equal_internal(
371    str1_ptr: *const u8,
372    str1_len: usize,
373    str2_ptr: *const u8,
374    str2_len: usize,
375) -> bool {
376    str1_len == str2_len
377        && (str1_len == 0
378            || !str1_ptr.is_null()
379                && !str2_ptr.is_null()
380                && memcmp(str1_ptr as *const c_void, str2_ptr as *const c_void, str1_len as _) == 0)
381}
382
383/// Execute feature check function and panic with an error message if the feature is not supported.
384///
385/// SAFETY: check_fn will be called, make sure to wrap this macro in unsafe if the function is
386/// unsafe.
387#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))]
388macro_rules! feature_check {
389    ($feature:literal, $check_fn:ident) => {
390        #[cfg(feature = $feature)]
391        // SAFETY: Function is always safe to call.
392        if $check_fn() != 1 {
393            panic!("Required feature \"{}\" is not supported by libcoap", $feature)
394        }
395    };
396    ( $(($feature:literal, $check_fn:ident)),* ) => {
397        $(
398            feature_check!($feature, $check_fn);
399        )*
400    }
401}
402
403#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))]
404macro_rules! dtls_backend_string {
405    ($backend:ident) => {
406        match $backend {
407            coap_tls_library_t_COAP_TLS_LIBRARY_OPENSSL => "openssl",
408            coap_tls_library_t_COAP_TLS_LIBRARY_GNUTLS => "gnutls",
409            coap_tls_library_t_COAP_TLS_LIBRARY_MBEDTLS => "mbedtls",
410            coap_tls_library_t_COAP_TLS_LIBRARY_TINYDTLS => "tinydtls",
411            coap_tls_library_t_COAP_TLS_LIBRARY_WOLFSSL => "wolfssl",
412            coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS => "notls",
413            _ => "unknown",
414        }
415    };
416}
417
418#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))]
419macro_rules! panic_wrong_dtls {
420    ($presumed_backend:ident, $detected_backend:ident) => {
421        panic!(
422            concat!(
423                "compile-time detected DTLS backend \"{}\" does not match run-time detected DTLS backend \"{}\".\n",
424                "This almost certainly means that libcoap-sys was linked against a different version of \n",
425                "libcoap than the one whose headers were used for binding generation."
426            ),
427            dtls_backend_string!($presumed_backend),
428            dtls_backend_string!($detected_backend)
429        )
430    };
431}
432
433/// Initialize the CoAP library and additionally perform runtime checks to ensure that required
434/// features (as enabled in `Cargo.toml`) are available and that the used DTLS library matches the
435/// one that was determined during compile-time.
436///
437/// You *should* prefer using this function over [`coap_startup()`], as without calling this function
438/// some of the features enabled using the Cargo features may not actually be available.
439///
440/// Either this function or [`coap_startup()`] must be run once before any libcoap function is called.
441///
442/// If you are absolutely 100% certain that all features you require are always available (or are
443/// prepared to deal with error return values/different behavior on your own if they aren't), you
444/// may use [`coap_startup()`] instead.
445// Make sure that if we're compiling for the ESP-IDF, this function is only compiled if the
446// libcoap component is installed in the ESP-IDF.
447// This way, these function calls will not cause missing function or struct definition errors that
448// clutter up the error log, and the only error message will be the way more descriptive
449// compile_error macro invocation at the start of this file.
450#[cfg(any(not(target_os = "espidf"), esp_idf_comp_espressif__coap_enabled))]
451pub fn coap_startup_with_feature_checks() {
452    unsafe {
453        feature_check!(
454            ("af-unix", coap_af_unix_is_supported),
455            ("async", coap_async_is_supported),
456            ("client", coap_client_is_supported),
457            ("dtls", coap_dtls_is_supported),
458            ("dtls-cid", coap_dtls_cid_is_supported),
459            ("dtls-psk", coap_dtls_psk_is_supported),
460            ("dtls-pki", coap_dtls_pki_is_supported),
461            ("dtls-pkcs11", coap_dtls_pkcs11_is_supported),
462            ("dtls-rpk", coap_dtls_rpk_is_supported),
463            ("epoll", coap_epoll_is_supported),
464            ("ipv4", coap_ipv4_is_supported),
465            ("ipv6", coap_ipv6_is_supported),
466            ("observe-persist", coap_observe_persist_is_supported),
467            ("oscore", coap_oscore_is_supported),
468            ("q-block", coap_q_block_is_supported),
469            ("server", coap_server_is_supported),
470            ("tcp", coap_tcp_is_supported),
471            ("thread-safe", coap_threadsafe_is_supported),
472            ("tls", coap_tls_is_supported),
473            ("websockets", coap_ws_is_supported),
474            ("secure-websockets", coap_wss_is_supported)
475        );
476    }
477
478    // ESP-IDF is missing the coap_tls_library_t type.
479    #[cfg(not(target_os = "espidf"))]
480    {
481        let presumed_dtls_backend = if cfg!(dtls_backend = "openssl") {
482            coap_tls_library_t_COAP_TLS_LIBRARY_OPENSSL
483        } else if cfg!(dtls_backend = "gnutls") {
484            coap_tls_library_t_COAP_TLS_LIBRARY_GNUTLS
485        } else if cfg!(dtls_backend = "mbedtls") {
486            coap_tls_library_t_COAP_TLS_LIBRARY_MBEDTLS
487        } else if cfg!(dtls_backend = "tinydtls") {
488            coap_tls_library_t_COAP_TLS_LIBRARY_TINYDTLS
489        } else if cfg!(dtls_backend = "wolfssl") {
490            coap_tls_library_t_COAP_TLS_LIBRARY_WOLFSSL
491        } else {
492            coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS
493        };
494
495        let actual_dtls_backend = unsafe { *coap_get_tls_library_version() }.type_;
496
497        if presumed_dtls_backend != coap_tls_library_t_COAP_TLS_LIBRARY_NOTLS
498            && actual_dtls_backend != presumed_dtls_backend
499        {
500            panic_wrong_dtls!(presumed_dtls_backend, actual_dtls_backend);
501        }
502    }
503
504    // SAFETY: Function is always safe to call.
505    unsafe { coap_startup() }
506}
507
508#[cfg(all(test, not(target_os = "espidf")))]
509mod tests {
510    use std::{
511        ffi::c_void,
512        net::{SocketAddr, UdpSocket},
513        os::raw::c_int,
514        sync::{Arc, Barrier},
515    };
516
517    use libc::{in6_addr, in_addr, sa_family_t, size_t, AF_INET, AF_INET6};
518
519    use super::*;
520
521    const COAP_TEST_RESOURCE_URI: &str = "test";
522    const COAP_TEST_RESOURCE_RESPONSE: &str = "Hello World!";
523
524    /// Creates a coap_address_t from a &SocketAddr.
525    fn coap_address_from_socketaddr(addr: &SocketAddr) -> coap_address_t {
526        match addr {
527            SocketAddr::V4(addr) => {
528                // addr is a bindgen-type union wrapper, so we can't assign to it directly and have
529                // to use a pointer instead.
530                // SAFETY: addr is not read before it is assigned properly, assignment cannot fail.
531                unsafe {
532                    let mut coap_addr = coap_address_t {
533                        size: std::mem::size_of::<sockaddr_in>() as socklen_t,
534                        addr: std::mem::zeroed(),
535                    };
536                    coap_addr.addr.sin = sockaddr_in {
537                        sin_family: AF_INET as sa_family_t,
538                        sin_port: addr.port().to_be(),
539                        sin_addr: in_addr {
540                            s_addr: u32::from_ne_bytes(addr.ip().octets()),
541                        },
542                        sin_zero: Default::default(),
543                    };
544                    coap_addr
545                }
546            },
547            SocketAddr::V6(addr) => {
548                // addr is a bindgen-type union wrapper, so we can't assign to it directly and have
549                // to use a pointer instead.
550                // SAFETY: addr is not read before it is assigned properly, assignment cannot fail.
551                unsafe {
552                    let mut coap_addr = coap_address_t {
553                        size: std::mem::size_of::<sockaddr_in6>() as socklen_t,
554                        addr: std::mem::zeroed(),
555                    };
556                    coap_addr.addr.sin6 = sockaddr_in6 {
557                        sin6_family: AF_INET6 as sa_family_t,
558                        sin6_port: addr.port().to_be(),
559                        sin6_addr: in6_addr {
560                            s6_addr: addr.ip().octets(),
561                        },
562                        sin6_flowinfo: addr.flowinfo(),
563                        sin6_scope_id: addr.scope_id(),
564                    };
565                    coap_addr
566                }
567            },
568        }
569    }
570
571    /// Response handler for the CoAP client/server test (client-side)
572    /// # Safety
573    /// Assumes all pointers to be valid and pointing to data structures containing valid data
574    /// according to the specification of the method handler function provided in libcoap.
575    /// Assumes that the application data in the CoAP context associated with the session is a
576    /// pointer to a boolean specifying whether a successful request was received (will be set to
577    /// true by this function).
578    unsafe extern "C" fn test_resource_handler(
579        _resource: *mut coap_resource_t,
580        session: *mut coap_session_t,
581        _incoming_pdu: *const coap_pdu_t,
582        _query: *const coap_string_t,
583        response_pdu: *mut coap_pdu_t,
584    ) {
585        let mut buf: [u8; 3] = [0; 3];
586        coap_add_option(
587            response_pdu,
588            COAP_OPTION_CONTENT_TYPE as coap_option_num_t,
589            coap_encode_var_safe(buf.as_mut_ptr(), buf.len(), COAP_MEDIATYPE_TEXT_PLAIN) as usize,
590            buf.as_ptr(),
591        );
592        coap_add_data(
593            response_pdu,
594            COAP_TEST_RESOURCE_RESPONSE.len(),
595            COAP_TEST_RESOURCE_RESPONSE.as_ptr(),
596        );
597        coap_set_app_data(coap_session_get_context(session), (&true) as *const bool as *mut c_void);
598        coap_pdu_set_code(response_pdu, coap_pdu_code_t_COAP_RESPONSE_CODE_CONTENT);
599    }
600
601    /// Response handler for the CoAP client/server test (client-side)
602    /// # Safety
603    /// Assumes all pointers to be valid and pointing to data structures containing valid data
604    /// according to the specification of the response handler function provided in libcoap.
605    /// Assumes that the application data in the CoAP context associated with the session is a
606    /// pointer to a boolean specifying whether a successful response was received (will be set to
607    /// true by this function).
608    unsafe extern "C" fn test_response_handler(
609        session: *mut coap_session_t,
610        _sent: *const coap_pdu_t,
611        received: *const coap_pdu_t,
612        _mid: coap_mid_t,
613    ) -> coap_response_t {
614        assert_eq!(coap_pdu_get_code(received), coap_pdu_code_t_COAP_RESPONSE_CODE_CONTENT);
615        let mut len: size_t = 0;
616        let mut data: *const u8 = std::ptr::null();
617        assert_ne!(coap_get_data(received, &mut len, &mut data), 0);
618        let data = std::slice::from_raw_parts(data, len);
619
620        assert_eq!(data, COAP_TEST_RESOURCE_RESPONSE.as_bytes());
621        coap_set_app_data(coap_session_get_context(session), (&true) as *const bool as *mut c_void);
622        return coap_response_t_COAP_RESPONSE_OK;
623    }
624
625    /// Creates a CoAP server that provides a single resource under COAP_TEST_RESOURCE_URI over the
626    /// supplied socket
627    fn run_coap_test_server(addr: &SocketAddr, barrier: Arc<Barrier>) {
628        // SAFETY: Null pointer is a valid parameter here.
629        let context = unsafe { coap_new_context(std::ptr::null()) };
630        assert!(!context.is_null());
631
632        let address: coap_address_t = coap_address_from_socketaddr(addr);
633
634        // SAFETY: We asserted that context != null, listen_addr is a reference and can therefore not be null.
635        let endpoint = unsafe { coap_new_endpoint(context, &address, coap_proto_t_COAP_PROTO_UDP) };
636        assert!(!endpoint.is_null());
637
638        // SAFETY: Since we use a string constant here, the arguments to the function are all valid.
639        let uri = unsafe { coap_new_str_const(COAP_TEST_RESOURCE_URI.as_ptr(), COAP_TEST_RESOURCE_URI.len()) };
640        assert!(!uri.is_null());
641
642        // SAFETY: We just asserted that uri is valid, COAP_RESOURCE_FLAGS_RELEASE_URI is valid because we will not free the uri ourselves.
643        let test_resource = unsafe { coap_resource_init(uri, COAP_RESOURCE_FLAGS_RELEASE_URI as c_int) };
644        assert!(!test_resource.is_null());
645
646        // SAFETY: We asserted that test_resource and context are valid, other pointers are always valid.
647        // The fact that we cast a constant to a mutable pointer is not problematic, because neither we
648        // nor libcoap ever mutate the pointer. This is necessary, because the underlying libcoap
649        // struct allows for mutable pointers to be set there, so that applications can use this to
650        // modify some application specific state.
651        unsafe {
652            coap_register_request_handler(
653                test_resource,
654                coap_request_t_COAP_REQUEST_GET,
655                Some(test_resource_handler),
656            );
657            coap_add_resource(context, test_resource);
658            coap_set_app_data(context, (&false) as *const bool as *mut c_void);
659        }
660
661        barrier.wait();
662        loop {
663            let time_spent_millis = unsafe { coap_io_process(context, 10000) };
664            // SAFETY: We are the only ones setting or accessing this value, so we know it is a
665            // const bool pointer (we have also set it to *false before and only ever set it to
666            // *true afterwards).
667            if unsafe { *(coap_get_app_data(context) as *const bool).as_ref().unwrap() } {
668                break;
669            }
670            if time_spent_millis == -1 || time_spent_millis >= 10000 {
671                panic!("Test timeout exceeded");
672            }
673        }
674        // SAFETY: Context is not referenced outside this function, and handlers (which get the
675        // context from libcoap) will not be called after the context is freed.
676        // This also seems to free all resources.
677        unsafe {
678            coap_free_context(context);
679        }
680    }
681
682    /// Test case that creates a basic coap server and makes a request to it from a separate context
683    #[test]
684    fn test_coap_client_server_basic() {
685        coap_startup_with_feature_checks();
686        // This will give us a SocketAddress with a port in the local port range automatically
687        // assigned by the operating system.
688        // Because the UdpSocket goes out of scope, the Port will be free for usage by libcoap.
689        // This seems to be the only portable way to get a port number assigned from the operating
690        // system, which is necessary here because of potential parallelisation (we can't just use
691        // the default CoAP port if multiple tests are run in parallel).
692        // It is assumed here that after unbinding the temporary socket, the OS will not reassign
693        // this port until we bind it again. This should work in most cases (unless we run on a
694        // system with very few free ports), because at least Linux will not reuse port numbers
695        // unless necessary, see https://unix.stackexchange.com/a/132524.
696        let server_address = UdpSocket::bind("localhost:0")
697            .expect("Failed to bind server socket")
698            .local_addr()
699            .expect("Failed to get server socket address");
700
701        let preparation_barrier = Arc::new(Barrier::new(2));
702
703        let server_address_clone = server_address.clone();
704        let preparation_barrier_clone = preparation_barrier.clone();
705        let server_thread_handle =
706            std::thread::spawn(move || run_coap_test_server(&server_address_clone, preparation_barrier_clone));
707
708        // SAFETY: Null pointer is a valid parameter here.
709        let context = unsafe { coap_new_context(std::ptr::null()) };
710        assert!(!context.is_null());
711
712        preparation_barrier.wait();
713
714        let server_address = coap_address_from_socketaddr(&server_address);
715
716        // SAFETY: null pointer is valid argument for local_if, server_address is guaranteed to be
717        // a correct value (conversion cannot fail), validity of context was asserted before.
718        let client_session =
719            unsafe { coap_new_client_session(context, std::ptr::null(), &server_address, coap_proto_t_COAP_PROTO_UDP) };
720
721        // SAFETY: context and client_session were asserted to be valid.
722        // Casting *const to *mut is fine because we don't mutate the value pointed to and the
723        // pointer is not used by libcoap (the application data pointer is intended to be used by
724        // client applications to store and/or modify their own data, which is why it is a mutable
725        // pointer in the first place).
726        // coap_request_pdu is asserted to be valid before it is used.
727        unsafe {
728            coap_register_response_handler(context, Some(test_response_handler));
729            coap_set_app_data(context, (&false) as *const bool as *mut c_void);
730            let coap_request_pdu = coap_new_pdu(
731                coap_pdu_type_t_COAP_MESSAGE_NON,
732                coap_pdu_code_t_COAP_REQUEST_CODE_GET,
733                client_session,
734            );
735            assert!(!coap_request_pdu.is_null());
736            assert_ne!(
737                coap_add_option(
738                    coap_request_pdu,
739                    COAP_OPTION_URI_PATH as coap_option_num_t,
740                    COAP_TEST_RESOURCE_URI.len(),
741                    COAP_TEST_RESOURCE_URI.as_ptr(),
742                ),
743                0
744            );
745            assert_ne!(coap_send(client_session, coap_request_pdu), COAP_INVALID_MID);
746        }
747
748        loop {
749            // SAFETY: context is asserted to be valid, no known side effects that would violate any guarantees
750            let time_spent_millis = unsafe { coap_io_process(context, 10000) };
751            // SAFETY: We are the only ones setting or accessing this value, so we know it is a
752            // const bool pointer (we have also set it to *false before and only ever set it to
753            // *true afterwards).
754            if unsafe { *(coap_get_app_data(context) as *const bool).as_ref().unwrap() } {
755                break;
756            }
757            if time_spent_millis == -1 || time_spent_millis >= 10000 {
758                panic!("Test timeout exceeded");
759            }
760        }
761        // SAFETY: Context is not references outside this function, and handlers (which get the
762        // context from libcoap) will not be called after the context is freed.
763        // This also seems to free all resources.
764        unsafe {
765            coap_free_context(context);
766        }
767        server_thread_handle.join().expect("Error waiting for server thread");
768    }
769}