summaryrefslogtreecommitdiffstats
path: root/src/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/util.rs')
-rw-r--r--src/util.rs165
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(())
+}