use sha2::{Digest as _, Sha256};
use std::{error::Error, fmt, path::Path};
pub const CANONICAL_V1_NAMESPACE: &str = "diagprint.canonical/v1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CanonicalizationVersion {
V1,
}
impl CanonicalizationVersion {
pub const fn as_str(self) -> &'static str {
match self {
Self::V1 => CANONICAL_V1_NAMESPACE,
}
}
}
impl fmt::Display for CanonicalizationVersion {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CanonicalizationError {
NonUtf8Path { display: String },
}
impl fmt::Display for CanonicalizationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonUtf8Path { display } => write!(
formatter,
"diagprint.canonical/v1 cannot encode non-UTF-8 remediation path: {display}"
),
}
}
}
impl Error for CanonicalizationError {}
pub(crate) fn canonical_path(path: &Path) -> Result<&str, CanonicalizationError> {
path.to_str()
.ok_or_else(|| CanonicalizationError::NonUtf8Path {
display: path.display().to_string(),
})
}
pub(crate) struct CanonicalHasher {
hasher: Sha256,
}
impl CanonicalHasher {
pub(crate) fn new(domain: &str) -> Self {
let mut value = Self {
hasher: Sha256::new(),
};
value.token(CANONICAL_V1_NAMESPACE.as_bytes());
value.token(domain.as_bytes());
value
}
pub(crate) fn finish(self) -> [u8; 32] {
let digest = self.hasher.finalize();
let mut bytes = [0_u8; 32];
bytes.copy_from_slice(&digest);
bytes
}
pub(crate) fn field(&mut self, name: &str) {
self.token(name.as_bytes());
}
pub(crate) fn string(&mut self, name: &str, value: &str) {
self.field(name);
self.token(value.as_bytes());
}
pub(crate) fn bytes(&mut self, name: &str, value: &[u8]) {
self.field(name);
self.token(value);
}
pub(crate) fn bool(&mut self, name: &str, value: bool) {
self.field(name);
self.raw(&[u8::from(value)]);
}
pub(crate) fn u32(&mut self, name: &str, value: u32) {
self.field(name);
self.raw(&value.to_be_bytes());
}
pub(crate) fn u64(&mut self, name: &str, value: u64) {
self.field(name);
self.raw(&value.to_be_bytes());
}
pub(crate) fn u128(&mut self, name: &str, value: u128) {
self.field(name);
self.raw(&value.to_be_bytes());
}
pub(crate) fn i64(&mut self, name: &str, value: i64) {
self.field(name);
self.raw(&value.to_be_bytes());
}
pub(crate) fn i128(&mut self, name: &str, value: i128) {
self.field(name);
self.raw(&value.to_be_bytes());
}
pub(crate) fn f64(&mut self, name: &str, value: f64) {
self.field(name);
self.raw(&canonical_f64_bits(value).to_be_bytes());
}
pub(crate) fn option_none(&mut self, name: &str) {
self.field(name);
self.raw(&[0]);
}
pub(crate) fn option_some(&mut self, name: &str) {
self.field(name);
self.raw(&[1]);
}
pub(crate) fn sequence(&mut self, name: &str, len: usize) {
self.field(name);
self.raw(&(len as u128).to_be_bytes());
}
fn token(&mut self, value: &[u8]) {
self.raw(&(value.len() as u128).to_be_bytes());
self.raw(value);
}
fn raw(&mut self, value: &[u8]) {
self.hasher.update(value);
}
}
pub(crate) fn canonical_f64_bits(value: f64) -> u64 {
if value.is_nan() {
0x7ff8_0000_0000_0000
} else if value == 0.0 {
0
} else {
value.to_bits()
}
}