1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256, Sha512};
use std::{fmt::Display, fs::File, io, path::Path};

#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub enum Checksum {
    Sha256(Vec<u8>),
    Sha512(Vec<u8>),
}

#[derive(Clone)]
pub enum ChecksumValidator {
    Sha256((Vec<u8>, Sha256)),
    Sha512((Vec<u8>, Sha512)),
}

impl ChecksumValidator {
    pub fn update(&mut self, data: impl AsRef<[u8]>) {
        match self {
            ChecksumValidator::Sha256((_, v)) => v.update(data),
            ChecksumValidator::Sha512((_, v)) => v.update(data),
        }
    }

    pub fn finish(&self) -> bool {
        match self {
            ChecksumValidator::Sha256((c, v)) => c == &v.clone().finalize().to_vec(),
            ChecksumValidator::Sha512((c, v)) => c == &v.clone().finalize().to_vec(),
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ChecksumError {
    #[error("Failed to open {0} for checking checksum")]
    FailedToOpenFile(String),
    #[error("Can not checksum: {0}")]
    ChecksumIOError(String),
    #[error("Sha256 bad length")]
    BadLength,
    #[error(transparent)]
    HexError(#[from] hex::FromHexError),
}

pub type Result<T> = std::result::Result<T, ChecksumError>;

impl Checksum {
    pub fn from_file_sha256(path: &Path) -> Result<Self> {
        let mut file = File::open(path)
            .map_err(|_| ChecksumError::FailedToOpenFile(path.display().to_string()))?;

        let mut hasher = Sha256::new();
        io::copy(&mut file, &mut hasher)
            .map_err(|e| ChecksumError::ChecksumIOError(e.to_string()))?;
        let hash = hasher.finalize().to_vec();

        Ok(Self::Sha256(hash))
    }

    /// This function does not do input sanitization, so do checks before!
    pub fn from_sha256_str(s: &str) -> Result<Self> {
        if s.len() != 64 {
            return Err(ChecksumError::BadLength);
        }
        Ok(Checksum::Sha256(hex::decode(s)?))
    }

    pub fn get_validator(&self) -> ChecksumValidator {
        match self {
            Checksum::Sha256(c) => ChecksumValidator::Sha256((c.clone(), Sha256::new())),
            Checksum::Sha512(c) => ChecksumValidator::Sha512((c.clone(), Sha512::new())),
        }
    }

    pub fn cmp_read(&self, mut r: Box<dyn std::io::Read>) -> Result<bool> {
        match self {
            Checksum::Sha256(hex) => {
                let mut hasher = Sha256::new();
                io::copy(&mut r, &mut hasher)
                    .map_err(|e| ChecksumError::ChecksumIOError(e.to_string()))?;
                let hash = hasher.finalize().to_vec();
                Ok(hex == &hash)
            }
            Checksum::Sha512(hex) => {
                let mut hasher = Sha512::new();
                io::copy(&mut r, &mut hasher)
                    .map_err(|e| ChecksumError::ChecksumIOError(e.to_string()))?;
                let hash = hasher.finalize().to_vec();
                Ok(hex == &hash)
            }
        }
    }

    pub fn cmp_file(&self, path: &Path) -> Result<bool> {
        let file = File::open(path)
            .map_err(|_| ChecksumError::FailedToOpenFile(path.display().to_string()))?;

        self.cmp_read(Box::new(file) as Box<dyn std::io::Read>)
    }
}

impl Display for Checksum {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Checksum::Sha256(hex) => {
                f.write_str("sha256::")?;
                f.write_str(&hex::encode(hex))
            }
            Checksum::Sha512(hex) => {
                f.write_str("sha512::")?;
                f.write_str(&hex::encode(hex))
            }
        }
    }
}