Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ https://www.boost.org/LICENSE_1_0.txt

| xref:byte_conversions.adoc[`from_le`]
| Converts a safe integer from little-endian to native byte order

| xref:byte_conversions.adoc[`to_be_bytes`]
| Converts a safe integer to a big-endian byte array

| xref:byte_conversions.adoc[`from_be_bytes`]
| Reconstructs a safe integer from a big-endian byte array
|===

=== Arithmetic
Expand Down
104 changes: 104 additions & 0 deletions doc/modules/ROOT/pages/byte_conversions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,107 @@ constexpr auto val = verified_u32{u32{0xDEADBEEF}};
constexpr auto round_tripped = from_le(to_le(val));
static_assert(round_tripped == val);
----

== to_be_bytes

Converts a safe integer value to a big-endian byte array.
The value is first converted to big-endian byte order using `to_be`, then reinterpreted as an array of `std::byte`.

=== Runtime Overload

[source,c++]
----
template <non_bounded_integral_library_type T>
requires (!is_verified_type_v<T>)
constexpr auto to_be_bytes(const T value) noexcept -> std::array<std::byte, sizeof(T)>;
----

==== Parameters

* `value` -- The value to convert.

==== Return Value

A `std::array<std::byte, sizeof(T)>` containing the value's bytes in big-endian order (most significant byte first).

==== Complexity

O(1).

==== Example

[source,c++]
----
using namespace boost::safe_numbers;

auto bytes = to_be_bytes(u32{0x01020304});
// bytes == {0x01, 0x02, 0x03, 0x04}
----

=== Verified Overload

[source,c++]
----
template <non_bounded_integral_library_type T>
consteval auto to_be_bytes(const verified_type_basis<T> value) noexcept -> std::array<std::byte, sizeof(T)>;
----

Compile-time only overload for verified types.

==== Example

[source,c++]
----
using namespace boost::safe_numbers;

constexpr auto bytes = to_be_bytes(verified_u32{u32{0x01020304}});
// bytes == {0x01, 0x02, 0x03, 0x04}
----

== from_be_bytes

Reconstructs a safe integer value from a big-endian byte array.
The bytes are reinterpreted as the underlying type and then converted from big-endian to native byte order using `from_be`.

[source,c++]
----
template <non_bounded_integral_library_type T, std::size_t N>
constexpr auto from_be_bytes(const std::span<const std::byte, N> bytes) -> T;
----

==== Parameters

* `bytes` -- A span of bytes in big-endian order. May have a fixed extent or `std::dynamic_extent`.

==== Return Value

The reconstructed safe integer value in native byte order.

==== Extent Matching

The function validates that the number of input bytes matches `sizeof(T)`:

* **Fixed extent matches `sizeof(T)`**: Compiles and executes normally.
* **Fixed extent does not match `sizeof(T)`**: Produces a `static_assert` failure at compile time.
* **Dynamic extent**: Checks at runtime and throws `std::domain_error` if the sizes do not match.

==== Complexity

O(1).

==== Example

[source,c++]
----
using namespace boost::safe_numbers;

// From a fixed-extent span
std::array<std::byte, 4> bytes {std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}};
auto val = from_be_bytes<u32>(std::span<const std::byte, 4>{bytes});
// val == u32{0x01020304}

// Round-trip
auto original = u32{0xDEADBEEF};
auto reconstructed = from_be_bytes<u32>(std::span<const std::byte, 4>{to_be_bytes(original)});
// reconstructed == original
----
53 changes: 53 additions & 0 deletions include/boost/safe_numbers/byte_conversions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE

#include <array>
#include <bit>
#include <cstddef>
#include <span>

#endif // BOOST_SAFE_NUMBERS_BUILD_MODULE

Expand Down Expand Up @@ -101,6 +104,56 @@ consteval auto from_le(const detail::verified_type_basis<T> value) noexcept -> d
return to_le(value);
}

template <detail::non_bounded_integral_library_type T>
requires (!detail::is_verified_type_v<T>)
constexpr auto to_be_bytes(const T value) noexcept -> std::array<std::byte, sizeof(T)>
{
const auto be_value {to_be(value)};
return std::bit_cast<std::array<std::byte, sizeof(T)>>(be_value);
}

template <detail::non_bounded_integral_library_type T>
consteval auto to_be_bytes(const detail::verified_type_basis<T> value) noexcept -> std::array<std::byte, sizeof(T)>
{
const auto be_value {to_be(value)};
return std::bit_cast<std::array<std::byte, sizeof(T)>>(be_value);
}

template <detail::non_bounded_integral_library_type T, std::size_t N>
constexpr auto from_be_bytes(const std::span<const std::byte, N> bytes) -> T
{
using underlying_type = detail::underlying_type_t<T>;

if constexpr (N == sizeof(T))
{
std::array<std::byte, sizeof(T)> arr {};
for (std::size_t i {}; i < N; ++i)
{
arr[i] = bytes[i];
}
return from_be(T{std::bit_cast<underlying_type>(arr)});
}
else if constexpr (N != std::dynamic_extent)
{
static_assert(detail::dependent_false<T>, "The number of bytes provided, and the target type number of bytes do not match");
return T{}; // LCOV_EXCL_LINE
}
else
{
if (bytes.size_bytes() != sizeof(T))
{
BOOST_THROW_EXCEPTION(std::domain_error("The number of bytes provided, and the target type number of bytes do not match"));
}

std::array<std::byte, sizeof(T)> arr {};
for (std::size_t i {}; i < sizeof(T); ++i)
{
arr[i] = bytes[i];
}
return from_be(T{std::bit_cast<underlying_type>(arr)});
}
}

} // namespace boost::safe_numbers

#endif //BOOST_SAFE_NUMBERS_BYTE_CONVERSIONS_HPP
2 changes: 2 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ run test_verified_limits.cpp ;
# Byte conversion tests
run test_to_from_be.cpp ;
run test_to_from_le.cpp ;
run test_to_from_be_bytes.cpp ;
compile-fail compile_fail_from_be_bytes_extent.cpp ;

# Utility function tests
run test_isqrt.cpp ;
Expand Down
22 changes: 22 additions & 0 deletions test/compile_fail_from_be_bytes_extent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

// This test verifies that from_be_bytes produces a compile error when
// the span's fixed extent does not match the target type's size.

#include <boost/safe_numbers/byte_conversions.hpp>
#include <array>
#include <cstddef>
#include <span>

using namespace boost::safe_numbers;

int main()
{
// u32 requires 4 bytes, but we provide a 2-byte span
const std::array<std::byte, 2> bytes {std::byte{0x01}, std::byte{0x02}};
auto val = from_be_bytes<u32>(std::span<const std::byte, 2>{bytes});

return 0;
}
Loading