1use core::{fmt, hash::Hash};
8
9use hex::FromHexError;
10use sha2::{digest::FixedOutputReset, digest::Output, Digest, Sha256, Sha512};
11use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
12
13pub trait FsVerityHashValue
18where
19 Self: Clone,
20 Self: From<Output<Self::Digest>>,
21 Self: FromBytes + Immutable + IntoBytes + KnownLayout + Unaligned,
22 Self: Hash + Eq,
23 Self: fmt::Debug,
24 Self: Send + Sync + Unpin + 'static,
25{
26 type Digest: Digest + FixedOutputReset + fmt::Debug;
28 const ALGORITHM: u8;
30 const EMPTY: Self;
32 const ID: &str;
34
35 fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, FromHexError> {
43 let mut value = Self::EMPTY;
44 hex::decode_to_slice(hex.as_ref(), value.as_mut_bytes())?;
45 Ok(value)
46 }
47
48 fn from_object_dir_and_basename(
61 dirnum: u8,
62 basename: impl AsRef<[u8]>,
63 ) -> Result<Self, FromHexError> {
64 let expected_size = 2 * (size_of::<Self>() - 1);
65 let bytes = basename.as_ref();
66 if bytes.len() != expected_size {
67 return Err(FromHexError::InvalidStringLength);
68 }
69 let mut result = Self::EMPTY;
70 result.as_mut_bytes()[0] = dirnum;
71 hex::decode_to_slice(bytes, &mut result.as_mut_bytes()[1..])?;
72 Ok(result)
73 }
74
75 fn from_object_pathname(pathname: impl AsRef<[u8]>) -> Result<Self, FromHexError> {
86 let min_size = 2 * size_of::<Self>() + 1;
88 let bytes = pathname.as_ref();
89 if bytes.len() < min_size {
90 return Err(FromHexError::InvalidStringLength);
91 }
92
93 let trailing = &bytes[bytes.len() - min_size..];
94 let mut result = Self::EMPTY;
95 hex::decode_to_slice(&trailing[0..2], &mut result.as_mut_bytes()[0..1])?;
96 if trailing[2] != b'/' {
97 return Err(FromHexError::InvalidHexCharacter {
98 c: trailing[2] as char,
99 index: 2,
100 });
101 }
102 hex::decode_to_slice(&trailing[3..], &mut result.as_mut_bytes()[1..])?;
103 Ok(result)
104 }
105
106 fn to_object_pathname(&self) -> String {
114 format!(
115 "{:02x}/{}",
116 self.as_bytes()[0],
117 hex::encode(&self.as_bytes()[1..])
118 )
119 }
120
121 fn to_object_dir(&self) -> String {
128 format!("{:02x}", self.as_bytes()[0])
129 }
130
131 fn to_hex(&self) -> String {
136 hex::encode(self.as_bytes())
137 }
138
139 fn to_id(&self) -> String {
144 format!("{}:{}", Self::ID, self.to_hex())
145 }
146}
147
148impl fmt::Debug for Sha256HashValue {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(f, "sha256:{}", self.to_hex())
151 }
152}
153
154impl fmt::Debug for Sha512HashValue {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 write!(f, "sha512:{}", self.to_hex())
157 }
158}
159
160#[derive(Clone, Eq, FromBytes, Hash, Immutable, IntoBytes, KnownLayout, PartialEq, Unaligned)]
164#[repr(C)]
165pub struct Sha256HashValue([u8; 32]);
166
167impl From<Output<Sha256>> for Sha256HashValue {
168 fn from(value: Output<Sha256>) -> Self {
169 Self(value.into())
170 }
171}
172
173impl FsVerityHashValue for Sha256HashValue {
174 type Digest = Sha256;
175 const ALGORITHM: u8 = 1;
176 const EMPTY: Self = Self([0; 32]);
177 const ID: &str = "sha256";
178}
179
180#[derive(Clone, Eq, FromBytes, Hash, Immutable, IntoBytes, KnownLayout, PartialEq, Unaligned)]
184#[repr(C)]
185pub struct Sha512HashValue([u8; 64]);
186
187impl From<Output<Sha512>> for Sha512HashValue {
188 fn from(value: Output<Sha512>) -> Self {
189 Self(value.into())
190 }
191}
192
193impl FsVerityHashValue for Sha512HashValue {
194 type Digest = Sha512;
195 const ALGORITHM: u8 = 2;
196 const EMPTY: Self = Self([0; 64]);
197 const ID: &str = "sha512";
198}
199
200#[cfg(test)]
201mod test {
202 use super::*;
203
204 fn test_fsverity_hash<H: FsVerityHashValue>() {
205 let len = size_of::<H>();
206 let hexlen = len * 2;
207
208 let hex = H::EMPTY.to_hex();
209 assert_eq!(hex.as_bytes(), [b'0'].repeat(hexlen));
210
211 assert_eq!(H::EMPTY.to_id(), format!("{}:{}", H::ID, hex));
212 assert_eq!(format!("{:?}", H::EMPTY), format!("{}:{}", H::ID, hex));
213
214 assert_eq!(H::from_hex(&hex), Ok(H::EMPTY));
215
216 assert_eq!(H::from_hex("lol"), Err(FromHexError::OddLength));
217 assert_eq!(H::from_hex("lolo"), Err(FromHexError::InvalidStringLength));
218 assert_eq!(
219 H::from_hex([b'l'].repeat(hexlen)),
220 Err(FromHexError::InvalidHexCharacter { c: 'l', index: 0 })
221 );
222
223 assert_eq!(H::from_object_dir_and_basename(0, &hex[2..]), Ok(H::EMPTY));
224
225 assert_eq!(H::from_object_dir_and_basename(0, &hex[2..]), Ok(H::EMPTY));
226
227 assert_eq!(
228 H::from_object_dir_and_basename(0, "lol"),
229 Err(FromHexError::InvalidStringLength)
230 );
231
232 assert_eq!(
233 H::from_object_dir_and_basename(0, [b'l'].repeat(hexlen - 2)),
234 Err(FromHexError::InvalidHexCharacter { c: 'l', index: 0 })
235 );
236
237 assert_eq!(
238 H::from_object_pathname(format!("{}/{}", &hex[0..2], &hex[2..])),
239 Ok(H::EMPTY)
240 );
241
242 assert_eq!(
243 H::from_object_pathname(format!("../this/is/ignored/{}/{}", &hex[0..2], &hex[2..])),
244 Ok(H::EMPTY)
245 );
246
247 assert_eq!(
248 H::from_object_pathname(&hex),
249 Err(FromHexError::InvalidStringLength)
250 );
251
252 assert_eq!(
253 H::from_object_pathname("lol"),
254 Err(FromHexError::InvalidStringLength)
255 );
256
257 assert_eq!(
258 H::from_object_pathname([b'l'].repeat(hexlen + 1)),
259 Err(FromHexError::InvalidHexCharacter { c: 'l', index: 0 })
260 );
261
262 assert_eq!(
263 H::from_object_pathname(format!("{}0{}", &hex[0..2], &hex[2..])),
264 Err(FromHexError::InvalidHexCharacter { c: '0', index: 2 })
265 );
266 }
267
268 #[test]
269 fn test_sha256hashvalue() {
270 test_fsverity_hash::<Sha256HashValue>();
271 }
272
273 #[test]
274 fn test_sha512hashvalue() {
275 test_fsverity_hash::<Sha512HashValue>();
276 }
277}