use hopper_schema::{LayoutManifest, ProgramIdl};
pub const LAYOUT_ID_OFFSET: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FingerprintCheck {
Match,
Mismatch {
expected: [u8; 8],
actual: [u8; 8],
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FingerprintError {
TooShort,
UnknownLayout,
}
pub fn read_layout_id(bytes: &[u8]) -> Result<[u8; 8], FingerprintError> {
if bytes.len() < LAYOUT_ID_OFFSET + 8 {
return Err(FingerprintError::TooShort);
}
let mut id = [0u8; 8];
id.copy_from_slice(&bytes[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8]);
Ok(id)
}
pub fn check_against_layout(
bytes: &[u8],
layout: &LayoutManifest,
) -> Result<FingerprintCheck, FingerprintError> {
let actual = read_layout_id(bytes)?;
if actual == layout.layout_id {
Ok(FingerprintCheck::Match)
} else {
Ok(FingerprintCheck::Mismatch {
expected: layout.layout_id,
actual,
})
}
}
pub fn check_in_idl(
bytes: &[u8],
idl: &ProgramIdl,
layout_name: &str,
) -> Result<FingerprintCheck, FingerprintError> {
let layout = idl
.find_account(layout_name)
.ok_or(FingerprintError::UnknownLayout)?;
check_against_layout(bytes, layout)
}
pub fn identify_in_idl<'a>(bytes: &[u8], idl: &'a ProgramIdl) -> Option<&'a LayoutManifest> {
let actual = read_layout_id(bytes).ok()?;
let mut i = 0;
while i < idl.accounts.len() {
if idl.accounts[i].layout_id == actual {
return Some(&idl.accounts[i]);
}
i += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use hopper_schema::FieldDescriptor;
const SAMPLE_ID: [u8; 8] = [9, 8, 7, 6, 5, 4, 3, 2];
fn mk_manifest() -> LayoutManifest {
LayoutManifest {
name: "Vault",
disc: 7,
version: 1,
layout_id: SAMPLE_ID,
total_size: 80,
field_count: 0,
fields: &[] as &[FieldDescriptor],
}
}
fn mk_bytes(id: [u8; 8]) -> [u8; 32] {
let mut b = [0u8; 32];
b[0] = 7; b[1] = 1; b[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8].copy_from_slice(&id);
b
}
#[test]
fn matches_when_equal() {
let bytes = mk_bytes(SAMPLE_ID);
let layout = mk_manifest();
assert_eq!(
check_against_layout(&bytes, &layout).unwrap(),
FingerprintCheck::Match
);
}
#[test]
fn reports_mismatch_with_actual() {
let other = [1, 1, 1, 1, 1, 1, 1, 1];
let bytes = mk_bytes(other);
let layout = mk_manifest();
let check = check_against_layout(&bytes, &layout).unwrap();
assert_eq!(
check,
FingerprintCheck::Mismatch {
expected: SAMPLE_ID,
actual: other,
}
);
}
#[test]
fn too_short_returns_error() {
let bytes = [0u8; 6];
let layout = mk_manifest();
assert_eq!(
check_against_layout(&bytes, &layout),
Err(FingerprintError::TooShort)
);
}
}