diff --git a/core/include/vf/Codec.h b/core/include/vf/Codec.h new file mode 100644 index 0000000..27a5e1f --- /dev/null +++ b/core/include/vf/Codec.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include + +namespace vf { + +using ByteBuffer = std::vector; + +// Growable byte writer with big-endian fixed ints plus LEB128 varints and +// zigzag signed varints. Shared codec surface for the region and wire formats. +class ByteWriter { +public: + void putU8(uint8_t v) { buffer.push_back(v); } + void putU16(uint16_t v); + void putU32(uint32_t v); + void putU64(uint64_t v); + + void putVarU32(uint32_t v); + void putVarU64(uint64_t v); + void putVarI32(int32_t v); + void putVarI64(int64_t v); + + void putFloat(float v); + void putDouble(double v); + void putBytes(const uint8_t* p, std::size_t n); + void putString(const std::string& s); + + std::size_t size() const { return buffer.size(); } + const ByteBuffer& bytes() const { return buffer; } + ByteBuffer& bytes() { return buffer; } + + // Reserve a placeholder byte and return its index so a header can be + // patched once the trailing payload length is known. + std::size_t reserveU8() { + std::size_t at = buffer.size(); + buffer.push_back(0); + return at; + } + void patchU8(std::size_t at, uint8_t v) { buffer[at] = v; } + +private: + ByteBuffer buffer; +}; + +// Cursor over an immutable byte span. Every read is bounds checked and sets a +// sticky failure flag; callers check ok() at the end rather than each read. +class ByteReader { +public: + ByteReader(const uint8_t* data, std::size_t size) : data(data), size(size) {} + + bool getU8(uint8_t& out); + bool getU16(uint16_t& out); + bool getU32(uint32_t& out); + bool getU64(uint64_t& out); + + bool getVarU32(uint32_t& out); + bool getVarU64(uint64_t& out); + bool getVarI32(int32_t& out); + bool getVarI64(int64_t& out); + + bool getFloat(float& out); + bool getDouble(double& out); + bool getBytes(uint8_t* dst, std::size_t n); + bool getString(std::string& out); + + std::size_t position() const { return cursor; } + std::size_t remaining() const { return size - cursor; } + bool exhausted() const { return cursor >= size; } + bool ok() const { return !failed; } + +private: + bool need(std::size_t n); + + const uint8_t* data; + std::size_t size; + std::size_t cursor = 0; + bool failed = false; +}; + +uint32_t zigzagEncode32(int32_t v); +int32_t zigzagDecode32(uint32_t v); +uint64_t zigzagEncode64(int64_t v); +int64_t zigzagDecode64(uint64_t v); + +uint32_t fnv1a32(const uint8_t* data, std::size_t size); +uint64_t fnv1a64(const uint8_t* data, std::size_t size); +uint32_t adler32(const uint8_t* data, std::size_t size); + +// Byte-oriented run-length codec. Literal tokens carry 1..128 bytes, run +// tokens carry a value repeated 3..130 times, so it never expands input by +// more than one byte per 128 literals. +ByteBuffer rleEncode(const uint8_t* data, std::size_t size); +bool rleDecode(const uint8_t* data, std::size_t size, ByteBuffer& out); + +// LZ77 with a hash-chain match finder and a flag-byte token stream (literal or +// varint offset+length match). The raw length is embedded so decode stops +// exactly, and it reconstructs the input byte for byte. +ByteBuffer lz77Compress(const uint8_t* data, std::size_t size); +bool lz77Decompress(const uint8_t* data, std::size_t size, ByteBuffer& out); + +enum class CodecMethod : uint8_t { Store = 0, Rle = 1, Lz77 = 2 }; + +// Frames a payload: method + varint raw length + fnv1a32 of the raw bytes + +// the smallest of {store, rle, lz77}. decompress verifies length and checksum. +ByteBuffer compress(const uint8_t* data, std::size_t size); +ByteBuffer compress(const ByteBuffer& in); +bool decompress(const uint8_t* data, std::size_t size, ByteBuffer& out); + +} // namespace vf