hopper_sdk/
fingerprint.rs1use hopper_schema::{LayoutManifest, ProgramIdl};
13
14pub const LAYOUT_ID_OFFSET: usize = 4;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum FingerprintCheck {
22 Match,
24 Mismatch {
26 expected: [u8; 8],
28 actual: [u8; 8],
30 },
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum FingerprintError {
36 TooShort,
38 UnknownLayout,
40}
41
42pub fn read_layout_id(bytes: &[u8]) -> Result<[u8; 8], FingerprintError> {
46 if bytes.len() < LAYOUT_ID_OFFSET + 8 {
47 return Err(FingerprintError::TooShort);
48 }
49 let mut id = [0u8; 8];
50 id.copy_from_slice(&bytes[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8]);
51 Ok(id)
52}
53
54pub fn check_against_layout(
56 bytes: &[u8],
57 layout: &LayoutManifest,
58) -> Result<FingerprintCheck, FingerprintError> {
59 let actual = read_layout_id(bytes)?;
60 if actual == layout.layout_id {
61 Ok(FingerprintCheck::Match)
62 } else {
63 Ok(FingerprintCheck::Mismatch {
64 expected: layout.layout_id,
65 actual,
66 })
67 }
68}
69
70pub fn check_in_idl(
73 bytes: &[u8],
74 idl: &ProgramIdl,
75 layout_name: &str,
76) -> Result<FingerprintCheck, FingerprintError> {
77 let layout = idl
78 .find_account(layout_name)
79 .ok_or(FingerprintError::UnknownLayout)?;
80 check_against_layout(bytes, layout)
81}
82
83pub fn identify_in_idl<'a>(bytes: &[u8], idl: &'a ProgramIdl) -> Option<&'a LayoutManifest> {
86 let actual = read_layout_id(bytes).ok()?;
87 let mut i = 0;
88 while i < idl.accounts.len() {
89 if idl.accounts[i].layout_id == actual {
90 return Some(&idl.accounts[i]);
91 }
92 i += 1;
93 }
94 None
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use hopper_schema::FieldDescriptor;
101
102 const SAMPLE_ID: [u8; 8] = [9, 8, 7, 6, 5, 4, 3, 2];
103
104 fn mk_manifest() -> LayoutManifest {
105 LayoutManifest {
106 name: "Vault",
107 disc: 7,
108 version: 1,
109 layout_id: SAMPLE_ID,
110 total_size: 80,
111 field_count: 0,
112 fields: &[] as &[FieldDescriptor],
113 }
114 }
115
116 fn mk_bytes(id: [u8; 8]) -> [u8; 32] {
117 let mut b = [0u8; 32];
118 b[0] = 7; b[1] = 1; b[LAYOUT_ID_OFFSET..LAYOUT_ID_OFFSET + 8].copy_from_slice(&id);
121 b
122 }
123
124 #[test]
125 fn matches_when_equal() {
126 let bytes = mk_bytes(SAMPLE_ID);
127 let layout = mk_manifest();
128 assert_eq!(
129 check_against_layout(&bytes, &layout).unwrap(),
130 FingerprintCheck::Match
131 );
132 }
133
134 #[test]
135 fn reports_mismatch_with_actual() {
136 let other = [1, 1, 1, 1, 1, 1, 1, 1];
137 let bytes = mk_bytes(other);
138 let layout = mk_manifest();
139 let check = check_against_layout(&bytes, &layout).unwrap();
140 assert_eq!(
141 check,
142 FingerprintCheck::Mismatch {
143 expected: SAMPLE_ID,
144 actual: other,
145 }
146 );
147 }
148
149 #[test]
150 fn too_short_returns_error() {
151 let bytes = [0u8; 6];
152 let layout = mk_manifest();
153 assert_eq!(
154 check_against_layout(&bytes, &layout),
155 Err(FingerprintError::TooShort)
156 );
157 }
158}