feat(codec): add CodecException, ByteWriter and Crc32C

This commit is contained in:
dualfroz committed 2026-09-04 17:56:03 +02:00
1 parent b54f41d3b5
commit fccb54cd70
2 files changed
+127

No files matched your search

@@ -0,0 +1,113 @@
package kestrel.store.codec
import kestrel.KestrelException
class CodecException(message: String) : KestrelException(message)
/** Growable little-endian-free byte sink. Multi-byte integers are written big-endian. */
class ByteWriter(initialCapacity: Int = 32) {
private var buffer = ByteArray(initialCapacity)
private var size = 0
val length: Int get() = size
private fun ensure(extra: Int) {
if (size + extra <= buffer.size) return
var newCapacity = buffer.size * 2
while (newCapacity < size + extra) newCapacity *= 2
buffer = buffer.copyOf(newCapacity)
}
fun writeByte(value: Int): ByteWriter {
ensure(1)
buffer[size++] = value.toByte()
return this
}
fun writeBytes(bytes: ByteArray): ByteWriter {
ensure(bytes.size)
System.arraycopy(bytes, 0, buffer, size, bytes.size)
size += bytes.size
return this
}
fun writeFixedLong(value: Long): ByteWriter {
ensure(8)
for (i in 7 downTo 0) {
buffer[size++] = ((value ushr (i * 8)) and 0xFF).toByte()
}
return this
}
fun writeFixedDouble(value: Double): ByteWriter = writeFixedLong(java.lang.Double.doubleToLongBits(value))
/** Unsigned LEB128 varint. */
fun writeVarInt(value: Long): ByteWriter {
var remaining = value
while (true) {
val byte = (remaining and 0x7F).toInt()
remaining = remaining ushr 7
if (remaining == 0L) {
writeByte(byte)
return this
}
writeByte(byte or 0x80)
}
}
/** Zigzag-encoded signed varint, safe for negative numbers. */
fun writeZigZag(value: Long): ByteWriter = writeVarInt((value shl 1) xor (value shr 63))
fun writeLengthPrefixed(bytes: ByteArray): ByteWriter {
writeVarInt(bytes.size.toLong())
writeBytes(bytes)
return this
}
fun toByteArray(): ByteArray = buffer.copyOf(size)
}
class ByteReader(private val data: ByteArray, private var position: Int = 0) {
val remaining: Int get() = data.size - position
fun hasRemaining(): Boolean = position < data.size
fun readByte(): Int {
if (position >= data.size) throw CodecException("unexpected end of buffer")
return data[position++].toInt() and 0xFF
}
fun readBytes(count: Int): ByteArray {
if (position + count > data.size) throw CodecException("unexpected end of buffer")
val slice = data.copyOfRange(position, position + count)
position += count
return slice
}
fun readFixedLong(): Long {
var result = 0L
repeat(8) { result = (result shl 8) or readByte().toLong() }
return result
}
fun readFixedDouble(): Double = java.lang.Double.longBitsToDouble(readFixedLong())
fun readVarInt(): Long {
var result = 0L
var shift = 0
while (true) {
val byte = readByte()
result = result or ((byte.toLong() and 0x7F) shl shift)
if (byte and 0x80 == 0) return result
shift += 7
if (shift > 63) throw CodecException("varint too long")
}
}
fun readZigZag(): Long {
val encoded = readVarInt()
return (encoded ushr 1) xor -(encoded and 1)
}
fun readLengthPrefixed(): ByteArray = readBytes(readVarInt().toInt())
}
@@ -0,0 +1,14 @@
package kestrel.store.codec
import java.util.zip.CRC32C
/** Castagnoli CRC-32 checksums used to detect corruption of on-disk pages and records. */
object Crc32C {
fun of(bytes: ByteArray): Int = of(bytes, 0, bytes.size)
fun of(bytes: ByteArray, offset: Int, length: Int): Int {
val crc = CRC32C()
crc.update(bytes, offset, length)
return crc.value.toInt()
}
}