auths_transparency/
types.rs1use std::fmt;
2
3use base64::{Engine, engine::general_purpose::STANDARD};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use sha2::{Digest, Sha256};
6
7use crate::error::TransparencyError;
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash)]
20pub struct MerkleHash([u8; 32]);
21
22impl MerkleHash {
23 pub const EMPTY: Self = Self([0u8; 32]);
25
26 pub fn from_bytes(bytes: [u8; 32]) -> Self {
28 Self(bytes)
29 }
30
31 pub fn from_hex(s: &str) -> Result<Self, TransparencyError> {
33 let bytes = hex::decode(s).map_err(|e| TransparencyError::InvalidProof(e.to_string()))?;
34 let arr: [u8; 32] = bytes
35 .try_into()
36 .map_err(|_| TransparencyError::InvalidProof("hash must be 32 bytes".into()))?;
37 Ok(Self(arr))
38 }
39
40 pub fn as_bytes(&self) -> &[u8; 32] {
42 &self.0
43 }
44
45 pub fn to_base64(&self) -> String {
47 STANDARD.encode(self.0)
48 }
49
50 pub fn from_base64(s: &str) -> Result<Self, TransparencyError> {
52 let bytes = STANDARD
53 .decode(s)
54 .map_err(|e| TransparencyError::InvalidProof(format!("base64 decode: {e}")))?;
55 let arr: [u8; 32] = bytes
56 .try_into()
57 .map_err(|_| TransparencyError::InvalidProof("hash must be 32 bytes".into()))?;
58 Ok(Self(arr))
59 }
60
61 pub fn sha256(data: &[u8]) -> Self {
63 let digest = Sha256::digest(data);
64 let mut out = [0u8; 32];
65 out.copy_from_slice(&digest);
66 Self(out)
67 }
68}
69
70impl fmt::Debug for MerkleHash {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "MerkleHash({})", self)
73 }
74}
75
76impl fmt::Display for MerkleHash {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 for b in &self.0 {
79 write!(f, "{b:02x}")?;
80 }
81 Ok(())
82 }
83}
84
85impl Serialize for MerkleHash {
86 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
87 serializer.serialize_str(&self.to_string())
88 }
89}
90
91impl<'de> Deserialize<'de> for MerkleHash {
92 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
93 let s = String::deserialize(deserializer)?;
94 Self::from_hex(&s).map_err(serde::de::Error::custom)
95 }
96}
97
98impl AsRef<[u8]> for MerkleHash {
99 fn as_ref(&self) -> &[u8] {
100 &self.0
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
116#[serde(try_from = "String", into = "String")]
117pub struct LogOrigin(String);
118
119impl LogOrigin {
120 pub fn new(s: &str) -> Result<Self, TransparencyError> {
122 if s.is_empty() {
123 return Err(TransparencyError::InvalidOrigin("must not be empty".into()));
124 }
125 if !s.is_ascii() {
126 return Err(TransparencyError::InvalidOrigin("must be ASCII".into()));
127 }
128 if s.bytes().any(|b| b < 0x20) {
129 return Err(TransparencyError::InvalidOrigin(
130 "must not contain control characters".into(),
131 ));
132 }
133 Ok(Self(s.to_string()))
134 }
135
136 #[allow(clippy::expect_used)] pub fn new_unchecked(s: &str) -> Self {
142 Self::new(s).expect("LogOrigin::new_unchecked called with invalid origin")
143 }
144
145 pub fn as_str(&self) -> &str {
147 &self.0
148 }
149}
150
151impl fmt::Display for LogOrigin {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 f.write_str(&self.0)
154 }
155}
156
157impl TryFrom<String> for LogOrigin {
158 type Error = TransparencyError;
159 fn try_from(s: String) -> Result<Self, Self::Error> {
160 Self::new(&s)
161 }
162}
163
164impl From<LogOrigin> for String {
165 fn from(o: LogOrigin) -> Self {
166 o.0
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn merkle_hash_hex_roundtrip() {
176 let bytes = [0xabu8; 32];
177 let h = MerkleHash::from_bytes(bytes);
178 let hex_str = h.to_string();
179 let h2 = MerkleHash::from_hex(&hex_str).unwrap();
180 assert_eq!(h, h2);
181 }
182
183 #[test]
184 fn merkle_hash_json_roundtrip() {
185 let h = MerkleHash::from_bytes([0x42u8; 32]);
186 let json = serde_json::to_string(&h).unwrap();
187 let h2: MerkleHash = serde_json::from_str(&json).unwrap();
188 assert_eq!(h, h2);
189 }
190
191 #[test]
192 fn log_origin_rejects_empty() {
193 assert!(LogOrigin::new("").is_err());
194 }
195
196 #[test]
197 fn log_origin_rejects_non_ascii() {
198 assert!(LogOrigin::new("日本語").is_err());
199 }
200
201 #[test]
202 fn log_origin_rejects_control_chars() {
203 assert!(LogOrigin::new("auths\x00log").is_err());
204 }
205
206 #[test]
207 fn log_origin_valid() {
208 let o = LogOrigin::new("auths.dev/log").unwrap();
209 assert_eq!(o.as_str(), "auths.dev/log");
210 }
211}