use std::fmt;
use crate::algorithm::Algorithm;
use crate::checker::IntegrityChecker;
use crate::errors::Error;
use crate::hash::Hash;
use crate::opts::IntegrityOpts;
use base64::Engine as _;
#[cfg(feature = "serde")]
use serde::de::{self, Deserialize, Deserializer, Visitor};
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Integrity {
pub hashes: Vec<Hash>,
}
impl fmt::Display for Integrity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.hashes
.iter()
.map(|h| h.to_string())
.collect::<Vec<String>>()
.join(" ")
)
}
}
impl std::str::FromStr for Integrity {
type Err = Error;
fn from_str(s: &str) -> Result<Integrity, Self::Err> {
let mut hashes = String::from(s)
.split_whitespace()
.map(|x| x.parse())
.collect::<Result<Vec<Hash>, Self::Err>>()?;
hashes.sort();
Ok(Integrity { hashes })
}
}
#[cfg(feature = "serde")]
impl Serialize for Integrity {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Integrity {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct IntegrityVisitor;
impl<'de> Visitor<'de> for IntegrityVisitor {
type Value = Integrity;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an Integrity object as a string")
}
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
v.parse::<Integrity>().map_err(de::Error::custom)
}
}
deserializer.deserialize_str(IntegrityVisitor)
}
}
impl Integrity {
pub fn pick_algorithm(&self) -> Algorithm {
self.hashes[0].algorithm
}
pub fn from<B: AsRef<[u8]>>(data: B) -> Integrity {
IntegrityOpts::new()
.algorithm(Algorithm::Sha256)
.chain(&data)
.result()
}
pub fn concat(&self, other: Integrity) -> Self {
let mut hashes = [self.hashes.clone(), other.hashes].concat();
hashes.sort();
hashes.dedup();
Integrity { hashes }
}
pub fn check<B: AsRef<[u8]>>(&self, data: B) -> Result<Algorithm, Error> {
let mut checker = IntegrityChecker::new(self.clone());
checker.input(&data);
checker.result()
}
pub fn to_hex(&self) -> (Algorithm, String) {
let hash = self.hashes.get(0).unwrap();
(
hash.algorithm,
hex::encode(
base64::prelude::BASE64_STANDARD
.decode(&hash.digest)
.unwrap(),
),
)
}
pub fn matches(&self, other: &Self) -> Option<Algorithm> {
let algo = other.pick_algorithm();
self.hashes
.iter()
.filter(|h| h.algorithm == algo)
.find(|&h| {
other
.hashes
.iter()
.filter(|i| i.algorithm == algo)
.any(|i| h == i)
})
.map(|h| h.algorithm)
}
}
#[cfg(test)]
mod tests {
use super::{Algorithm, Hash, Integrity, IntegrityOpts};
#[test]
fn parse() {
let sri: Integrity = "sha1-deadbeef=".parse().unwrap();
assert_eq!(
sri.hashes.get(0).unwrap(),
&Hash {
algorithm: Algorithm::Sha1,
digest: String::from("deadbeef=")
}
)
}
#[test]
fn to_hex() {
let sri = Integrity::from(b"hello world");
assert_eq!(
sri.to_hex(),
(
Algorithm::Sha256,
String::from("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
)
)
}
#[test]
fn matches() {
let sri1 = IntegrityOpts::new()
.algorithm(Algorithm::Sha512)
.algorithm(Algorithm::Sha256)
.chain(b"hello world")
.result();
let sri2 = Integrity::from(b"hello world");
let sri3 = Integrity::from(b"goodbye world");
assert_eq!(sri1.matches(&sri2), Some(Algorithm::Sha256));
assert_eq!(sri1.matches(&sri3), None);
assert_eq!(sri2.matches(&sri1), None)
}
#[test]
fn de_json() {
use serde_derive::Deserialize;
#[derive(Debug, PartialEq, Deserialize)]
struct Thing {
integrity: Integrity,
}
let json = r#"{ "integrity": "sha1-deadbeef" }"#;
let de: Thing = serde_json::from_str(json).unwrap();
assert_eq!(
de,
Thing {
integrity: "sha1-deadbeef".parse().unwrap()
}
);
}
#[test]
fn ser_json() {
use serde_derive::Serialize;
#[derive(Debug, PartialEq, Serialize)]
struct Thing {
integrity: Integrity,
}
let thing = Thing {
integrity: "sha1-deadbeef".parse().unwrap(),
};
let ser = serde_json::to_string(&thing).unwrap();
let json = r#"{"integrity":"sha1-deadbeef"}"#;
assert_eq!(ser, json);
}
}