///|
/// Version 1 frame: 28-byte metadata, 4-byte CRC32C, then shard payload.
/// Integer fields are unsigned little-endian; ids and lengths are capped at
/// signed 31-bit values so they behave identically on all MoonBit targets.
pub struct ShardEnvelope {
set_id : Int
stripe_index : Int
shard_index : Int
data_count : Int
parity_count : Int
original_length : Int
payload : Bytes
}
///|
pub fn ShardEnvelope::new(
codec : Codec,
set_id : Int,
stripe_index : Int,
shard_index : Int,
original_length : Int,
payload : Bytes,
) -> ShardEnvelope raise ErasureError {
if set_id < 0 || stripe_index < 0 {
raise InvalidEnvelope("set and stripe ids must be nonnegative")
}
if shard_index < 0 || shard_index >= codec.total_count() {
raise InvalidIndex(shard_index)
}
codec.check_length(payload.length())
if original_length < 1 ||
original_length > codec.data_count() * payload.length() {
raise InvalidEnvelope("original length must fit in data shards")
}
let expected_length = (original_length - 1) / codec.data_count() + 1
if payload.length() != expected_length {
raise InvalidEnvelope("shard payload length is not canonical")
}
{
set_id,
stripe_index,
shard_index,
data_count: codec.data_count(),
parity_count: codec.parity_count(),
original_length,
payload: copy_shard(payload),
}
}
///|
pub fn ShardEnvelope::set_id(self : ShardEnvelope) -> Int {
self.set_id
}
///|
pub fn ShardEnvelope::stripe_index(self : ShardEnvelope) -> Int {
self.stripe_index
}
///|
pub fn ShardEnvelope::shard_index(self : ShardEnvelope) -> Int {
self.shard_index
}
///|
pub fn ShardEnvelope::data_count(self : ShardEnvelope) -> Int {
self.data_count
}
///|
pub fn ShardEnvelope::parity_count(self : ShardEnvelope) -> Int {
self.parity_count
}
///|
pub fn ShardEnvelope::original_length(self : ShardEnvelope) -> Int {
self.original_length
}
///|
pub fn ShardEnvelope::payload(self : ShardEnvelope) -> Bytes {
copy_shard(self.payload)
}
///|
fn write_u16(output : Array[Byte], value : Int) -> Unit {
output.push((value & 0xff).to_byte())
output.push(((value >> 8) & 0xff).to_byte())
}
///|
fn write_u32(output : Array[Byte], value : UInt) -> Unit {
for shift in [0, 8, 16, 24] {
output.push(((value >> shift) & 0xffU).to_byte())
}
}
///|
fn read_u16(input : Bytes, offset : Int) -> Int {
input[offset].to_int() | (input[offset + 1].to_int() << 8)
}
///|
fn read_u32(input : Bytes, offset : Int) -> UInt {
input[offset].to_uint() |
(input[offset + 1].to_uint() << 8) |
(input[offset + 2].to_uint() << 16) |
(input[offset + 3].to_uint() << 24)
}
///|
fn read_bounded_int(input : Bytes, offset : Int) -> Int raise ErasureError {
let value = read_u32(input, offset)
if value > 0x7fffffffU {
raise InvalidEnvelope("integer exceeds portable signed range")
}
value.reinterpret_as_int()
}
///|
/// Serialize a validated envelope with a CRC covering metadata and payload.
pub fn ShardEnvelope::to_bytes(self : ShardEnvelope) -> Bytes {
let output : Array[Byte] = [b'M', b'E', b'R', b'S', b'\x01', b'\x00']
write_u16(output, 32)
write_u32(output, self.set_id.reinterpret_as_uint())
write_u32(output, self.stripe_index.reinterpret_as_uint())
write_u16(output, self.shard_index)
write_u16(output, self.data_count)
write_u16(output, self.parity_count)
write_u16(output, 0)
write_u32(output, self.original_length.reinterpret_as_uint())
let checksum = crc32c_header_payload(output, self.payload)
write_u32(output, checksum)
for byte in self.payload {
output.push(byte)
}
Bytes::from_array(output)
}
///|
/// Parse one bounded frame. Invalid metadata and damaged payloads fail closed.
pub fn ShardEnvelope::from_bytes(
input : Bytes,
max_encoded_bytes? : Int = 16_777_216,
) -> ShardEnvelope raise ErasureError {
if input.length() < 33 {
raise InvalidEnvelope("frame is shorter than header plus one payload byte")
}
if input[0] != b'M' ||
input[1] != b'E' ||
input[2] != b'R' ||
input[3] != b'S' {
raise InvalidEnvelope("bad magic")
}
let version = input[4].to_int()
if version != 1 {
raise UnsupportedVersion(version)
}
if input[5] != b'\x00' || read_u16(input, 6) != 32 || read_u16(input, 22) != 0 {
raise InvalidEnvelope(
"nonzero flags, reserved bytes or wrong header length",
)
}
let set_id = read_bounded_int(input, 8)
let stripe_index = read_bounded_int(input, 12)
let shard_index = read_u16(input, 16)
let data_count = read_u16(input, 18)
let parity_count = read_u16(input, 20)
let original_length = read_bounded_int(input, 24)
let max_shard_bytes = validated_max_shard_bytes(
data_count, parity_count, max_encoded_bytes,
)
if shard_index >= data_count + parity_count {
raise InvalidIndex(shard_index)
}
let length = input.length() - 32
if length > max_shard_bytes {
raise ResourceLimit("frame payload exceeds encoded byte budget")
}
if original_length < 1 || original_length > data_count * length {
raise InvalidEnvelope("original length does not fit data shards")
}
if length != (original_length - 1) / data_count + 1 {
raise InvalidEnvelope("shard payload length is not canonical")
}
let payload = Bytes::from_array(input[32:input.length()].to_array())
let header = input[0:28].to_array()
let expected = read_u32(input, 28)
if crc32c_header_payload(header, payload) != expected {
raise ChecksumMismatch(shard_index)
}
{
set_id,
stripe_index,
shard_index,
data_count,
parity_count,
original_length,
payload,
}
}