use std::path::Path;
use sha2::{Digest, Sha256};
pub fn cache_key(resolved_url: &str) -> String {
hex_lower(&Sha256::digest(resolved_url.as_bytes()))
}
pub fn cache_key_range(resolved_url: &str, start: u64, end: u64) -> String {
cache_key(&format!("{resolved_url}#bytes={start}-{end}"))
}
pub fn sha256_hex(data: &[u8]) -> String {
hex_lower(&Sha256::digest(data))
}
pub fn sha256_file(path: &Path) -> std::io::Result<String> {
let mut file = std::fs::File::open(path)?;
let mut hasher = Sha256::new();
std::io::copy(&mut file, &mut hasher)?;
Ok(hex_lower(&hasher.finalize()))
}
pub(crate) struct Sha256Writer<W> {
inner: W,
hasher: Sha256,
}
impl<W: std::io::Write> Sha256Writer<W> {
pub(crate) fn new(inner: W) -> Self {
Self {
inner,
hasher: Sha256::new(),
}
}
pub(crate) fn finalize(self) -> String {
hex_lower(&self.hasher.finalize())
}
}
impl<W: std::io::Write> std::io::Write for Sha256Writer<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = self.inner.write(buf)?;
self.hasher.update(&buf[..n]);
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
fn hex_lower(bytes: &[u8]) -> String {
use std::fmt::Write as _;
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
let _ = write!(s, "{b:02x}");
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn era5_key_matches_corpus() {
assert_eq!(
cache_key("https://data.earthsci.dev/era5/2018/11/20181108.nc"),
"11cdcec111409f586e6afc432e1a6da47e6f97ccf3715e5db8554632b00671c1"
);
}
#[test]
fn openaq_key_matches_corpus() {
assert_eq!(
cache_key(
"https://openaq-data-archive.s3.amazonaws.com/records/openaq/locationid=1/2018-11-08.csv"
),
"69dd26b950e43cb2182e3b4d02e89e09bfb798b13469183ca2dad15c5794379a"
);
}
#[test]
fn key_is_exact_bytes_no_newline() {
assert_ne!(cache_key("https://x/y"), cache_key("https://x/y\n"));
}
#[test]
fn range_key_differs_from_whole() {
let url = "https://x/y.nc";
assert_ne!(cache_key(url), cache_key_range(url, 0, 1023));
assert_eq!(
cache_key_range(url, 0, 1023),
cache_key("https://x/y.nc#bytes=0-1023")
);
}
#[test]
fn sha256_writer_matches_whole_buffer_digest() {
use std::io::Write as _;
let mut w = Sha256Writer::new(Vec::new());
w.write_all(b"hello-").unwrap();
w.write_all(b"bytes").unwrap();
assert_eq!(w.finalize(), sha256_hex(b"hello-bytes"));
}
#[test]
fn sha256_hex_known_vector() {
assert_eq!(
sha256_hex(b""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
}