diff options
Diffstat (limited to 'src/util.rs')
| -rw-r--r-- | src/util.rs | 165 |
1 files changed, 100 insertions, 65 deletions
diff --git a/src/util.rs b/src/util.rs index c6739b6..fe11c38 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,35 +1,30 @@ -// use std::fmt::{Debug, Display, Formatter}; -// use serde::{Deserialize, Deserializer}; -// use serde::de::{Error, Visitor}; -// use hex::{FromHex, FromHexError, ToHex}; -// use sha1_smol::Sha1; -// -// // sha1 digests are 20 bytes long -// pub use sha1_smol::DIGEST_LENGTH; -// pub type Sha1DigestBytes = [u8; DIGEST_LENGTH]; -// -// #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] -// pub struct Sha1Digest(pub Sha1DigestBytes); -// -// impl Debug for Sha1Digest { -// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { -// write!(f, "Sha1Digest {{{}}}", self.0.encode_hex::<String>()) -// } -// } -// -// impl Display for Sha1Digest { -// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { -// f.write_str(&self.0.encode_hex::<String>()) -// } -// } -// -// impl Sha1Digest { -// pub fn from_hex(s: &str) -> Result<Sha1Digest, FromHexError> { -// -// } -// - +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; +use log::debug; use sha1_smol::{Digest, Sha1}; +use tokio::fs::File; +use tokio::io::AsyncReadExt; + +#[derive(Debug)] +pub enum IntegrityError { + SizeMismatch{ expect: usize, actual: usize }, + Sha1Mismatch{ expect: Digest, actual: Digest } +} + +impl Display for IntegrityError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + IntegrityError::SizeMismatch{ expect, actual } => + write!(f, "size mismatch (expect {expect} bytes, got {actual} bytes)"), + IntegrityError::Sha1Mismatch {expect, actual} => + write!(f, "sha1 mismatch (expect {expect}, got {actual})") + } + } +} + +impl Error for IntegrityError {} pub fn verify_sha1(expect: Digest, s: &str) -> Result<(), Digest> { let dig = Sha1::from(s).digest(); @@ -41,37 +36,77 @@ pub fn verify_sha1(expect: Digest, s: &str) -> Result<(), Digest> { Err(dig) } -// -// pub fn as_hex(&self) -> String { -// // allocate the string with capacity first so we only do one heap alloc -// let mut s: String = String::with_capacity(2 * self.0.len()); -// self.0.iter().for_each(|b| s.push_str(&format!("{:02x}", b))); -// s -// } -// } -// -// struct Sha1DigestVisitor; -// -// impl <'a> Visitor<'a> for Sha1DigestVisitor { -// type Value = Sha1Digest; -// -// fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { -// write!(formatter, "a valid SHA-1 digest (40-character hex string)") -// } -// -// fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> -// where -// E: Error, -// { -// Sha1DigestBytes::from_hex(v).map_err(|e| E::custom(e)).map(Sha1Digest) -// } -// } -// -// impl<'a> Deserialize<'a> for Sha1Digest { -// fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> -// where -// D: Deserializer<'a>, -// { -// deserializer.deserialize_any(Sha1DigestVisitor) -// } -// }
\ No newline at end of file +#[derive(Debug)] +pub enum FileVerifyError { + Integrity(PathBuf, IntegrityError), + Open(PathBuf, tokio::io::Error), + Read(PathBuf, tokio::io::Error), +} + +impl Display for FileVerifyError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FileVerifyError::Integrity(path, e) => write!(f, "file integrity error {}: {}", path.display(), e), + FileVerifyError::Open(path, e) => write!(f, "error opening file {}: {}", path.display(), e), + FileVerifyError::Read(path, e) => write!(f, "error reading file {}: {}", path.display(), e) + } + } +} + +impl Error for FileVerifyError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + FileVerifyError::Integrity(_, e) => Some(e), + FileVerifyError::Open(_, e) => Some(e), + FileVerifyError::Read(_, e) => Some(e) + } + } +} + +pub async fn verify_file(path: impl AsRef<Path>, expect_size: Option<usize>, expect_sha1: Option<Digest>) -> Result<(), FileVerifyError> { + let path = path.as_ref(); + + if expect_size.is_none() && expect_sha1.is_none() { + debug!("No size or sha1 for {}, have to assume it's good.", path.display()); + return Ok(()); + } + + let mut file = File::open(path).await.map_err(|e| FileVerifyError::Open(path.to_owned(), e))?; + + let mut tally = 0usize; + let mut st = Sha1::new(); + let mut buf = [0u8; 4096]; + + loop { + let n = match file.read(&mut buf).await { + Ok(n) => n, + Err(e) => match e.kind() { + ErrorKind::Interrupted => continue, + _ => return Err(FileVerifyError::Read(path.to_owned(), e)) + } + }; + + if n == 0 { + break; + } + + st.update(&buf[..n]); + tally += n; + } + + let dig = st.digest(); + + if expect_size.is_some_and(|sz| sz != tally) { + return Err(FileVerifyError::Integrity(path.to_owned(), IntegrityError::SizeMismatch { + expect: expect_size.unwrap(), + actual: tally + })); + } else if expect_sha1.is_some_and(|exp_dig| exp_dig != dig) { + return Err(FileVerifyError::Integrity(path.to_owned(), IntegrityError::Sha1Mismatch { + expect: expect_sha1.unwrap(), + actual: dig + })); + } + + Ok(()) +} |
