aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore12
-rw-r--r--LICENSE19
-rw-r--r--Makefile24
-rw-r--r--README80
-rw-r--r--libnvxx/Makefile37
-rw-r--r--libnvxx/nvxx.3205
-rw-r--r--libnvxx/nvxx.cc944
-rw-r--r--libnvxx/nvxx.h32
-rw-r--r--libnvxx/nvxx_base.h550
-rw-r--r--libnvxx/tests/Makefile30
-rw-r--r--libnvxx/tests/nvxx_basic.cc571
11 files changed, 2504 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d00a1e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+*.swp
+*.o
+*.a
+*.so
+*.so.*
+*.pico
+*.core
+*.full
+*.debug
+.depend*
+Kyuafile
+/libnvxx/tests/nvxx_basic
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..874d31b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+this software, either in source code form or as a compiled binary, for any
+purpose, commercial or non-commercial, and by any means.
+
+In jurisdictions that recognize copyright laws, the author or authors of
+this software dedicate any and all copyright interest in the software to the
+public domain. We make this dedication for the benefit of the public at
+large and to the detriment of our heirs and successors. We intend this
+dedication to be an overt act of relinquishment in perpetuity of all present
+and future rights to this software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cbc6d1e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+# this software, either in source code form or as a compiled binary, for any
+# purpose, commercial or non-commercial, and by any means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors of
+# this software dedicate any and all copyright interest in the software to the
+# public domain. We make this dedication for the benefit of the public at
+# large and to the detriment of our heirs and successors. We intend this
+# dedication to be an overt act of relinquishment in perpetuity of all present
+# and future rights to this software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+PREFIX?= /usr/local
+SUBDIR= libnvxx
+
+.include <bsd.subdir.mk>
diff --git a/README b/README
new file mode 100644
index 0000000..37d70f9
--- /dev/null
+++ b/README
@@ -0,0 +1,80 @@
+libnvxx is a lightweight C++ wrapper around FreeBSD's libnv. it allows you to
+interoperate with C code that uses libnv, which is becoming fairly common on
+FreeBSD.
+
+priority is given to a clean and idiomatic C++ API for the library. this means
+in some cases the API is less efficient than it could be; for example, it
+sometimes requires data copies which are, strictly speaking, not necessary.
+this is not considered a problem in practice, since the overhead is still very
+low, and using libnv in a tight loop would be fairly unusual.
+
+to build and install the library:
+
+% make
+% su
+# make install
+# kyua test --kyuafile /usr/local/tests/nvxx/Kyuafile
+
+to use the library:
+
+#include <nvxx.h> and link with -lnvxx. if you link statically, you also need
+to link with -lnv.
+
+the basic API is very similar to the C libnv API:
+
+ auto nvl = bsd::nv_list();
+ nvl.add_number("the answer", 42);
+ assert(nvl.exists_number("the answer"));
+ std::print("{0}", nvl.get_number("the answer"));
+
+infrequently asked questions:
+
+Q: what version of FreeBSD does libnvxx require?
+A: libnvxx is developed and tested on FreeBSD 15.0-CURRENT. it will probably
+ work on earlier versions, as long as the C++ compiler is sufficiently
+ capable.
+
+Q: what version of C++ does libnvxx require?
+A: libnvxx requires C++23 (or later).
+
+Q: but isn't FreeBSD's implementation of C++23 rather incomplete?
+A: yes. however, libnvxx only uses the parts which are implemented in the
+ version of LLVM that FreeBSD ships in base.
+
+Q: doesn't the public API only require C++20?
+A: this may be the case, i haven't verified it. if so, patches to support
+ C++20 for the public API would probably be accepted.
+
+Q: why is the type called bsd::nv_list instead of bsd::nvlist?
+A: because <sys/nv_namespace.h> does "#define nvlist FreeBSD_nvlist", which
+ would cause issues with symbol names in the ABI.
+
+Q: i found a bug and i have a patch that fixes it.
+A: that's not a question.
+
+Q: i found a bug and i have a patch that fixes it?
+A: please open a pull request on the GitHub repository.
+
+Q: what if i found a bug but i don't have a patch?
+A: in that case please open an issue on the GitHub repository, preferably with
+ a minimal test case.
+
+Q: why wrap libnv instead of creating a C++ version from scratch?
+A: the primary use-case of libnv in C++ is to interoperate with existing C APIs
+ and protocols which use libnv. this requires using libnv in order to, for
+ example, pass nvlists between C and C++ code.
+
+ unlike in C, there is little reason to use libnv in C++ native code for data
+ storage, since we already have a rich template library for that.
+
+Q: why does the library abort on invalid operations instead of throwing an
+ exception?
+A: because this is how libnv works and there's no way to override it.
+
+Q: do you intend to submit this for inclusion in the FreeBSD base system?
+A: eventually, yes, but not until there are some actual users of it to justify
+ importing it.
+
+Q: is this why you're using <bsd.lib.mk> instead of something more sensible
+ like CMake?
+A: precisely!
diff --git a/libnvxx/Makefile b/libnvxx/Makefile
new file mode 100644
index 0000000..d201992
--- /dev/null
+++ b/libnvxx/Makefile
@@ -0,0 +1,37 @@
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+# this software, either in source code form or as a compiled binary, for any
+# purpose, commercial or non-commercial, and by any means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors of
+# this software dedicate any and all copyright interest in the software to the
+# public domain. We make this dedication for the benefit of the public at
+# large and to the detriment of our heirs and successors. We intend this
+# dedication to be an overt act of relinquishment in perpetuity of all present
+# and future rights to this software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+.include <bsd.opts.mk>
+
+PREFIX?= /usr/local
+LIB_CXX= nvxx
+LIBDIR= ${PREFIX}/lib
+INCLUDEDIR= ${PREFIX}/include
+SHLIB_MAJOR= 1
+INCS= nvxx.h nvxx_base.h
+SRCS= nvxx.cc
+CXXSTD= c++23
+CXXFLAGS+= -W -Wall -Wextra -Werror
+LDADD= -lnv
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.lib.mk>
diff --git a/libnvxx/nvxx.3 b/libnvxx/nvxx.3
new file mode 100644
index 0000000..555b8eb
--- /dev/null
+++ b/libnvxx/nvxx.3
@@ -0,0 +1,205 @@
+.\" This is free and unencumbered software released into the public domain.
+.\"
+.\" Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+.\" this software, either in source code form or as a compiled binary, for any
+.\" purpose, commercial or non-commercial, and by any means.
+.\"
+.\" In jurisdictions that recognize copyright laws, the author or authors of
+.\" this software dedicate any and all copyright interest in the software to the
+.\" public domain. We make this dedication for the benefit of the public at
+.\" large and to the detriment of our heirs and successors. We intend this
+.\" dedication to be an overt act of relinquishment in perpetuity of all present
+.\" and future rights to this software under copyright law.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+.\" AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+.\" ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+.\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+.Dd March 24, 2025
+.Dt NVXX 3
+.Os
+.Sh NAME
+.Nm nvxx
+.Nd C++ interface for
+.Xr nv 9
+.Sh LIBRARY
+.Lb libnvxx
+.Sh SYNOPSIS
+.In nvxx.h
+.\" unfortunately, mdoc is not really set up for documenting C++ classes.
+.Bd -literal
+namespace bsd {
+
+// exposition only
+template<typename T>
+using container-type = ...;
+
+// exposition only
+struct const_nv_list : <unspecified> {
+ const_nv_list();
+ explicit const_nv_list(::nvlist_t const *);
+ const_nv_list(const_nv_list const &);
+
+ const_nv_list &operator=(const_nv_list const &);
+ const_nv_list &operator=(nv_list const &);
+
+ ::nvlist_t const *ptr() const;
+
+ void dump(int fd) const;
+ void fdump(std::FILE *fp) const;
+
+ std::size_t packed_size() const;
+ std::vector<std::byte> pack() const;
+
+ std::error_code error() const;
+
+ explicit operator bool() const;
+
+ bool empty() const;
+ int flags() const;
+ bool in_array() const;
+
+ void send(int fd) const;
+
+ bool exists(std::string_view key) const;
+ bool exists_null(std::string_view key) const;
+ bool exists_bool(std::string_view key) const;
+ bool exists_number(std::string_view key) const;
+ bool exists_string(std::string_view key) const;
+ bool exists_binary(std::string_view key) const;
+ bool exists_nvlist(std::string_view key) const;
+ bool exists_descriptor(std::string_view key) const;
+
+ auto get_bool(std::string_view key) const -> bool;
+ auto get_number(std::string_view key) const -> std::uint64_t;
+ auto get_string(std::string_view key) const -> std::string_view;
+ auto get_descriptor(std::string_view key) const -> int;
+ auto get_nvlist(std::string_view key) const -> const_nv_list;
+
+ // exposition only
+ auto get_binary(std::string_view key) const -> container-type<std::byte const>;
+
+ bool exists_bool_array(std::string_view key) const;
+ bool exists_number_array(std::string_view key) const;
+ bool exists_string_array(std::string_view key) const;
+ bool exists_descriptor_array(std::string_view key) const;
+ bool exists_nvlist_array(std::string_view key) const;
+
+ // exposition only
+ auto get_bool_array(std::string_view key) const -> container-type<bool const>;
+ auto get_number_array(std::string_view key) const -> container-type<std::uint64_t const>;
+ auto get_string_array(std::string_view key) const -> container-type<std::string_view>;
+ auto get_descriptor_array(std::string_view key) const -> container-type<int const>;
+ auto get_nvlist_array(std::string_view key) const -> container-type<const_nv_list>;
+};
+
+// exposition only
+struct nv_list : <unspecified> {
+ explicit nv_list(int flags = 0);
+ explicit nv_list(::nvlist_t *);
+ nv_list(nv_list const &);
+ nv_list(nv_list &&);
+
+ nv_list &operator=(nv_list const &);
+ nv_list &operator=(nv_list &&);
+
+ ::nvlist_t *ptr();
+ ::nvlist_t const *ptr() const;
+
+ ::nvlist_t *release() &&;
+
+ void set_error(int error);
+
+ void free(std::string_view name);
+
+ operator const_nv_list() const;
+
+ void add_null(std::string_view key);
+ void add_bool(std::string_view key, bool value);
+ void add_number(std::string_view key, std::uint64_t);
+ void add_string(std::string_view key, std::string_view);
+ void add_descriptor(std::string_view key, int);
+ void add_nvlist(std::string_view key, const_nv_list const &);
+ void add_binary(std::string_view key, std::span<std::byte const>);
+
+ void move_string(std::string_view key, char *);
+ void move_descriptor(std::string_view key, int);
+ void move_binary(std::string_view key, std::span<std::byte>);
+ void move_nvlist(std::string_view key, nv_list &&);
+ void move_nvlist(std::string_view key, ::nvlist_t *);
+
+ void free_null(std::string_view key);
+ void free_bool(std::string_view key);
+ void free_number(std::string_view key);
+ void free_string(std::string_view key);
+ void free_descriptor(std::string_view key);
+ void free_binary(std::string_view key);
+ void free_nvlist(std::string_view key);
+
+ auto take_bool(std::string_view key) -> bool;
+ auto take_number(std::string_view key) -> std::uint64_t;
+ auto take_string(std::string_view key) -> std::string;
+ auto take_descriptor(std::string_view key) -> int;
+ auto take_nvlist(std::string_view key) -> nv_list;
+
+ void move_bool_array(std::string_view key, std::span<bool>);
+ void move_number_array(std::string_view key, std::span<std::uint64_t>);
+ void move_string_array(std::string_view key, std::span<char *>);
+ void move_descriptor_array(std::string_view key, std::span<int>);
+ void move_nvlist_array(std::string_view key, std::span<::nvlist_t *>);
+
+ void append_bool_array(std::string_view key, bool);
+ void append_number_array(std::string_view key, std::uint64_t);
+ void append_string_array(std::string_view key, std::string_view);
+ void append_descriptor_array(std::string_view key, int);
+ void append_nvlist_array(std::string_view key, const_nv_list const &);
+
+ void free_bool_array(std::string_view key);
+ void free_number_array(std::string_view key);
+ void free_string_array(std::string_view key);
+ void free_nvlist_array(std::string_view key);
+ void free_descriptor_array(std::string_view key);
+
+ void add_bool_array(std::string_view key, std::span<bool const>);
+ void add_number_array(std::string_view key, std::span<std::uint64_t const>);
+ void add_string_array(std::string_view key, std::span<std::string_view const>);
+ void add_descriptor_array(std::string_view key, std::span<int const>);
+ void add_nvlist_array(std::string_view key, std::span<const_nv_list const>);
+ void add_nvlist_array(std::string_view key, std::span<nv_list const>);
+
+ // exposition only
+
+ template<std::ranges::range Range>
+ void add_bool_range(std::string_view key, Range &&);
+
+ template<std::ranges::range Range>
+ void add_number_range(std::string_view key, Range &&);
+
+ template<std::ranges::range Range>
+ void add_binary_range(std::string_view key, Range &&);
+
+ template<std::ranges::range Range>
+ void add_string_range(std::string_view key, _Range &&);
+
+ // exposition only
+ auto take_bool_array(std::string_view key) -> container-type<bool>;
+ auto take_number_array(std::string_view key) -> container-type<std::uint64_t>;
+ auto take_string_array(std::string_view key) -> container-type<std::string>;
+ auto take_nvlist_array(std::string_view key) -> container-type<nv_list>;
+ auto take_descriptor_array(std::string_view __key) -> container-type<int>;
+ auto take_binary(std::string_view key) -> container-type<std::byte>;
+};
+
+} // namespace bsd
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+library provides a C++ wrapper around the
+.Xr nv 9
+C library.
+The library is ABI compatible with the C library, in the case that it can both
+consume and produce pointers of type
+.Vt nvlist_t .
diff --git a/libnvxx/nvxx.cc b/libnvxx/nvxx.cc
new file mode 100644
index 0000000..1cd5f84
--- /dev/null
+++ b/libnvxx/nvxx.cc
@@ -0,0 +1,944 @@
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+ * this software, either in source code form or as a compiled binary, for any
+ * purpose, commercial or non-commercial, and by any means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors of
+ * this software dedicate any and all copyright interest in the software to the
+ * public domain. We make this dedication for the benefit of the public at
+ * large and to the detriment of our heirs and successors. We intend this
+ * dedication to be an overt act of relinquishment in perpetuity of all present
+ * and future rights to this software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <cerrno>
+#include <cassert>
+
+#include "nvxx.h"
+
+namespace {
+
+/*
+ * Some useful helper types.
+ */
+
+template<typename T>
+struct ptr_guard {
+ ptr_guard(T *ptr_) : ptr(ptr_) {}
+
+ ~ptr_guard() {
+ std::free(ptr);
+ }
+
+ T *ptr;
+};
+
+template<typename T>
+auto construct = std::views::transform([] (auto &&value) {
+ return T(std::forward<decltype(value)>(value));
+});
+
+}
+
+namespace bsd {
+
+/*
+ * const_nv_list
+ */
+
+const_nv_list::const_nv_list() noexcept
+ : __nv_list_base(nullptr, nvlist_owning::non_owning)
+{
+}
+
+// const_cast is safe here since a non-owning nvlist is never modified.
+const_nv_list::const_nv_list(::nvlist_t const *nvl) noexcept
+ : __nv_list_base(const_cast<::nvlist_t *>(nvl),
+ nvlist_owning::non_owning)
+{
+}
+
+const_nv_list::const_nv_list(const_nv_list const &other) noexcept
+ : __nv_list_base(other.__m_nv, nvlist_owning::non_owning)
+{
+}
+
+const_nv_list &
+const_nv_list::operator=(const_nv_list const &other) noexcept
+{
+ if (this != &other) {
+ /* This is not a leak since we never own the nvlist_t. */
+ __m_nv = other.__m_nv;
+ }
+
+ return *this;
+}
+
+const_nv_list &
+const_nv_list::operator=(nv_list const &other) noexcept
+{
+ /* This is not a leak since we never own the nvlist_t. */
+ __m_nv = other.__m_nv;
+ return *this;
+}
+
+::nvlist_t const *
+const_nv_list::ptr() const
+{
+ return __m_nv;
+}
+
+/*
+ * nv_list
+ */
+
+nv_list::nv_list(int flags)
+ : __nv_list_base(flags)
+{
+ if (__m_nv == nullptr)
+ throw std::system_error(
+ std::error_code(errno, std::system_category()));
+}
+
+nv_list::nv_list(::nvlist_t *nvl) noexcept
+ : __nv_list_base(nvl, nvlist_owning::owning)
+{
+}
+
+nv_list::nv_list(nv_list const &other)
+ : __nv_list_base(::nvlist_clone(other.__m_nv),
+ nvlist_owning::owning)
+{
+ if (__m_nv == nullptr)
+ throw std::system_error(
+ std::error_code(errno, std::system_category()));
+}
+
+nv_list::nv_list(nv_list &&other) noexcept
+ : __nv_list_base(std::exchange(other.__m_nv, nullptr),
+ nvlist_owning::owning)
+{
+}
+
+nv_list &
+nv_list::operator=(nv_list const &other)
+{
+ if (this != &other) {
+ auto *clone = nvlist_clone(other.__m_nv);
+ if (clone == nullptr)
+ throw std::system_error(
+ std::error_code(errno,
+ std::system_category()));
+ __free_nv();
+ __m_nv = clone;
+ __m_owning = nvlist_owning::owning;
+ }
+
+ return *this;
+}
+
+nv_list &
+nv_list::operator=(nv_list &&other) noexcept
+{
+ if (this != &other) {
+ __m_nv = std::exchange(other.__m_nv, nullptr);
+ __m_owning = nvlist_owning::owning;
+ }
+
+ return *this;
+}
+
+::nvlist_t *
+nv_list::ptr()
+{
+ return __m_nv;
+}
+
+::nvlist_t const *
+nv_list::ptr() const
+{
+ return __m_nv;
+}
+
+::nvlist_t *
+nv_list::release() &&
+{
+ return std::exchange(__m_nv, nullptr);
+}
+
+nv_list
+nv_list::unpack(std::span<std::byte> data, int flags)
+{
+ if (auto nv = ::nvlist_unpack(std::ranges::data(data),
+ std::ranges::size(data),
+ flags);
+ nv != nullptr) {
+ return nv_list(nv);
+ }
+
+ throw std::system_error(
+ std::error_code(errno, std::system_category()));
+}
+
+nv_list
+nv_list::recv(int fd, int flags)
+{
+ if (auto *nv = ::nvlist_recv(fd, flags); nv != nullptr)
+ return nv_list(nv);
+
+ throw std::system_error(
+ std::error_code(errno, std::system_category()));
+}
+
+nv_list
+nv_list::xfer(int fd, nv_list &&nvl, int flags)
+{
+ if (auto *nv = ::nvlist_xfer(fd, nvl.__m_nv, flags);
+ nv != nullptr) {
+ // nvlist_xfer destroys the original list
+ nvl.__m_nv = nullptr;
+ return nv_list(nv);
+ }
+
+ throw std::system_error(
+ std::error_code(errno, std::system_category()));
+}
+
+} // namespace bsd
+
+namespace bsd::__detail {
+
+/*
+ * __nv_list_base
+ */
+
+__nv_list_base::__nv_list_base(int flags)
+ : __m_nv(::nvlist_create(flags))
+ , __m_owning(nvlist_owning::owning)
+{
+ if (__m_nv == nullptr)
+ throw std::system_error(
+ std::error_code(errno, std::system_category()));
+}
+
+__nv_list_base::__nv_list_base(nvlist_t *nv, nvlist_owning owning)
+ : __m_nv(nv)
+ , __m_owning(owning)
+{
+ assert(nv);
+}
+
+__nv_list_base::~__nv_list_base()
+{
+ __free_nv();
+}
+
+void
+__nv_list_base::__free_nv() noexcept
+{
+ if ((__m_nv != nullptr) &&
+ (__m_owning == nvlist_owning::owning))
+ ::nvlist_destroy(__m_nv);
+}
+
+/*
+ * __const_nv_list
+ */
+
+std::error_code
+__const_nv_list::error() const noexcept
+{
+ if (auto const err = nvlist_error(__m_nv); err != 0)
+ return std::make_error_code(std::errc(err));
+ return {};
+}
+
+bool
+__const_nv_list::exists(std::string_view key) const
+{
+ return ::nvlist_exists(__m_nv, std::string(key).c_str());
+}
+
+bool
+__const_nv_list::empty() const noexcept
+{
+ return ::nvlist_empty(__m_nv);
+}
+
+int
+__const_nv_list::flags() const noexcept
+{
+ return ::nvlist_flags(__m_nv);
+}
+
+bool
+__const_nv_list::in_array() const noexcept
+{
+ return ::nvlist_in_array(__m_nv);
+}
+
+__const_nv_list::operator bool() const noexcept
+{
+ return ::nvlist_error(__m_nv) == 0;
+}
+
+void
+__const_nv_list::send(int fd) const
+{
+ if (::nvlist_send(fd, __m_nv) == 0)
+ return;
+
+ throw std::system_error(error());
+}
+
+void
+__const_nv_list::dump(int fd) const noexcept
+{
+ ::nvlist_dump(__m_nv, fd);
+}
+
+void
+__const_nv_list::fdump(std::FILE *__fp) const noexcept
+{
+ ::nvlist_fdump(__m_nv, __fp);
+}
+
+std::size_t
+__const_nv_list::packed_size() const noexcept
+{
+ return ::nvlist_size(__m_nv);
+}
+
+std::vector<std::byte>
+__const_nv_list::pack() const
+{
+ auto size = std::size_t{};
+
+ if (auto *data = nvlist_pack(__m_nv, &size); data != nullptr) {
+ auto bytes = ptr_guard(static_cast<std::byte *>(data));
+ return {bytes.ptr, bytes.ptr + size};
+ }
+
+ throw std::system_error(error());
+}
+
+/*
+ * __nv_list
+ */
+
+
+void
+__nv_list::set_error(int error) noexcept
+{
+ ::nvlist_set_error(__m_nv, error);
+}
+
+__nv_list::operator const_nv_list() const
+{
+ return const_nv_list(__m_nv);
+}
+
+void
+__nv_list::free(std::string_view key)
+{
+ ::nvlist_free(__m_nv, std::string(key).c_str());
+}
+
+/*
+ * null operations
+ */
+
+bool
+__const_nv_list::exists_null(std::string_view key) const
+{
+ return ::nvlist_exists_null(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::add_null(std::string_view key)
+{
+ ::nvlist_add_null(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::free_null(std::string_view key)
+{
+ ::nvlist_free_null(__m_nv, std::string(key).c_str());
+}
+
+/*
+ * bool operations
+ */
+
+void
+__nv_list::add_bool(std::string_view key, bool value)
+{
+ ::nvlist_add_bool(__m_nv, std::string(key).c_str(), value);
+}
+
+bool
+__const_nv_list::exists_bool(std::string_view key) const
+{
+ return ::nvlist_exists_bool(__m_nv, std::string(key).c_str());
+}
+
+bool
+__const_nv_list::get_bool(std::string_view key) const
+{
+ return ::nvlist_get_bool(__m_nv, std::string(key).c_str());
+}
+
+bool
+__nv_list::take_bool(std::string_view key)
+{
+ return ::nvlist_take_bool(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::free_bool(std::string_view key)
+{
+ ::nvlist_free_bool(__m_nv, std::string(key).c_str());
+}
+
+bool
+__const_nv_list::exists_bool_array(std::string_view key) const
+{
+ return ::nvlist_exists_bool_array(__m_nv, std::string(key).c_str());
+}
+
+std::span<bool const>
+__const_nv_list::get_bool_array(std::string_view key) const
+{
+ auto nitems = std::size_t{};
+ auto *data = ::nvlist_get_bool_array(__m_nv,
+ std::string(key).c_str(),
+ &nitems);
+ return {data, nitems};
+}
+
+std::vector<bool>
+__nv_list::take_bool_array(std::string_view key)
+{
+ auto nitems = std::size_t{};
+ auto ptr = ptr_guard(::nvlist_take_bool_array(__m_nv,
+ std::string(key).c_str(),
+ &nitems));
+ return std::vector<bool>(ptr.ptr, ptr.ptr + nitems);
+}
+
+void
+__nv_list::add_bool_array(std::string_view key,
+ std::span<bool const> value)
+{
+ ::nvlist_add_bool_array(__m_nv,
+ std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::move_bool_array(std::string_view key, std::span<bool> value)
+{
+ ::nvlist_move_bool_array(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::append_bool_array(std::string_view key, bool value)
+{
+ ::nvlist_append_bool_array(__m_nv, std::string(key).c_str(), value);
+}
+
+void
+__nv_list::free_bool_array(std::string_view key)
+{
+ ::nvlist_free_bool_array(__m_nv, std::string(key).c_str());
+}
+
+/*
+ * number operations
+ */
+
+void
+__nv_list::add_number(std::string_view key, std::uint64_t value)
+{
+ ::nvlist_add_number(__m_nv, std::string(key).c_str(), value);
+}
+
+bool
+__const_nv_list::exists_number(std::string_view key) const
+{
+ return ::nvlist_exists_number(__m_nv, std::string(key).c_str());
+}
+
+std::uint64_t
+__const_nv_list::get_number(std::string_view key) const
+{
+ return ::nvlist_get_number(__m_nv, std::string(key).c_str());
+}
+
+std::uint64_t
+__nv_list::take_number(std::string_view key)
+{
+ return ::nvlist_take_number(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::free_number(std::string_view key)
+{
+ ::nvlist_free_number(__m_nv, std::string(key).c_str());
+}
+
+bool
+__const_nv_list::exists_number_array(std::string_view key) const
+{
+ return ::nvlist_exists_number_array(__m_nv, std::string(key).c_str());
+}
+
+std::span<std::uint64_t const>
+__const_nv_list::get_number_array(std::string_view key) const
+{
+ auto nitems = std::size_t{};
+ auto *data = ::nvlist_get_number_array(
+ __m_nv, std::string(key).c_str(), &nitems);
+ return {data, nitems};
+}
+
+std::vector<std::uint64_t>
+__nv_list::take_number_array(std::string_view key)
+{
+ auto nitems = std::size_t{};
+ auto ptr = ptr_guard(
+ ::nvlist_take_number_array(__m_nv,
+ std::string(key).c_str(),
+ &nitems));
+ return {ptr.ptr, ptr.ptr + nitems};
+}
+
+void
+__nv_list::add_number_array(std::string_view key,
+ std::span<std::uint64_t const> value)
+{
+ ::nvlist_add_number_array(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::move_number_array(std::string_view key,
+ std::span<std::uint64_t> value)
+{
+ ::nvlist_move_number_array(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::append_number_array(std::string_view key, std::uint64_t value)
+{
+ ::nvlist_append_number_array(__m_nv, std::string(key).c_str(), value);
+}
+
+void
+__nv_list::free_number_array(std::string_view key)
+{
+ ::nvlist_free_number_array(__m_nv, std::string(key).c_str());
+}
+
+/*
+ * string operations
+ */
+
+void
+__nv_list::add_string(std::string_view key, std::string_view value)
+{
+ ::nvlist_add_string(__m_nv,
+ std::string(key).c_str(),
+ std::string(value).c_str());
+}
+
+void
+__nv_list::move_string(std::string_view key, char *value)
+{
+ ::nvlist_move_string(__m_nv, std::string(key).c_str(), value);
+}
+
+bool
+__const_nv_list::exists_string(std::string_view key) const
+{
+ return nvlist_exists_string(__m_nv, std::string(key).c_str());
+}
+
+std::string_view
+__const_nv_list::get_string(std::string_view key) const
+{
+ return nvlist_get_string(__m_nv, std::string(key).c_str());
+}
+
+std::string
+__nv_list::take_string(std::string_view key)
+{
+ return nvlist_take_string(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::free_string(std::string_view key)
+{
+ nvlist_free_string(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::add_string_array(std::string_view key,
+ std::span<std::string_view const> value)
+{
+ // nvlist_add_string_array expects an array of NUL-terminated
+ // C strings.
+
+ auto strings = value
+ | construct<std::string>()
+ | std::ranges::to<std::vector>();
+
+ auto ptrs = strings
+ | std::views::transform(&std::string::c_str)
+ | std::ranges::to<std::vector>();
+
+ nvlist_add_string_array(__m_nv, std::string(key).c_str(),
+ ptrs.data(), ptrs.size());
+}
+
+void
+__nv_list::move_string_array(std::string_view key,
+ std::span<char *> value)
+{
+ ::nvlist_move_string_array(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::append_string_array(std::string_view key, std::string_view value)
+{
+ ::nvlist_append_string_array(__m_nv,
+ std::string(key).c_str(),
+ std::string(value).c_str());
+}
+
+bool
+__const_nv_list::exists_string_array(std::string_view key) const
+{
+ return nvlist_exists_string_array(__m_nv, std::string(key).c_str());
+}
+
+std::vector<std::string_view>
+__const_nv_list::get_string_array(std::string_view key) const
+{
+ auto nitems = std::size_t{};
+ auto *data = nvlist_get_string_array(__m_nv, std::string(key).c_str(),
+ &nitems);
+ return std::span(data, data + nitems)
+ | construct<std::string_view>()
+ | std::ranges::to<std::vector>();
+}
+
+std::vector<std::string>
+__nv_list::take_string_array(std::string_view key)
+{
+ auto nitems = std::size_t{};
+ auto *data = nvlist_take_string_array(__m_nv, std::string(key).c_str(),
+ &nitems);
+ return std::span(data, data + nitems)
+ | construct<std::string>()
+ | std::ranges::to<std::vector>();
+}
+
+void
+__nv_list::free_string_array(std::string_view key)
+{
+ ::nvlist_free_string_array(__m_nv, std::string(key).c_str());
+}
+
+/*
+ * nv_list operations
+ */
+
+void
+__nv_list::add_nvlist(std::string_view key, const_nv_list const &other)
+{
+ nvlist_add_nvlist(__m_nv, std::string(key).c_str(), other.__m_nv);
+}
+
+void
+__nv_list::move_nvlist(std::string_view key, nv_list &&value)
+{
+ ::nvlist_move_nvlist(__m_nv, std::string(key).c_str(),
+ std::exchange(value.__m_nv, nullptr));
+}
+
+void
+__nv_list::move_nvlist(std::string_view key, ::nvlist_t *value)
+{
+ ::nvlist_move_nvlist(__m_nv, std::string(key).c_str(), value);
+}
+
+bool
+__const_nv_list::exists_nvlist(std::string_view key) const
+{
+ return nvlist_exists_nvlist(__m_nv, std::string(key).c_str());
+}
+
+const_nv_list
+__const_nv_list::get_nvlist(std::string_view key) const
+{
+ auto nvl = nvlist_get_nvlist(__m_nv, std::string(key).c_str());
+ return const_nv_list(nvl);
+}
+
+nv_list
+__nv_list::take_nvlist(std::string_view key)
+{
+ auto nvl = nvlist_take_nvlist(__m_nv, std::string(key).c_str());
+ return nv_list(nvl);
+}
+
+void
+__nv_list::free_nvlist(std::string_view key)
+{
+ nvlist_free_nvlist(__m_nv, std::string(key).c_str());
+}
+
+bool
+__const_nv_list::exists_nvlist_array(std::string_view key) const
+{
+ return nvlist_exists_nvlist_array(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::add_nvlist_array(std::string_view key,
+ std::span<const_nv_list const> value)
+{
+ auto ptrs = value
+ | std::views::transform(&const_nv_list::__m_nv)
+ | std::ranges::to<std::vector>();
+
+ ::nvlist_add_nvlist_array(__m_nv, std::string(key).c_str(),
+ ptrs.data(), ptrs.size());
+}
+
+void
+__nv_list::add_nvlist_array(std::string_view key,
+ std::span<nv_list const> value)
+{
+ auto ptrs = value
+ | std::views::transform(&nv_list::__m_nv)
+ | std::ranges::to<std::vector>();
+
+ nvlist_add_nvlist_array(__m_nv, std::string(key).c_str(),
+ ptrs.data(), ptrs.size());
+}
+
+void
+__nv_list::move_nvlist_array(std::string_view key,
+ std::span<::nvlist_t *> value)
+{
+ ::nvlist_move_nvlist_array(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::append_nvlist_array(std::string_view key,
+ const_nv_list const &value)
+{
+ ::nvlist_append_nvlist_array(__m_nv, std::string(key).c_str(),
+ value.__m_nv);
+}
+
+std::vector<const_nv_list>
+__const_nv_list::get_nvlist_array(std::string_view key) const
+{
+ auto nitems = std::size_t{};
+ auto *data = nvlist_get_nvlist_array(__m_nv, std::string(key).c_str(),
+ &nitems);
+ return {std::from_range,
+ std::span(data, nitems) | construct<const_nv_list>()};
+}
+
+std::vector<nv_list>
+__nv_list::take_nvlist_array(std::string_view key)
+{
+ auto nitems = std::size_t{};
+ auto ptr = ptr_guard(
+ ::nvlist_take_nvlist_array(__m_nv,
+ std::string(key).c_str(),
+ &nitems));
+ return {std::from_range,
+ std::span(ptr.ptr, nitems) | construct<nv_list>()};
+}
+
+void
+__nv_list::free_nvlist_array(std::string_view key)
+{
+ ::nvlist_free_nvlist_array(__m_nv, std::string(key).c_str());
+}
+
+/*
+ * descriptor operations
+ */
+
+int
+__const_nv_list::get_descriptor(std::string_view key) const
+{
+ return ::nvlist_get_descriptor(__m_nv, std::string(key).c_str());
+}
+
+int
+__nv_list::take_descriptor(std::string_view key)
+{
+ return ::nvlist_take_descriptor(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::add_descriptor(std::string_view key, int value)
+{
+ ::nvlist_add_descriptor(__m_nv, std::string(key).c_str(), value);
+}
+
+void
+__nv_list::move_descriptor(std::string_view key, int value)
+{
+ ::nvlist_move_descriptor(__m_nv, std::string(key).c_str(), value);
+}
+
+void
+__nv_list::free_descriptor(std::string_view key)
+{
+ ::nvlist_free_descriptor(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::append_descriptor_array(std::string_view key, int value)
+{
+ ::nvlist_append_descriptor_array(__m_nv,
+ std::string(key).c_str(),
+ value);
+}
+
+void
+__nv_list::add_descriptor_array(std::string_view key,
+ std::span<int const> value)
+{
+ ::nvlist_add_descriptor_array(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::move_descriptor_array(std::string_view key, std::span<int> value)
+{
+ ::nvlist_add_descriptor_array(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+std::span<int const>
+__const_nv_list::get_descriptor_array(std::string_view key) const
+{
+ auto nitems = std::size_t{};
+ auto *data = ::nvlist_get_descriptor_array(
+ __m_nv, std::string(key).c_str(), &nitems);
+ return {data, nitems};
+}
+
+void
+__nv_list::free_descriptor_array(std::string_view key)
+{
+ ::nvlist_free_descriptor_array(__m_nv, std::string(key).c_str());
+}
+
+bool
+__const_nv_list::exists_descriptor(std::string_view key) const
+{
+ return ::nvlist_exists_descriptor(__m_nv, std::string(key).c_str());
+}
+
+bool
+__const_nv_list::exists_descriptor_array(std::string_view key) const
+{
+ return ::nvlist_exists_descriptor_array(__m_nv,
+ std::string(key).c_str());
+}
+
+std::vector<int>
+__nv_list::take_descriptor_array(std::string_view key)
+{
+ auto nitems = std::size_t{};
+ auto ptr = ptr_guard(
+ ::nvlist_take_descriptor_array(__m_nv,
+ std::string(key).c_str(),
+ &nitems));
+ return {ptr.ptr, ptr.ptr + nitems};
+}
+
+/*
+ * binary operations
+ */
+
+bool
+__const_nv_list::exists_binary(std::string_view key) const
+{
+ return ::nvlist_exists_binary(__m_nv, std::string(key).c_str());
+}
+
+void
+__nv_list::add_binary(std::string_view key, std::span<std::byte const> value)
+{
+ ::nvlist_add_binary(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::move_binary(std::string_view key, std::span<std::byte> value)
+{
+ ::nvlist_move_binary(__m_nv, std::string(key).c_str(),
+ std::ranges::data(value),
+ std::ranges::size(value));
+}
+
+void
+__nv_list::free_binary(std::string_view key)
+{
+ ::nvlist_free_binary(__m_nv, std::string(key).c_str());
+}
+
+std::span<std::byte const>
+__const_nv_list::get_binary(std::string_view key) const
+{
+ auto size = std::size_t{};
+ auto *data = nvlist_get_binary(__m_nv, std::string(key).c_str(),
+ &size);
+ return {static_cast<std::byte const *>(data), size};
+}
+
+std::vector<std::byte>
+__nv_list::take_binary(std::string_view key)
+{
+ auto size = std::size_t{};
+ auto *data = ::nvlist_take_binary(__m_nv,
+ std::string(key).c_str(),
+ &size);
+ auto ptr = ptr_guard(static_cast<std::byte *>(data));
+ return {ptr.ptr, ptr.ptr + size};
+}
+
+} // namespace bsd::__detail
diff --git a/libnvxx/nvxx.h b/libnvxx/nvxx.h
new file mode 100644
index 0000000..7c938ce
--- /dev/null
+++ b/libnvxx/nvxx.h
@@ -0,0 +1,32 @@
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+ * this software, either in source code form or as a compiled binary, for any
+ * purpose, commercial or non-commercial, and by any means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors of
+ * this software dedicate any and all copyright interest in the software to the
+ * public domain. We make this dedication for the benefit of the public at
+ * large and to the detriment of our heirs and successors. We intend this
+ * dedication to be an overt act of relinquishment in perpetuity of all present
+ * and future rights to this software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _NVXX_H_INCLUDED
+#define _NVXX_H_INCLUDED
+
+/*
+ * nvl: lightweight wrapper around nv(9).
+ */
+
+#include "nvxx_base.h"
+
+#endif /* !_NVXX_H_INCLUDED */
diff --git a/libnvxx/nvxx_base.h b/libnvxx/nvxx_base.h
new file mode 100644
index 0000000..298d5d6
--- /dev/null
+++ b/libnvxx/nvxx_base.h
@@ -0,0 +1,550 @@
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+ * this software, either in source code form or as a compiled binary, for any
+ * purpose, commercial or non-commercial, and by any means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors of
+ * this software dedicate any and all copyright interest in the software to the
+ * public domain. We make this dedication for the benefit of the public at
+ * large and to the detriment of our heirs and successors. We intend this
+ * dedication to be an overt act of relinquishment in perpetuity of all present
+ * and future rights to this software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _NVXX_BASE_H_INCLUDED
+#define _NVXX_BASE_H_INCLUDED
+
+#include <sys/nv.h>
+
+#include <expected>
+#include <ranges>
+#include <span>
+#include <system_error>
+#include <vector>
+#include <stdexcept>
+
+namespace bsd {
+
+struct nv_list;
+struct const_nv_list;
+
+enum struct nvlist_owning {
+ owning,
+ non_owning
+};
+
+namespace __detail {
+
+struct __nv_list_base {
+protected:
+ friend struct bsd::const_nv_list;
+
+ __nv_list_base(int __flags = 0);
+ __nv_list_base(::nvlist_t *,
+ nvlist_owning = nvlist_owning::owning);
+
+ __nv_list_base(__nv_list_base const &) = delete;
+ __nv_list_base(__nv_list_base &&) noexcept = delete;
+
+ __nv_list_base &operator=(__nv_list_base const &) = delete;
+ __nv_list_base &operator=(__nv_list_base &&) noexcept = delete;
+
+ ~__nv_list_base();
+ void __free_nv() noexcept;
+
+ ::nvlist_t *__m_nv{};
+ nvlist_owning __m_owning;
+};
+
+struct __const_nv_list : virtual __nv_list_base {
+ friend struct __nv_list;
+
+ /*
+ * Write the contents of this nvlist to __fd or __fp in a
+ * human-readable format suitable for debugging.
+ */
+ void dump(int __fd) const noexcept;
+ void fdump(std::FILE *__fp) const noexcept;
+
+ /*
+ * Return the number of bytes that would be required to call pack() on
+ * this nvlist. This is equivalent to nvlist_size(), but we rename it
+ * to packed_size() to avoid confusion with the usual container size()
+ * function (which returns the number of elements).
+ */
+ [[nodiscard]] std::size_t packed_size() const noexcept;
+
+ /*
+ * Return a byte array representing the contents of this nvlist; the
+ * array can later be passed to nv_list::unpack() to reproduce the
+ * nvlist.
+ */
+ [[nodiscard]] std::vector<std::byte> pack() const;
+
+ /*
+ * Return the error code associated with this nvlist, if any, by
+ * calling nvlist_error().
+ */
+ [[nodiscard]] std::error_code error() const noexcept;
+
+ /*
+ * Return true if this nvlist is in a non-error state, otherwise false.
+ */
+ [[nodiscard]] explicit operator bool() const noexcept;
+
+ /*
+ * Returns true if this nvlist is empty (contains no values).
+ */
+ [[nodiscard]] bool empty() const noexcept;
+
+ /*
+ * Return the flags used to create this nv_list.
+ */
+ [[nodiscard]] int flags() const noexcept;
+
+ /*
+ * Returns true if this nv_list is part of an array contained inside
+ * another nv_list.
+ */
+ [[nodiscard]] bool in_array() const noexcept;
+
+ /*
+ * Pack this nvlist and write it to the given file descriptor. On
+ * error, throws std::system_error.
+ */
+ void send(int __fd) const;
+
+ /*
+ * if a key of any type with the given name exists, return true.
+ */
+ [[nodiscard]] bool exists(std::string_view __key) const;
+
+ // TODO: exists_type()
+
+ /* exists */
+
+ [[nodiscard]] bool exists_null(std::string_view __key) const;
+ [[nodiscard]] bool exists_bool(std::string_view __key) const;
+ [[nodiscard]] bool exists_number(std::string_view __key) const;
+ [[nodiscard]] bool exists_string(std::string_view __key) const;
+ [[nodiscard]] bool exists_nvlist(std::string_view __key) const;
+ [[nodiscard]] bool exists_descriptor(std::string_view __key) const;
+ [[nodiscard]] bool exists_binary(std::string_view __key) const;
+
+ [[nodiscard]] bool exists_bool_array(std::string_view __key) const;
+ [[nodiscard]] bool exists_number_array(std::string_view __key) const;
+ [[nodiscard]] bool exists_string_array(std::string_view __key) const;
+ [[nodiscard]] bool exists_nvlist_array(std::string_view __key) const;
+ [[nodiscard]] bool exists_descriptor_array(
+ std::string_view __key) const;
+
+ /* get */
+
+ [[nodiscard]] auto
+ get_bool(std::string_view __key) const -> bool;
+
+ [[nodiscard]] auto
+ get_number(std::string_view __key) const -> std::uint64_t;
+
+ [[nodiscard]] auto
+ get_string(std::string_view __key) const -> std::string_view;
+
+ [[nodiscard]] auto
+ get_nvlist(std::string_view __key) const -> const_nv_list;
+
+ [[nodiscard]] auto
+ get_descriptor(std::string_view __key) const -> int;
+
+ [[nodiscard]] auto
+ get_binary(std::string_view key) const -> std::span<std::byte const>;
+
+
+ [[nodiscard]] auto
+ get_bool_array(std::string_view __key) const -> std::span<bool const>;
+
+ [[nodiscard]] auto
+ get_number_array(std::string_view __key) const
+ -> std::span<std::uint64_t const>;
+
+ [[nodiscard]] auto
+ get_string_array(std::string_view __key) const
+ -> std::vector<std::string_view>;
+
+ [[nodiscard]] auto
+ get_nvlist_array(std::string_view name) const
+ -> std::vector<const_nv_list>;
+
+ [[nodiscard]] auto
+ get_descriptor_array(std::string_view __key) const
+ -> std::span<int const>;
+};
+
+struct __nv_list : virtual __nv_list_base {
+ friend struct const_nv_list;
+
+ /*
+ * Set the error code on this nvlist to the given value.
+ */
+ void set_error(int __error) noexcept;
+
+ /*
+ * Convert this nv_list into a const_nv_list. This is a shallow copy
+ * which does not clone the underlying nvlist_t; therefore, the
+ * lifetime of the const_nv_list ends when this nv_list is destroyed.
+ */
+ operator const_nv_list() const;
+
+ /* add */
+
+ void add_null(std::string_view __key);
+ void add_bool(std::string_view __key, bool);
+ void add_number(std::string_view __key, std::uint64_t);
+ void add_string(std::string_view __key, std::string_view);
+ void add_nvlist(std::string_view __key, const_nv_list const &);
+ void add_descriptor(std::string_view __key, int);
+ void add_binary(std::string_view __key, std::span<std::byte const>);
+
+ void add_bool_array(std::string_view __key,
+ std::span<bool const>);
+ void add_number_array(std::string_view __key,
+ std::span<std::uint64_t const>);
+ void add_string_array(std::string_view __key,
+ std::span<std::string_view const>);
+ void add_nvlist_array(std::string_view __key,
+ std::span<const_nv_list const>);
+ void add_nvlist_array(std::string_view __key,
+ std::span<nv_list const>);
+ void add_descriptor_array(std::string_view __key,
+ std::span<int const>);
+
+ /* free */
+
+ void free(std::string_view __key);
+ // TODO: free_type()
+ void free_null(std::string_view __key);
+ void free_bool(std::string_view __key);
+ void free_number(std::string_view __key);
+ void free_string(std::string_view __key);
+ void free_nvlist(std::string_view __key);
+ void free_descriptor(std::string_view __key);
+ void free_binary(std::string_view __key);
+
+ void free_bool_array(std::string_view __key);
+ void free_number_array(std::string_view __key);
+ void free_string_array(std::string_view __key);
+ void free_nvlist_array(std::string_view __key);
+ void free_descriptor_array(std::string_view __key);
+
+ /* take */
+
+ [[nodiscard]] auto take_bool(std::string_view __key) -> bool;
+ [[nodiscard]] auto take_number(std::string_view __key)
+ -> std::uint64_t;
+ [[nodiscard]] auto take_string(std::string_view __key) -> std::string;
+ [[nodiscard]] auto take_nvlist(std::string_view __key) -> nv_list;
+ [[nodiscard]] auto take_descriptor(std::string_view __key) -> int;
+ [[nodiscard]] auto take_binary(std::string_view __key)
+ -> std::vector<std::byte>;
+
+ [[nodiscard]] auto
+ take_bool_array(std::string_view __key) -> std::vector<bool>;
+
+ [[nodiscard]] auto
+ take_number_array(std::string_view __key)
+ -> std::vector<std::uint64_t>;
+
+ [[nodiscard]] auto
+ take_string_array(std::string_view __key) -> std::vector<std::string>;
+
+ [[nodiscard]] auto
+ take_nvlist_array(std::string_view __key) -> std::vector<nv_list>;
+
+ [[nodiscard]] auto
+ take_descriptor_array(std::string_view __key) -> std::vector<int>;
+
+ /* move */
+
+ void move_string(std::string_view __key, char *);
+ void move_nvlist(std::string_view __key, nv_list &&);
+ void move_nvlist(std::string_view __key, ::nvlist_t *);
+ void move_descriptor(std::string_view __key, int);
+ void move_binary(std::string_view __key, std::span<std::byte>);
+
+ void move_bool_array(std::string_view __key, std::span<bool>);
+ void move_number_array(std::string_view __key, std::span<std::uint64_t>);
+ void move_string_array(std::string_view __key, std::span<char *>);
+ void move_nvlist_array(std::string_view __key, std::span<::nvlist_t *>);
+ void move_descriptor_array(std::string_view __key, std::span<int>);
+
+ /* append */
+
+ void append_bool_array(std::string_view __key, bool);
+ void append_number_array(std::string_view __key, std::uint64_t);
+ void append_string_array(std::string_view __key, std::string_view);
+ void append_nvlist_array(std::string_view __key, const_nv_list const &);
+ void append_descriptor_array(std::string_view __key, int);
+
+ /* add_bool_range */
+ template<std::ranges::contiguous_range _Range>
+ requires std::is_same_v<bool,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>
+ void add_bool_range(std::string_view __key, _Range &&__value)
+ {
+ add_bool_array(__key, std::span(__value));
+ }
+
+ template<std::ranges::range _Range>
+ requires (!std::ranges::contiguous_range<_Range>
+ && std::is_same_v<bool,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>)
+ void add_bool_range(std::string_view __key, _Range &&__value)
+ {
+ /*
+ * since vector<bool> is not a contiguous_range,
+ * we need to do two copies here.
+ */
+
+ auto __v = std::vector(std::from_range, __value);
+ auto __p = std::make_unique<bool[]>(__v.size());
+ std::ranges::copy(__v, __p.get());
+ add_bool_array(__key, std::span(__p.get(), __v.size()));
+ }
+
+ /* add_number_range */
+
+ template<std::ranges::contiguous_range _Range>
+ requires std::is_same_v<std::uint64_t,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>
+ void add_number_range(std::string_view __key, _Range &&__value)
+ {
+ add_number_array(__key, std::span(__value));
+ }
+
+ template<std::ranges::range _Range>
+ requires (
+ !std::ranges::contiguous_range<_Range>
+ && std::is_same_v<std::uint64_t,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>)
+ void add_number_range(std::string_view __key, _Range &&__value)
+ {
+ auto __arr = std::vector(std::from_range, __value);
+ add_number_range(__key, __arr);
+ }
+
+
+ /* add_string_range */
+
+ template<std::ranges::contiguous_range _Range>
+ requires std::is_same_v<std::string_view,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>
+ void add_string_range(std::string_view __key, _Range &&__value)
+ {
+ add_string_array(__key, std::span(__value));
+ }
+
+ template<std::ranges::range _Range>
+ requires (!std::ranges::contiguous_range<_Range>
+ && std::is_same_v<std::string_view,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>)
+ void add_string_range(std::string_view __key, _Range &&__value)
+ {
+ auto __arr = std::vector(std::from_range, __value);
+ add_string_range(__key, __arr);
+ }
+
+ /* add_binary_range */
+
+ template<std::ranges::contiguous_range _Range>
+ requires std::is_same_v<std::byte,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>
+ void add_binary_range(std::string const &__name, _Range &&__value)
+ {
+ auto __span = std::span(__value);
+ add_binary(__name, __span);
+ }
+
+ template<std::ranges::range _Range>
+ requires (
+ !std::ranges::contiguous_range<_Range>
+ && std::is_same_v<std::byte,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>)
+ void add_binary_range(std::string_view __key, _Range &&__value)
+ {
+ auto __arr = std::vector(std::from_range, __value);
+ add_number_range(__key, __arr);
+ }
+};
+
+} // namespace bsd::__detail
+
+/*
+ * const_nv_list is an immutable, non-owning reference to an nvlist.
+ * it will not free the nvlist_t on destruction.
+ */
+struct const_nv_list final
+ : virtual __detail::__nv_list_base
+ , __detail::__const_nv_list
+{
+ /*
+ * Default constructing a const_nv_list leaves it in the empty state;
+ * it can be assigned to or destructed but no other operations are
+ * valid.
+ */
+ const_nv_list() noexcept;
+
+ /*
+ * Create an nv_list object that refers to an existing nvlist_t. The
+ * const_nv_list is non-owning, i.e. it will not free the nvlist_t on
+ * destruction. If the nvlist_t is null, the const_nv_list will be
+ * empty.
+ */
+ explicit const_nv_list(::nvlist_t const *) noexcept;
+
+ /*
+ * Copy the nvlist pointer from an existing const_nv_list. This does
+ * not clone the nvlist itself; both nvlists will have the same
+ * lifetime. If the other nvlist is empty, this nvlist will also be
+ * empty.
+ */
+ const_nv_list(const_nv_list const &) noexcept;
+
+ /*
+ * Cause this const_nv_list to refer to the same nvlist as the RHS.
+ * This does not clone the nvlist; both nvlists will have the same
+ * lifetime. If the RHS nvlist is empty, this nvlist will also be
+ * empty.
+ */
+ const_nv_list &operator=(const_nv_list const &) noexcept;
+ const_nv_list &operator=(nv_list const &) noexcept;
+
+ /*
+ * Return the nvlist pointer stored by this const_nv_list. Since
+ * const_nv_list is non-owning, the lifetime of the pointer is
+ * unspecified.
+ */
+ ::nvlist_t const *ptr() const;
+};
+
+/*
+ * nv_list is a mutable, owning reference to an nvlist. it will free the
+ * nvlist_t on destruction, invalidating any const_nv_lists created from it.
+ */
+struct nv_list final
+ : virtual __detail::__nv_list_base
+ , __detail::__const_nv_list
+ , __detail::__nv_list
+{
+ /*
+ * Create a new, empty nv_list. On failure, throws std::system_error.
+ * The flags argument is passed to nvlist_create().
+ */
+ explicit nv_list(int __flags = 0);
+
+ /*
+ * Create an nv_list object that refers to an existing nvlist_t.
+ */
+ explicit nv_list(::nvlist_t *) noexcept;
+
+ /*
+ * Create an nv_list by copying an existing nv_list object with
+ * nvlist_clone(). On failure, throws std::system_error.
+ */
+ nv_list(nv_list const &);
+
+ /*
+ * Create an nv_list by moving from an existing nv_list. The
+ * moved-from nvlist is left in an undefined state and must not be
+ * accessed other than to destruct it.
+ */
+ nv_list(nv_list &&) noexcept;
+
+ /*
+ * Replace the wrapped nv_list with a copy of the RHS nv_list using
+ * nvlist_clone(). On failure, throws std::system_error.
+ */
+ nv_list &operator=(nv_list const &);
+
+ /*
+ * Replace the wrapped nv_list by moving from another nv_list. The
+ * moved-from nv_list is left in an undefined state and must not be
+ * accessed other than to destruct it.
+ */
+ nv_list &operator=(nv_list &&) noexcept;
+
+ /*
+ * Return the pointer stored by this nv_list, without releasing it.
+ * The pointer may be used to modify the nvlist, but must not be
+ * freed (e.g., by passing it to a function like nvlist_xfer()).
+ */
+ ::nvlist_t *ptr();
+ ::nvlist_t const *ptr() const;
+
+ /*
+ * Release the pointer held by this nv_list and return it. The nv_list
+ * will be left in a moved-from state and must not be used other than
+ * to destruct it.
+ */
+ ::nvlist_t *release() &&;
+
+ /*
+ * Create and return an nv_list by calling nvlist_unpack() on the
+ * provided buffer. The __flags argument is passed to nvlist_unpack().
+ * On failure, throws std::system_error.
+ */
+ [[nodiscard]] static nv_list
+ unpack(std::span<std::byte> __data, int __flags = 0);
+
+ /*
+ * As the previous function, but the nv_list is unpacked from the
+ * provided range, which must be a contiguous_range of value type
+ * std::byte.
+ */
+ template<std::ranges::contiguous_range _Range>
+ requires std::is_same_v<std::byte,
+ std::remove_cvref_t<
+ std::ranges::range_value_t<_Range>>>
+ [[nodiscard]] static nv_list
+ unpack(_Range const &__data, int __flags = 0)
+ {
+ return unpack(std::span<std::byte>(__data), __flags);
+ }
+
+ /*
+ * Receive an nv_list from a file descriptor by calling nvlist_recv(),
+ * to which flags is passed. On failure, throws std::system_error.
+ */
+ [[nodiscard]] static auto recv(int __fd, int __flags) -> nv_list;
+
+ /*
+ * Send an nv_list over a file descriptor and receive another nv_list
+ * in response, which is returned, by calling nvlist_xfer(). On
+ * failure, throws std::system_error.
+ *
+ * The source nv_list is moved-from and is left in an undefined state.
+ * The returned nv_list is owning.
+ */
+ [[nodiscard]] static nv_list xfer(int __fd,
+ nv_list &&__source,
+ int __flags);
+};
+
+} // namespace bsd
+
+#endif /* !_NVXX_BASE_H_INCLUDED */
diff --git a/libnvxx/tests/Makefile b/libnvxx/tests/Makefile
new file mode 100644
index 0000000..29d0351
--- /dev/null
+++ b/libnvxx/tests/Makefile
@@ -0,0 +1,30 @@
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+# this software, either in source code form or as a compiled binary, for any
+# purpose, commercial or non-commercial, and by any means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors of
+# this software dedicate any and all copyright interest in the software to the
+# public domain. We make this dedication for the benefit of the public at
+# large and to the detriment of our heirs and successors. We intend this
+# dedication to be an overt act of relinquishment in perpetuity of all present
+# and future rights to this software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+PREFIX?= /usr/local
+TESTSDIR?= ${PREFIX}/tests/nvxx
+ATF_TESTS_CXX= nvxx_basic
+CXXSTD= c++23
+# Note that we can't use -Werror here because it breaks ATF.
+CXXFLAGS+= -W -Wall -Wextra
+CFLAGS+= -I${.CURDIR:H}
+LDFLAGS+= -lprivateatf-c++ -L${.OBJDIR:H} -lnvxx
+
+.include <bsd.test.mk>
diff --git a/libnvxx/tests/nvxx_basic.cc b/libnvxx/tests/nvxx_basic.cc
new file mode 100644
index 0000000..1cf2bfd
--- /dev/null
+++ b/libnvxx/tests/nvxx_basic.cc
@@ -0,0 +1,571 @@
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or distribute
+ * this software, either in source code form or as a compiled binary, for any
+ * purpose, commercial or non-commercial, and by any means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors of
+ * this software dedicate any and all copyright interest in the software to the
+ * public domain. We make this dedication for the benefit of the public at
+ * large and to the detriment of our heirs and successors. We intend this
+ * dedication to be an overt act of relinquishment in perpetuity of all present
+ * and future rights to this software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <algorithm>
+#include <ranges>
+#include <list>
+#include <vector>
+#include <span>
+#include <string>
+#include <string_view>
+
+#include <atf-c++.hpp>
+
+#include "nvxx.h"
+
+#define TEST_CASE(name) \
+ ATF_TEST_CASE_WITHOUT_HEAD(name) \
+ ATF_TEST_CASE_BODY(name)
+
+/*
+ * test the default ctor
+ */
+
+TEST_CASE(nvxx_ctor_default)
+{
+ auto nvl = bsd::nv_list();
+}
+
+/*
+ * test the NV_FLAG_IGNORE_CASE flag.
+ */
+
+TEST_CASE(nvxx_ignore_case)
+{
+ auto nvl = bsd::nv_list(NV_FLAG_IGNORE_CASE);
+ nvl.add_number("TEST number", 42u);
+ ATF_REQUIRE_EQ(true, nvl.exists_number("TesT nUMBEr"));
+
+ auto n = nvl.take_number("test NuMbEr");
+ ATF_REQUIRE_EQ(42u, n);
+ ATF_REQUIRE_EQ(false, nvl.exists_number("TesT nUMBEr"));
+}
+
+/*
+ * null tests
+ */
+
+TEST_CASE(nvxx_add_null)
+{
+ auto nvl = bsd::nv_list();
+ nvl.add_null("test");
+ ATF_REQUIRE_EQ(true, nvl.exists_null("test"));
+ ATF_REQUIRE_EQ(true, nvl.exists("test"));
+}
+
+/*
+ * bool tests
+ */
+
+TEST_CASE(nvxx_add_bool)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_bool"sv;
+ auto constexpr value = true;
+
+ auto nvl = bsd::nv_list();
+ nvl.add_bool(key, value);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_bool(key));
+ ATF_REQUIRE_EQ(value, nvl.get_bool(key));
+}
+
+TEST_CASE(nvxx_take_bool)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_bool"sv;
+ auto constexpr value = true;
+
+ auto nvl = bsd::nv_list();
+ nvl.add_bool(key, value);
+ auto b = nvl.take_bool(key);
+ ATF_REQUIRE_EQ(value, b);
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_free_bool)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_bool"sv;
+ auto constexpr value = true;
+
+ auto nvl = bsd::nv_list();
+ nvl.add_bool(key, value);
+ nvl.free_bool(key);
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_add_bool_array)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_bool"sv;
+
+ auto data = std::array<bool, 2>{true, false};
+
+ auto nvl = bsd::nv_list();
+ nvl.add_bool_array(key, std::span(data));
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_bool_array(key));
+
+ auto data2 = nvl.get_bool_array(key);
+ ATF_REQUIRE_EQ(2, data2.size());
+ ATF_REQUIRE_EQ(true, data2[0]);
+ ATF_REQUIRE_EQ(false, data2[1]);
+
+ auto data3 = nvl.take_bool_array(key);
+ ATF_REQUIRE_EQ(2, data3.size());
+ ATF_REQUIRE_EQ(true, data3[0]);
+ ATF_REQUIRE_EQ(false, data3[1]);
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_add_bool_range)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_bool"sv;
+
+ auto data = std::list{true, false};
+
+ auto nvl = bsd::nv_list();
+ nvl.add_bool_range(key, data);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_bool_array(key));
+
+ auto data2 = nvl.get_bool_array(key);
+ ATF_REQUIRE_EQ(2, data2.size());
+ ATF_REQUIRE_EQ(true, data2[0]);
+ ATF_REQUIRE_EQ(false, data2[1]);
+}
+
+TEST_CASE(nvxx_add_bool_contig_range)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_bool"sv;
+
+ auto data = std::vector{true, false};
+
+ auto nvl = bsd::nv_list();
+ nvl.add_bool_range(key, data);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_bool_array(key));
+
+ auto data2 = nvl.get_bool_array(key);
+ ATF_REQUIRE_EQ(2, data2.size());
+ ATF_REQUIRE_EQ(true, data2[0]);
+ ATF_REQUIRE_EQ(false, data2[1]);
+}
+
+/*
+ * number tests
+ */
+
+// a literal operator to create std::uint64_ts
+constexpr std::uint64_t operator"" _u64(unsigned long long v) {
+ return static_cast<std::uint64_t>(v);
+}
+
+TEST_CASE(nvxx_add_number)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_number"sv;
+ auto constexpr value = 42_u64;
+
+ auto nvl = bsd::nv_list();
+
+ nvl.add_number(key, value);
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_number(key));
+ ATF_REQUIRE_EQ(value, nvl.get_number(key));
+}
+
+TEST_CASE(nvxx_take_number)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_number"sv;
+ auto constexpr value = 42_u64;
+
+ auto nvl = bsd::nv_list();
+
+ nvl.add_number(key, value);
+ auto n = nvl.take_number(key);
+ ATF_REQUIRE_EQ(value, n);
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_free_number)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_number"sv;
+ auto constexpr value = 42_u64;
+
+ auto nvl = bsd::nv_list();
+
+ nvl.add_number(key, value);
+ nvl.free_number(key);
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_add_number_array)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_number"sv;
+ auto constexpr size = 16u;
+
+ auto data = std::array<std::uint64_t, size>{};
+ std::ranges::copy(std::ranges::iota_view{0u, size}, data.begin());
+
+ auto nvl = bsd::nv_list();
+ nvl.add_number_array(key, std::span(data));
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_number_array(key));
+
+ auto data2 = nvl.get_number_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+
+ auto data3 = nvl.take_number_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3));
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_add_number_range)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_number"sv;
+ auto constexpr size = 16u;
+
+ auto data = std::list(std::from_range,
+ std::ranges::iota_view{0_u64, size});
+
+ auto nvl = bsd::nv_list();
+ nvl.add_number_range(key, data);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_number_array(key));
+
+ auto data2 = nvl.get_number_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+}
+
+TEST_CASE(nvxx_add_number_contig_range)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_number"sv;
+ auto constexpr size = 16u;
+
+ auto data = std::vector(std::from_range,
+ std::ranges::iota_view{0_u64, size});
+
+ auto nvl = bsd::nv_list();
+ nvl.add_number_range(key, data);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_number_array(key));
+
+ auto data2 = nvl.get_number_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+}
+
+/*
+ * string tests
+ */
+
+TEST_CASE(nvxx_add_string)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_string"sv;
+ auto constexpr value = "testing value"sv;
+
+ auto nvl = bsd::nv_list();
+ nvl.add_string(key, value);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_string(key));
+ ATF_REQUIRE_EQ(value, nvl.get_string(key));
+}
+
+TEST_CASE(nvxx_take_string)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_string"sv;
+ auto constexpr value = "testing value"sv;
+
+ auto nvl = bsd::nv_list();
+ nvl.add_string(key, value);
+ auto s = nvl.take_string(key);
+ ATF_REQUIRE_EQ(value, s);
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_free_string)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_string"sv;
+ auto constexpr value = "testing value"sv;
+
+ auto nvl = bsd::nv_list();
+ nvl.add_string(key, value);
+ nvl.free_string(key);
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_add_string_array)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_string"sv;
+
+ auto data = std::array<std::string_view, 2>{"one"sv, "two"sv};
+
+ auto nvl = bsd::nv_list();
+ nvl.add_string_array(key, std::span(data));
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_string_array(key));
+
+ auto data2 = nvl.get_string_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+
+ auto data3 = nvl.take_string_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3));
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_add_string_range)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_string"sv;
+
+ auto data = std::list<std::string_view>{"one"sv, "two"sv};
+
+ auto nvl = bsd::nv_list();
+ nvl.add_string_range(key, data);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_string_array(key));
+
+ auto data2 = nvl.get_string_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+
+ auto data3 = nvl.take_string_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3));
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+TEST_CASE(nvxx_add_string_contig_range)
+{
+ using namespace std::literals;
+ auto constexpr key = "test_string"sv;
+
+ auto data = std::vector<std::string_view>{"one"sv, "two"sv};
+
+ auto nvl = bsd::nv_list();
+ nvl.add_string_range(key, data);
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_string_array(key));
+
+ auto data2 = nvl.get_string_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+
+ auto data3 = nvl.take_string_array(key);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data3));
+ ATF_REQUIRE_EQ(false, nvl.exists(key));
+}
+
+/*
+ * nv_list tests
+ */
+
+TEST_CASE(nvxx_add_nvlist)
+{
+ auto nvl = bsd::nv_list(), nvl2 = bsd::nv_list();
+
+ nvl2.add_number("test_number", 42);
+ nvl.add_nvlist("test_nvlist", nvl2);
+
+ ATF_REQUIRE_EQ(true, nvl.exists("test_nvlist"));
+ ATF_REQUIRE_EQ(true, nvl.exists_nvlist("test_nvlist"));
+ ATF_REQUIRE_EQ(42, nvl.get_nvlist("test_nvlist")
+ .get_number("test_number"));
+}
+
+TEST_CASE(nvxx_take_nvlist)
+{
+ auto nvl = bsd::nv_list(), nvl2 = bsd::nv_list();
+
+ nvl2.add_number("test_number", 42);
+ nvl.add_nvlist("test_nvlist", nvl2);
+
+ auto nvl3 = nvl.take_nvlist("test_nvlist");
+ ATF_REQUIRE_EQ(false, nvl.exists("test_nvlist"));
+ ATF_REQUIRE_EQ(42, nvl3.get_number("test_number"));
+}
+
+TEST_CASE(nvxx_free_nvlist)
+{
+ auto nvl = bsd::nv_list(), nvl2 = bsd::nv_list();
+
+ nvl2.add_number("test_number", 42);
+ nvl.add_nvlist("test_nvlist", nvl2);
+
+ nvl.free_nvlist("test_nvlist");
+ ATF_REQUIRE_EQ(false, nvl.exists("test_nvlist"));
+}
+
+TEST_CASE(nvxx_add_nvlist_array)
+{
+ using namespace std::literals;
+ auto constexpr key = "nvls"sv;
+
+ auto nvls = std::vector<bsd::nv_list>();
+
+ {
+ auto nvl_ = bsd::nv_list();
+ nvl_.add_number("one", 1);
+ nvls.push_back(nvl_);
+ }
+
+ {
+ auto nvl_ = bsd::nv_list();
+ nvl_.add_number("two", 2);
+ nvls.push_back(nvl_);
+ }
+
+ auto nvl = bsd::nv_list();
+ nvl.add_nvlist_array(key, std::span(nvls));
+
+ ATF_REQUIRE_EQ(true, nvl.exists(key));
+ ATF_REQUIRE_EQ(true, nvl.exists_nvlist_array(key));
+
+ auto nvls2 = nvl.get_nvlist_array(key);
+
+ auto n1 = nvls2[0].get_number("one");
+ ATF_REQUIRE_EQ(n1, 1);
+
+ auto n2 = nvls2[1].get_number("two");
+ ATF_REQUIRE_EQ(n2, 2);
+}
+
+TEST_CASE(nvxx_add_descriptor)
+{
+ int fds[2] = {};
+ auto ret = ::pipe(&fds[0]);
+ ATF_REQUIRE_EQ(0, ret);
+
+ auto guard = std::unique_ptr<int[],
+ decltype([](auto fds) {
+ ::close(fds[0]);
+ ::close(fds[1]);
+ })>(fds);
+
+ auto nvl = bsd::nv_list();
+ nvl.add_descriptor("test_descriptor", fds[0]);
+ ATF_REQUIRE_EQ(true, nvl.exists("test_descriptor"));
+ ATF_REQUIRE_EQ(true, nvl.exists_descriptor("test_descriptor"));
+
+ auto fd = nvl.get_descriptor("test_descriptor");
+ ret = ::write(fd, "1234", 4);
+ ATF_REQUIRE_EQ(4, ret);
+
+ auto buf = std::array<char, 4>{};
+ ret = ::read(fds[1], &buf[0], 4);
+ ATF_REQUIRE_EQ(4, ret);
+
+ ATF_REQUIRE_EQ('1', buf[0]);
+ ATF_REQUIRE_EQ('2', buf[1]);
+ ATF_REQUIRE_EQ('3', buf[2]);
+ ATF_REQUIRE_EQ('4', buf[3]);
+}
+
+TEST_CASE(nvxx_add_binary)
+{
+ auto nvl = bsd::nv_list();
+ auto data = std::array<std::byte, 16>{};
+
+ for (auto i = 0u; i < data.size(); ++i)
+ data[i] = static_cast<std::byte>(i);
+
+ nvl.add_binary("test_binary", std::span(data));
+
+ ATF_REQUIRE_EQ(true, nvl.exists("test_binary"));
+ ATF_REQUIRE_EQ(true, nvl.exists_binary("test_binary"));
+ auto data2 = nvl.get_binary("test_binary");
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+}
+
+TEST_CASE(nvxx_add_binary_range)
+{
+ auto nvl = bsd::nv_list();
+ auto data = std::array<std::byte, 16>{};
+
+ for (auto i = 0u; i < data.size(); ++i)
+ data[i] = static_cast<std::byte>(i);
+
+ nvl.add_binary_range("test_binary", data);
+
+ ATF_REQUIRE_EQ(true, nvl.exists("test_binary"));
+ ATF_REQUIRE_EQ(true, nvl.exists_binary("test_binary"));
+ auto data2 = nvl.get_binary("test_binary");
+ ATF_REQUIRE_EQ(true, std::ranges::equal(data, data2));
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, nvxx_ctor_default);
+ ATF_ADD_TEST_CASE(tcs, nvxx_ignore_case);
+
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_null);
+
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_bool);
+ ATF_ADD_TEST_CASE(tcs, nvxx_take_bool);
+ ATF_ADD_TEST_CASE(tcs, nvxx_free_bool);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_bool_array);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_bool_range);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_bool_contig_range);
+
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_number);
+ ATF_ADD_TEST_CASE(tcs, nvxx_take_number);
+ ATF_ADD_TEST_CASE(tcs, nvxx_free_number);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_number_array);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_number_range);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_number_contig_range);
+
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_string);
+ ATF_ADD_TEST_CASE(tcs, nvxx_take_string);
+ ATF_ADD_TEST_CASE(tcs, nvxx_free_string);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_string_array);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_string_range);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_string_contig_range);
+
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_nvlist);
+ ATF_ADD_TEST_CASE(tcs, nvxx_take_nvlist);
+ ATF_ADD_TEST_CASE(tcs, nvxx_free_nvlist);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_nvlist_array);
+
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_descriptor);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_binary);
+ ATF_ADD_TEST_CASE(tcs, nvxx_add_binary_range);
+}