hashtree_core/
visibility.rs1use std::str::FromStr;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum TreeVisibility {
8 Public,
9 LinkVisible,
10 Private,
11}
12
13impl TreeVisibility {
14 pub fn as_str(&self) -> &'static str {
15 match self {
16 TreeVisibility::Public => "public",
17 TreeVisibility::LinkVisible => "link-visible",
18 TreeVisibility::Private => "private",
19 }
20 }
21}
22
23impl FromStr for TreeVisibility {
24 type Err = String;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 let normalized = s.trim().to_ascii_lowercase();
28 match normalized.as_str() {
29 "public" => Ok(TreeVisibility::Public),
30 "link-visible" | "link_visible" | "linkvisible" => Ok(TreeVisibility::LinkVisible),
31 "private" => Ok(TreeVisibility::Private),
32 _ => Err(format!("invalid visibility: {}", s)),
33 }
34 }
35}
36
37pub fn xor_keys(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
39 let mut out = [0u8; 32];
40 for i in 0..32 {
41 out[i] = a[i] ^ b[i];
42 }
43 out
44}
45
46#[derive(Clone, Copy, PartialEq, Eq, Hash)]
48pub enum LinkKeyBytes {
49 Legacy([u8; 32]),
50 Versioned([u8; 33]),
51}
52
53impl std::fmt::Debug for LinkKeyBytes {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.write_str("LinkKeyBytes(..)")
56 }
57}
58
59impl LinkKeyBytes {
60 pub fn from_hex(value: &str) -> Result<Self, crate::CryptoError> {
61 Self::from_bytes(&hex::decode(value).map_err(|_| crate::CryptoError::InvalidKeyLength)?)
62 }
63 pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::CryptoError> {
64 match bytes.len() {
65 32 => Ok(Self::Legacy(bytes.try_into().unwrap())),
66 33 => Ok(Self::Versioned(bytes.try_into().unwrap())),
67 _ => Err(crate::CryptoError::InvalidKeyLength),
68 }
69 }
70}
71
72impl AsRef<[u8]> for LinkKeyBytes {
73 fn as_ref(&self) -> &[u8] {
74 match self {
75 Self::Legacy(bytes) => bytes,
76 Self::Versioned(bytes) => bytes,
77 }
78 }
79}
80
81pub fn mask_object_key(
84 key: &crate::ObjectKey,
85 secret: &[u8],
86) -> Result<Vec<u8>, crate::CryptoError> {
87 match (key.mode(), secret.len()) {
88 (crate::EncryptionMode::ChkV1, 32) => xor_key_bytes(&key.raw_key(), secret),
89 (crate::EncryptionMode::LegacyChk, 33) => {
90 let mut encoded = [1u8; 33];
91 encoded[1..].copy_from_slice(key.as_bytes());
92 xor_key_bytes(&encoded, secret)
93 }
94 _ => xor_key_bytes(key.as_bytes(), secret),
95 }
96}
97
98pub fn xor_key_bytes(a: &[u8], b: &[u8]) -> Result<Vec<u8>, crate::crypto::CryptoError> {
101 if a.len() != b.len() || !matches!(a.len(), 32 | 33) {
102 return Err(crate::crypto::CryptoError::InvalidKeyLength);
103 }
104 Ok(a.iter().zip(b).map(|(a, b)| a ^ b).collect())
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_visibility_parse() {
113 assert_eq!(
114 TreeVisibility::from_str("public").unwrap(),
115 TreeVisibility::Public
116 );
117 assert_eq!(
118 TreeVisibility::from_str("link-visible").unwrap(),
119 TreeVisibility::LinkVisible
120 );
121 assert_eq!(
122 TreeVisibility::from_str("link_visible").unwrap(),
123 TreeVisibility::LinkVisible
124 );
125 assert_eq!(
126 TreeVisibility::from_str("private").unwrap(),
127 TreeVisibility::Private
128 );
129 assert!(TreeVisibility::from_str("unknown").is_err());
130 }
131
132 #[test]
133 fn test_xor_keys_roundtrip() {
134 let a = [0x11u8; 32];
135 let b = [0x22u8; 32];
136 let masked = xor_keys(&a, &b);
137 let unmasked = xor_keys(&masked, &b);
138 assert_eq!(unmasked, a);
139 }
140}