///|
/// Per-call limits are checked before allocating the cell array.
pub struct DecodeLimits {
priv max_width : Int32
priv max_members : Int32
}
///|
pub fn DecodeLimits::new(
max_width~ : Int32 = 65536,
max_members~ : Int32 = 10000000,
) -> DecodeLimits raise ReconcileError {
if max_width < 1 || max_width > 262144 || max_members < 0 {
raise InvalidConfig("invalid wire decode limits")
}
{ max_width, max_members, }
}
///|
fn put_u32(out : Array[Byte], value : UInt32) -> Unit {
for i = 0; i < 4; i = i + 1 {
out.push((value >> (i * 8)).to_byte())
}
}
///|
fn put_u64(out : Array[Byte], value : UInt64) -> Unit {
for i = 0; i < 8; i = i + 1 {
out.push((value >> (i * 8)).to_byte())
}
}
///|
fn get_u32(data : Bytes, offset : Int32) -> UInt32 {
let mut value = 0U
for i = 0; i < 4; i = i + 1 {
value = value | (data[offset + i].to_uint() << (8 * i))
}
value
}
///|
fn get_u64(data : Bytes, offset : Int32) -> UInt64 {
let mut value = 0UL
for i = 0; i < 8; i = i + 1 {
value = value | (data[offset + i].to_uint64() << (8 * i))
}
value
}
///|
// IEEE CRC-32 detects accidental damage only, not a malicious peer.
fn crc32(data : Bytes, end : Int32) -> UInt32 {
let mut crc = 0xffffffffU
for i = 0; i < end; i = i + 1 {
crc = crc ^ data[i].to_uint()
for j = 0; j < 8; j = j + 1 {
crc = if (crc & 1U) != 0U { (crc >> 1) ^ 0xedb88320U } else { crc >> 1 }
}
}
crc ^ 0xffffffffU
}
///|
/// MRCL v1, little-endian, fixed 64-byte header and 20-byte cells.
pub fn Snapshot::encode(self : Snapshot) -> Bytes {
let out : Array[Byte] = [77, 82, 67, 76, 1, 0, 0, 0]
put_u32(out, self.sketch.config.width.reinterpret_as_uint())
put_u32(out, self.fingerprint.count.reinterpret_as_uint())
put_u64(out, self.sketch.config.seed)
put_u64(out, self.scope)
put_u64(out, self.epoch)
put_u64(out, self.fingerprint.sum)
put_u64(out, self.fingerprint.xor)
put_u64(out, 0UL)
for cell in self.sketch.cells {
put_u32(out, cell.count.reinterpret_as_uint())
put_u64(out, cell.key)
put_u64(out, cell.check)
}
let payload = Bytes::from_array(out)
put_u32(out, crc32(payload, payload.length()))
Bytes::from_array(out)
}
///|
pub fn Snapshot::encoded_size(self : Snapshot) -> Int32 {
68 + self.sketch.config.cells() * 20
}
///|
/// Wire accepts inventory snapshots, not arbitrary signed difference tables.
/// Strict length/version/reserved-byte checks precede CRC and allocation.
pub fn Snapshot::decode(
data : Bytes,
limits : DecodeLimits,
) -> Snapshot raise ReconcileError {
if data.length() < 68 {
raise InvalidFrame("truncated header")
}
if data[0] != 77 || data[1] != 82 || data[2] != 67 || data[3] != 76 {
raise InvalidFrame("bad magic")
}
if data[4] != 1 ||
data[5] != 0 ||
data[6] != 0 ||
data[7] != 0 ||
get_u64(data, 56) != 0UL {
raise InvalidFrame("unsupported version or reserved fields")
}
let width_raw = get_u32(data, 8)
let count_raw = get_u32(data, 12)
if width_raw == 0U || width_raw > limits.max_width.reinterpret_as_uint() {
raise LimitExceeded("frame width")
}
if count_raw > limits.max_members.reinterpret_as_uint() {
raise LimitExceeded("frame members")
}
let width = width_raw.reinterpret_as_int()
let members = count_raw.reinterpret_as_int()
let expected = 68 + width * 60
if data.length() != expected {
raise InvalidFrame("incorrect frame length")
}
if get_u32(data, expected - 4) != crc32(data, expected - 4) {
raise InvalidFrame("CRC mismatch")
}
let config = Config::new(width, seed=get_u64(data, 16))
let sketch = Sketch::new(config)
let counts : Array[Int64] = [0L, 0L, 0L]
let band_keys : Array[UInt64] = [0UL, 0UL, 0UL]
let band_checks : Array[UInt64] = [0UL, 0UL, 0UL]
for i = 0; i < config.cells(); i = i + 1 {
let offset = 64 + 20 * i
let raw = get_u32(data, offset)
if raw > count_raw {
raise InvalidFrame("cell count exceeds inventory size")
}
let count = raw.reinterpret_as_int()
let key = get_u64(data, offset + 4)
let check = get_u64(data, offset + 12)
if count == 0 && (key != 0UL || check != 0UL) {
raise InvalidFrame("nonempty zero-count inventory cell")
}
if count == 1 &&
(
check != check_key(key, config.seed) ||
!positions(key, config).contains(i)
) {
raise InvalidFrame("invalid singleton")
}
sketch.cells[i] = { count, key, check, }
let band = i / width
counts[band] = counts[band] + count.to_int64()
band_keys[band] = band_keys[band] ^ key
band_checks[band] = band_checks[band] ^ check
}
for band = 0; band < 3; band = band + 1 {
if counts[band] != members.to_int64() ||
band_keys[band] != band_keys[0] ||
band_checks[band] != band_checks[0] {
raise InvalidFrame("inconsistent hash bands")
}
}
let fingerprint : Fingerprint = {
count: members,
sum: get_u64(data, 40),
xor: get_u64(data, 48),
}
if members == 0 && fingerprint != empty_fingerprint() {
raise InvalidFrame("invalid empty fingerprint")
}
{ scope: get_u64(data, 24), epoch: get_u64(data, 32), fingerprint, sketch, }
}