| //! Package checksums (i.e. SHA-256 digests) |
| |
| use crate::{Error, Result}; |
| use serde::{de, ser, Deserialize, Serialize}; |
| use std::{fmt, str::FromStr}; |
| |
| /// Cryptographic checksum (SHA-256) for a package |
| #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] |
| pub enum Checksum { |
| /// SHA-256 digest of a package |
| Sha256([u8; 32]), |
| } |
| |
| impl Checksum { |
| /// Is this checksum SHA-256? |
| pub fn is_sha256(&self) -> bool { |
| self.as_sha256().is_some() |
| } |
| |
| /// If this is a SHA-256 checksum, get the raw bytes |
| pub fn as_sha256(&self) -> Option<[u8; 32]> { |
| match self { |
| Checksum::Sha256(digest) => Some(*digest), |
| } |
| } |
| } |
| |
| impl From<[u8; 32]> for Checksum { |
| fn from(bytes: [u8; 32]) -> Checksum { |
| Checksum::Sha256(bytes) |
| } |
| } |
| |
| impl FromStr for Checksum { |
| type Err = Error; |
| |
| fn from_str(s: &str) -> Result<Self> { |
| if s.len() != 64 { |
| return Err(Error::Parse(format!( |
| "invalid checksum: expected 64 hex chars, got {}", |
| s.len() |
| ))); |
| } |
| |
| let mut digest = [0u8; 32]; |
| |
| for (i, byte) in digest.iter_mut().enumerate() { |
| *byte = u8::from_str_radix(&s[(i * 2)..=(i * 2) + 1], 16)?; |
| } |
| |
| Ok(Checksum::Sha256(digest)) |
| } |
| } |
| |
| impl fmt::Debug for Checksum { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| Checksum::Sha256(_) => write!(f, "Sha256({:x})", self), |
| } |
| } |
| } |
| |
| impl fmt::Display for Checksum { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{:x}", self) |
| } |
| } |
| |
| impl fmt::LowerHex for Checksum { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| Checksum::Sha256(digest) => { |
| for b in digest { |
| write!(f, "{:02x}", b)?; |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| impl fmt::UpperHex for Checksum { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| Checksum::Sha256(digest) => { |
| for b in digest { |
| write!(f, "{:02X}", b)?; |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| impl<'de> Deserialize<'de> for Checksum { |
| fn deserialize<D: de::Deserializer<'de>>( |
| deserializer: D, |
| ) -> std::result::Result<Self, D::Error> { |
| let hex = String::deserialize(deserializer)?; |
| hex.parse().map_err(de::Error::custom) |
| } |
| } |
| |
| impl Serialize for Checksum { |
| fn serialize<S: ser::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> { |
| self.to_string().serialize(serializer) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::{Checksum, Error}; |
| |
| #[test] |
| fn checksum_round_trip() { |
| let checksum_str = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"; |
| let checksum = checksum_str.parse::<Checksum>().unwrap(); |
| assert_eq!(checksum_str, checksum.to_string()); |
| } |
| |
| #[test] |
| fn invalid_checksum() { |
| // Missing one hex letter |
| let invalid_str = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f"; |
| let error = invalid_str.parse::<Checksum>().err().unwrap(); |
| assert!(matches!(error, Error::Parse(_))); |
| } |
| } |