106 lines
4.0 KiB
C++
106 lines
4.0 KiB
C++
// 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 <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
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 <class T>
|
|
inline void putLE(std::vector<std::uint8_t>& out, T v) {
|
|
static_assert(std::is_unsigned_v<T> && !std::is_same_v<T, bool>, "putLE takes the unsigned wire image");
|
|
for (std::size_t b = 0; b < sizeof(T); ++b) {
|
|
out.push_back(static_cast<std::uint8_t>((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<std::uint64_t>(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<std::uint8_t>& bytes;
|
|
std::size_t pos = 0;
|
|
bool ok = true;
|
|
|
|
explicit ByteReader(const std::vector<std::uint8_t>& b) : bytes(b) {}
|
|
|
|
// Read one unsigned integral little-endian (fixed sizeof(T) width).
|
|
template <class T>
|
|
T readLE() {
|
|
static_assert(std::is_unsigned_v<T>, "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<T>(bytes[pos + b]) << (b * 8);
|
|
}
|
|
pos += sizeof(T);
|
|
return v;
|
|
}
|
|
|
|
std::uint8_t u8() { return readLE<std::uint8_t>(); }
|
|
std::uint32_t u32() { return readLE<std::uint32_t>(); }
|
|
std::uint64_t u64() { return readLE<std::uint64_t>(); }
|
|
// Signed ints ride the wire as fixed-width two's-complement unsigned images.
|
|
int i32() { return static_cast<int>(static_cast<std::int32_t>(u32())); }
|
|
std::int64_t i64() { return static_cast<std::int64_t>(u64()); }
|
|
|
|
std::string str(std::uint32_t len) {
|
|
if (!ok || pos + len > bytes.size()) {
|
|
ok = false;
|
|
return {};
|
|
}
|
|
std::string s(reinterpret_cast<const char*>(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<std::uint32_t>(bytes[pos]) |
|
|
(static_cast<std::uint32_t>(bytes[pos + 1]) << 8) |
|
|
(static_cast<std::uint32_t>(bytes[pos + 2]) << 16) |
|
|
(static_cast<std::uint32_t>(bytes[pos + 3]) << 24);
|
|
}
|
|
};
|
|
|
|
} // namespace reasampler::wire
|