use alloc::vec::Vec;
use purecrypto::hash::{Digest, Sha1, Sha256};
use crate::error::Error;
use crate::object::ObjectType;
use crate::oid::{HashAlgo, ObjectId};
pub fn hash_object(algo: HashAlgo, ty: ObjectType, content: &[u8]) -> ObjectId {
match algo {
HashAlgo::Sha1 => digest_with::<Sha1>(algo, ty, content),
HashAlgo::Sha256 => digest_with::<Sha256>(algo, ty, content),
}
}
pub fn hash_loose_payload(algo: HashAlgo, ty: ObjectType, payload: &[u8]) -> ObjectId {
hash_object(algo, ty, payload)
}
pub fn hash_bytes(algo: HashAlgo, data: &[u8]) -> Vec<u8> {
match algo {
HashAlgo::Sha1 => Sha1::digest(data).as_ref().to_vec(),
HashAlgo::Sha256 => Sha256::digest(data).as_ref().to_vec(),
}
}
fn digest_with<D: Digest>(algo: HashAlgo, ty: ObjectType, content: &[u8]) -> ObjectId {
let mut d = D::new();
d.update(&loose_header(ty, content.len()));
d.update(content);
let out = d.finalize();
ObjectId::from_bytes(algo, out.as_ref()).expect("digest length matches algo")
}
pub(crate) fn loose_header(ty: ObjectType, len: usize) -> Vec<u8> {
let mut header = Vec::with_capacity(16);
header.extend_from_slice(ty.as_str().as_bytes());
header.push(b' ');
push_decimal(&mut header, len);
header.push(0);
header
}
fn push_decimal(buf: &mut Vec<u8>, mut n: usize) {
if n == 0 {
buf.push(b'0');
return;
}
let start = buf.len();
while n > 0 {
buf.push(b'0' + (n % 10) as u8);
n /= 10;
}
buf[start..].reverse();
}
pub fn verify(expected: &ObjectId, ty: ObjectType, content: &[u8]) -> Result<(), Error> {
let got = hash_object(expected.algo(), ty, content);
if &got == expected {
Ok(())
} else {
use alloc::format;
Err(Error::InvalidOid(format!(
"object hash mismatch: expected {expected}, computed {got}"
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_blob_sha1() {
let id = hash_object(HashAlgo::Sha1, ObjectType::Blob, b"");
assert_eq!(id.to_hex(), "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
}
#[test]
fn hello_blob_sha1() {
let id = hash_object(HashAlgo::Sha1, ObjectType::Blob, b"hello\n");
assert_eq!(id.to_hex(), "ce013625030ba8dba906f756967f9e9ca394464a");
}
#[test]
fn empty_blob_sha256() {
let id = hash_object(HashAlgo::Sha256, ObjectType::Blob, b"");
assert_eq!(
id.to_hex(),
"473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813"
);
}
#[test]
fn verify_roundtrip() {
let id = hash_object(HashAlgo::Sha1, ObjectType::Blob, b"hello\n");
assert!(verify(&id, ObjectType::Blob, b"hello\n").is_ok());
assert!(verify(&id, ObjectType::Blob, b"goodbye\n").is_err());
}
}