async_fetcher/
checksum.rs

1// Copyright 2021 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use digest::{generic_array::GenericArray, Digest, OutputSizeUser};
5use hex::FromHex;
6use md5::Md5;
7use serde::Deserialize;
8use sha2::Sha256;
9use std::{convert::TryFrom, io};
10
11/// A checksum of a `Source` as a fixed-sized byte array.
12#[derive(Debug, Clone)]
13pub enum Checksum {
14    Md5(GenericArray<u8, <Md5 as OutputSizeUser>::OutputSize>),
15    Sha256(GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>),
16}
17
18/// An error that can occur from a failed checksum validation.
19#[derive(Debug, Error)]
20pub enum ChecksumError {
21    #[error("expected {}, found {}", _0, _1)]
22    Invalid(String, String),
23    #[error("I/O error encountered while reading from reader")]
24    IO(#[from] io::Error),
25}
26
27/// The `&str` representation of a `Checksum`.
28pub enum SumStr<'a> {
29    Md5(&'a str),
30    Sha256(&'a str),
31}
32
33/// The `String` representation of a `Checksum`.
34#[derive(Deserialize)]
35pub enum SumStrBuf {
36    Md5(String),
37    Sha256(String),
38}
39
40impl SumStrBuf {
41    pub fn as_ref(&'_ self) -> SumStr<'_> {
42        match self {
43            SumStrBuf::Md5(string) => SumStr::Md5(string.as_str()),
44            SumStrBuf::Sha256(string) => SumStr::Sha256(string.as_str()),
45        }
46    }
47}
48
49impl<'a> TryFrom<SumStr<'a>> for Checksum {
50    type Error = hex::FromHexError;
51
52    fn try_from(input: SumStr) -> Result<Self, Self::Error> {
53        match input {
54            SumStr::Md5(sum) => <[u8; 16]>::from_hex(sum)
55                .map(GenericArray::from)
56                .map(Checksum::Md5),
57            SumStr::Sha256(sum) => <[u8; 32]>::from_hex(sum)
58                .map(GenericArray::from)
59                .map(Checksum::Sha256),
60        }
61    }
62}
63
64impl Checksum {
65    pub fn validate<F: std::io::Read>(
66        &self,
67        reader: F,
68        buffer: &mut [u8],
69    ) -> Result<(), ChecksumError> {
70        match self {
71            Checksum::Md5(sum) => checksum::<Md5, F>(reader, buffer, sum),
72            Checksum::Sha256(sum) => checksum::<Sha256, F>(reader, buffer, sum),
73        }
74    }
75}
76
77pub(crate) fn checksum<D: Digest, F: io::Read>(
78    reader: F,
79    buffer: &mut [u8],
80    expected: &GenericArray<u8, D::OutputSize>,
81) -> Result<(), ChecksumError> {
82    let result = generate_checksum::<D, F>(reader, buffer).map_err(ChecksumError::IO)?;
83
84    if result == *expected {
85        Ok(())
86    } else {
87        Err(ChecksumError::Invalid(
88            hex::encode(expected),
89            hex::encode(result),
90        ))
91    }
92}
93
94pub(crate) fn generate_checksum<D: Digest, F: io::Read>(
95    mut reader: F,
96    buffer: &mut [u8],
97) -> io::Result<GenericArray<u8, D::OutputSize>> {
98    let mut hasher = D::new();
99    let mut read;
100
101    loop {
102        read = reader.read(buffer)?;
103
104        if read == 0 {
105            return Ok(hasher.finalize());
106        }
107
108        hasher.update(&buffer[..read]);
109    }
110}