util/ carries no wire surface, so it lands before the contract freeze. - error: enum class Error, the engine-wide failure taxonomy; is_retryable enumerates every value (no default:) so -Wswitch forces the retry decision on each future addition. ErrorInfo carries context/http_status. - result: Result<T> over std::expected<T, ErrorInfo>, Result<void>, VDM_TRY / VDM_TRY_ASSIGN. Errors returned, never thrown, on the transfer path. - bytes: span aliases, LE load_le/store_le (debug-asserted precondition, not input validation), and a bounds-checked latching ByteReader for the .veloxpart.meta reader. - event_bus: typed thread-safe pub/sub; header states plainly that unsubscribe is not a quiesce point and download_task will need its own drain. - thread_pool: std::jthread pool; dtor joins in the body before members die (fixed a use-after-destruction on cv_/mu_). Header notes shutdown is drain-only and DAEMON will need a cancel mode. - log: sink interface (core does no I/O); DAEMON installs one. Tested: -Werror clean, 6 binaries green under plain / ASan+UBSan / TSan. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
95 lines
2.8 KiB
C++
95 lines
2.8 KiB
C++
#include "vdm/util/bytes.hpp"
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include "vtest.hpp"
|
|
|
|
using vdm::as_bytes;
|
|
using vdm::as_chars;
|
|
using vdm::ByteReader;
|
|
using vdm::ByteSpan;
|
|
using vdm::ConstByteSpan;
|
|
using vdm::load_le;
|
|
using vdm::store_le;
|
|
|
|
VT_TEST(le_roundtrip_u32) {
|
|
std::array<std::byte, 4> buf{};
|
|
store_le<std::uint32_t>(buf, 0x01020304u);
|
|
// little-endian: least significant byte first
|
|
VT_CHECK_EQ(std::to_integer<int>(buf[0]), 0x04);
|
|
VT_CHECK_EQ(std::to_integer<int>(buf[1]), 0x03);
|
|
VT_CHECK_EQ(std::to_integer<int>(buf[2]), 0x02);
|
|
VT_CHECK_EQ(std::to_integer<int>(buf[3]), 0x01);
|
|
VT_CHECK_EQ(load_le<std::uint32_t>(buf), 0x01020304u);
|
|
}
|
|
|
|
VT_TEST(le_roundtrip_u64) {
|
|
std::array<std::byte, 8> buf{};
|
|
const std::uint64_t v = 0xDEADBEEF0BADF00Dull;
|
|
store_le<std::uint64_t>(buf, v);
|
|
VT_CHECK_EQ(load_le<std::uint64_t>(buf), v);
|
|
}
|
|
|
|
VT_TEST(as_bytes_as_chars_roundtrip) {
|
|
std::string_view s = "veloxpart";
|
|
ConstByteSpan b = as_bytes(s);
|
|
VT_CHECK_EQ(b.size(), s.size());
|
|
VT_CHECK_EQ(std::string(as_chars(b)), std::string(s));
|
|
}
|
|
|
|
VT_TEST(byte_reader_sequential_reads) {
|
|
// magic "VDMP", u16 version=2, u16 flags=0, u64 total=5
|
|
std::array<std::byte, 4 + 2 + 2 + 8> buf{};
|
|
ByteSpan w(buf);
|
|
std::memcpy(buf.data(), "VDMP", 4);
|
|
store_le<std::uint16_t>(w.subspan(4), 2);
|
|
store_le<std::uint16_t>(w.subspan(6), 0);
|
|
store_le<std::uint64_t>(w.subspan(8), 5);
|
|
|
|
ByteReader r{ConstByteSpan(buf)};
|
|
VT_CHECK_EQ(std::string(as_chars(r.raw(4))), std::string("VDMP"));
|
|
VT_CHECK_EQ(r.u16(), 2);
|
|
VT_CHECK_EQ(r.u16(), 0);
|
|
VT_CHECK_EQ(r.u64(), 5u);
|
|
VT_CHECK(!r.overran());
|
|
VT_CHECK_EQ(r.remaining(), 0u);
|
|
}
|
|
|
|
VT_TEST(byte_reader_latches_on_overrun) {
|
|
std::array<std::byte, 3> buf{};
|
|
ByteReader r{ConstByteSpan(buf)};
|
|
VT_CHECK_EQ(r.u16(), 0); // ok, 2 of 3 consumed
|
|
VT_CHECK(!r.overran());
|
|
VT_CHECK_EQ(r.u32(), 0u); // wants 4, only 1 left -> latch
|
|
VT_CHECK(r.overran());
|
|
VT_CHECK_EQ(r.remaining(), 0u);
|
|
// further reads stay zero and latched
|
|
VT_CHECK_EQ(r.u8(), 0);
|
|
VT_CHECK(r.overran());
|
|
}
|
|
|
|
VT_TEST(byte_reader_length_prefixed_string) {
|
|
std::array<std::byte, 4 + 5> buf{};
|
|
ByteSpan w(buf);
|
|
store_le<std::uint32_t>(w, 5);
|
|
std::memcpy(buf.data() + 4, "hello", 5);
|
|
|
|
ByteReader r{ConstByteSpan(buf)};
|
|
VT_CHECK_EQ(std::string(r.lp_string()), std::string("hello"));
|
|
VT_CHECK(!r.overran());
|
|
}
|
|
|
|
VT_TEST(byte_reader_length_prefixed_string_rejects_bogus_length) {
|
|
std::array<std::byte, 4 + 2> buf{};
|
|
ByteSpan w(buf);
|
|
store_le<std::uint32_t>(w, 0xFFFFFFFFu); // claims 4 GiB, only 2 bytes follow
|
|
ByteReader r{ConstByteSpan(buf)};
|
|
std::string_view s = r.lp_string();
|
|
VT_CHECK(s.empty());
|
|
VT_CHECK(r.overran());
|
|
}
|