use hopper_schema::{FieldDescriptor, LayoutManifest};
#[cfg(feature = "std")]
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy)]
pub struct FieldDelta<'a> {
pub field: &'a FieldDescriptor,
pub before: &'a [u8],
pub after: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiffError {
LengthMismatch {
before: usize,
after: usize,
},
BufferTooShort,
}
#[cfg(feature = "std")]
pub fn fixed_size_diff<'a>(
before: &'a [u8],
after: &'a [u8],
manifest: &'a LayoutManifest,
) -> Result<Vec<FieldDelta<'a>>, DiffError> {
if before.len() != after.len() {
return Err(DiffError::LengthMismatch {
before: before.len(),
after: after.len(),
});
}
if before.len() < manifest.total_size {
return Err(DiffError::BufferTooShort);
}
let mut out = Vec::new();
let mut i = 0;
while i < manifest.fields.len() {
let f = &manifest.fields[i];
let start = f.offset as usize;
let end = start + f.size as usize;
if end > before.len() {
break;
}
if before[start..end] != after[start..end] {
out.push(FieldDelta {
field: f,
before: &before[start..end],
after: &after[start..end],
});
}
i += 1;
}
Ok(out)
}
pub fn field_change_mask(before: &[u8], after: &[u8], manifest: &LayoutManifest) -> u64 {
let mut mask = 0u64;
let common = core::cmp::min(before.len(), after.len());
let mut i = 0;
while i < manifest.fields.len() && i < 64 {
let f = &manifest.fields[i];
let start = f.offset as usize;
let end = start + f.size as usize;
if end > common {
break;
}
if before[start..end] != after[start..end] {
mask |= 1u64 << i;
}
i += 1;
}
mask
}
#[cfg(test)]
mod tests {
use super::*;
use hopper_schema::FieldIntent;
fn fields() -> &'static [FieldDescriptor] {
static F: [FieldDescriptor; 2] = [
FieldDescriptor {
name: "a",
canonical_type: "u64",
size: 8,
offset: 0,
intent: FieldIntent::Counter,
},
FieldDescriptor {
name: "b",
canonical_type: "u64",
size: 8,
offset: 8,
intent: FieldIntent::Balance,
},
];
&F
}
fn manifest() -> LayoutManifest {
LayoutManifest {
name: "Pair",
disc: 1,
version: 1,
layout_id: [0; 8],
total_size: 16,
field_count: 2,
fields: fields(),
}
}
#[test]
fn mask_detects_changed_field() {
let mut before = [0u8; 16];
let mut after = [0u8; 16];
before[8..16].copy_from_slice(&1u64.to_le_bytes());
after[8..16].copy_from_slice(&2u64.to_le_bytes());
let m = manifest();
assert_eq!(field_change_mask(&before, &after, &m), 0b10);
}
#[cfg(feature = "std")]
#[test]
fn fixed_size_diff_returns_deltas() {
let before = [0u8; 16];
let mut after = [0u8; 16];
after[0..8].copy_from_slice(&5u64.to_le_bytes());
let m = manifest();
let d = fixed_size_diff(&before, &after, &m).unwrap();
assert_eq!(d.len(), 1);
assert_eq!(d[0].field.name, "a");
}
}