// core/wire/bytes.h — the ONE little-endian byte codec. Pure, header-only: // standard library only — no REAPER, no SWELL, no VST3. // // component_state_io is the biggest consumer. Wire format is FROZEN: putLE // emits fixed-width LSB-first bytes exactly as the hand-rolled copies it // replaced did, and ByteReader preserves the latch-on-truncation contract — // once a read runs past the end, ok latches false and every subsequent read // yields zeros/empties, so a truncated blob degrades to a partial parse, // never an out-of-bounds read. #pragma once #include #include #include #include #include #include namespace reasampler::wire { // Append `v` little-endian (LSB first, sizeof(T) bytes). Unsigned integral types // only — signed values go on the wire as their two's-complement unsigned image // (cast at the call site, the established idiom: u32 for int, u64 for int64). template inline void putLE(std::vector& out, T v) { static_assert(std::is_unsigned_v && !std::is_same_v, "putLE takes the unsigned wire image"); for (std::size_t b = 0; b < sizeof(T); ++b) { out.push_back(static_cast((v >> (b * 8)) & 0xFF)); } } // Signed 64-bit values ride the wire as their two's-complement unsigned image (the putLE // call site's u64 cast). The one home for that cast — component_state_io and params_payload // both need it and must not carry their own copies. inline std::uint64_t asU64(std::int64_t v) { return static_cast(v); } // IEEE-754 double <-> u64 bit-cast for the wire (memcpy is the only defined // type-pun in C++17). Doubles ride the wire as their u64 bit image via putLE. inline std::uint64_t doubleToBits(double d) { std::uint64_t bits; std::memcpy(&bits, &d, sizeof(bits)); return bits; } inline double bitsToDouble(std::uint64_t bits) { double d; std::memcpy(&d, &bits, sizeof(d)); return d; } // A bounded little-endian reader over a byte blob (see the file header for the // truncation-latch contract). struct ByteReader { const std::vector& bytes; std::size_t pos = 0; bool ok = true; explicit ByteReader(const std::vector& b) : bytes(b) {} // Read one unsigned integral little-endian (fixed sizeof(T) width). template T readLE() { static_assert(std::is_unsigned_v, "readLE yields the unsigned wire image"); if (!ok || pos + sizeof(T) > bytes.size()) { ok = false; return 0; } T v = 0; for (std::size_t b = 0; b < sizeof(T); ++b) { v |= static_cast(bytes[pos + b]) << (b * 8); } pos += sizeof(T); return v; } std::uint8_t u8() { return readLE(); } std::uint32_t u32() { return readLE(); } std::uint64_t u64() { return readLE(); } // Signed ints ride the wire as fixed-width two's-complement unsigned images. int i32() { return static_cast(static_cast(u32())); } std::int64_t i64() { return static_cast(u64()); } std::string str(std::uint32_t len) { if (!ok || pos + len > bytes.size()) { ok = false; return {}; } std::string s(reinterpret_cast(bytes.data() + pos), len); pos += len; return s; } // Non-consuming peek of the next u32 (format-marker probes). Yields 0 and // latches nothing when fewer than 4 bytes remain — the caller treats a short // blob as "no marker" and falls through to its (also-guarded) fallback read. std::uint32_t peekU32() const { if (!ok || pos + 4 > bytes.size()) return 0; return static_cast(bytes[pos]) | (static_cast(bytes[pos + 1]) << 8) | (static_cast(bytes[pos + 2]) << 16) | (static_cast(bytes[pos + 3]) << 24); } }; } // namespace reasampler::wire