haggis-rs/src/checksum.rs

166 lines
5.1 KiB
Rust

use {
crate::Error,
std::{
io::{Read, Write},
str::FromStr,
},
};
/// A hashing algorithm used for checking file integrity
#[derive(Clone, Copy, Debug)]
pub enum Algorithm {
/// The [md5](https://www.ietf.org/rfc/rfc1321.txt) hash is
/// a widely deployed fast hash included for completeness.
/// As md5 is not cryptographically secure and collisions have
/// been proven to occur, the newer sha\[x\] algorithms are to
/// be preferred
Md5,
/// The [sha1](https://datatracker.ietf.org/doc/html/rfc3174)
/// hash is competitive in speed to md5 while having significantly
/// lower possibility of hash collision. Use this hash method if
/// you want goot file integrity checking coupled with low overhead.
Sha1,
/// The [sha256](https://tools.ietf.org/html/rfc6234) hash is
/// a cryptographically secure hash that is stronger than both
/// md5 and sha1. Use this hash when the importance of file
/// integrity far outweighs performance considerations.
Sha256,
/// Do not use file integrity checks
Skip,
}
impl FromStr for Algorithm {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"md5" | "Md5" => Ok(Self::Md5),
"sha1" | "Sha1" => Ok(Self::Sha1),
"sha256" | "Sha256" => Ok(Self::Sha256),
"skip" | "Skip" => Ok(Self::Skip),
_ => Err(Error::InvalidAlgorithm),
}
}
}
/// Optional checksumming for individual files
#[derive(Debug)]
pub enum Checksum {
/// Md5 checksumming, fastest, but collisions have occurred. Not recommended
/// for production use.
Md5([u8; 16]),
/// Sha1 checksumming. On par with md5.
Sha1([u8; 20]),
/// The most thorough checksumming offered. Use this if data integrity is of
/// utmost importance and computational overhead is of lesser importance.
Sha256([u8; 32]),
/// Do not use checksumming. If this option is selected it is recommended that
/// the entire archive is downloaded in entirety and checksummed to ensure it's
/// integrity, as was the pattern with Unix 'tar' archives and 'iso' images
Skip,
}
impl Checksum {
/// Reads a `Checksum` item into memory from a byte stream
/// # Errors
/// Returns an `Error` if io fails or the archive is incorrectly formatted
pub(crate) fn read<T: Read>(reader: &mut T) -> Result<Self, Error> {
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
match buf[0] {
0 => {
let mut sum = [0; 16];
reader.read_exact(&mut sum)?;
Ok(Self::Md5(sum))
}
1 => {
let mut sum = [0; 20];
reader.read_exact(&mut sum)?;
Ok(Self::Sha1(sum))
}
2 => {
let mut sum = [0; 32];
reader.read_exact(&mut sum)?;
Ok(Self::Sha256(sum))
}
_ => Ok(Self::Skip),
}
}
/// Writes a `Checksum` into a byte stream
/// # Errors
/// Returns `Error` if io fails
pub(crate) fn write<T: Write>(&self, writer: &mut T) -> Result<(), Error> {
match self {
Self::Md5(sum) => {
writer.write_all(&[0])?;
writer.write_all(&sum[..])?;
}
Self::Sha1(sum) => {
writer.write_all(&[1])?;
writer.write_all(&sum[..])?;
}
Self::Sha256(sum) => {
writer.write_all(&[2])?;
writer.write_all(&sum[..])?;
}
Self::Skip => writer.write_all(&[3])?,
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
const MD5BUF: [u8; 16] = [
102, 95, 170, 95, 1, 29, 157, 44, 184, 244, 181, 209, 47, 30, 223, 21,
];
const SHA1BUF: [u8; 20] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
];
#[test]
fn load_store_md5() {
{
let mut fd = File::options()
.create(true)
.truncate(true)
.write(true)
.open("test/md5")
.unwrap();
let cksum = Checksum::Md5(MD5BUF);
cksum.write(&mut fd).unwrap();
}
let mut fd = File::open("test/md5").unwrap();
let cksum = Checksum::read(&mut fd).unwrap();
match cksum {
Checksum::Md5(sum) => assert_eq!(sum, MD5BUF),
_ => unreachable!(),
}
}
#[test]
fn load_store_sha1() {
{
let mut fd = File::options()
.create(true)
.truncate(true)
.write(true)
.open("test/sha1")
.unwrap();
let cksum = Checksum::Sha1(SHA1BUF);
cksum.write(&mut fd).unwrap();
}
let mut fd = File::open("test/sha1").unwrap();
let cksum = Checksum::read(&mut fd).unwrap();
match cksum {
Checksum::Sha1(sum) => assert_eq!(sum, SHA1BUF),
_ => unreachable!(),
}
}
}