use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use sha2::{Digest, Sha256};
const DIGEST_PREFIX: &str = "sha-256=:";
const DIGEST_SUFFIX: &str = ":";
#[must_use]
pub fn sha256_digest(bytes: &[u8]) -> String {
format_digest(&Sha256::digest(bytes))
}
pub(crate) fn format_digest(d: &[u8]) -> String {
format!("{DIGEST_PREFIX}{}{DIGEST_SUFFIX}", STANDARD.encode(d))
}
#[must_use]
pub fn is_digest(s: &str) -> bool {
let Some(b64) = s
.strip_prefix(DIGEST_PREFIX)
.and_then(|r| r.strip_suffix(DIGEST_SUFFIX))
else {
return false;
};
let mut buf = [0u8; 33];
STANDARD.decode_slice(b64, &mut buf).is_ok_and(|n| n == 32)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub(crate) const VECTORS: &str = include_str!("../testdata/digests.json");
#[test]
fn sha256_digest_matches_shared_body_vectors() {
let v: serde_json::Value = serde_json::from_str(VECTORS).unwrap();
let cases = v["body"].as_array().unwrap();
assert!(!cases.is_empty());
for case in cases {
let name = case["name"].as_str().unwrap();
let body = case["body"].as_str().unwrap();
let want = case["digest"].as_str().unwrap();
assert_eq!(sha256_digest(body.as_bytes()), want, "{name}");
assert!(is_digest(want), "{name}");
}
}
}