1use alloc::string::String;
2use core::fmt;
3
4use crate::constants;
5use crate::hash;
6
7#[derive(Debug)]
8pub enum DestinationError {
9 DotInAppName,
10 DotInAspect,
11}
12
13impl fmt::Display for DestinationError {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 match self {
16 DestinationError::DotInAppName => write!(f, "Dots can't be used in app names"),
17 DestinationError::DotInAspect => write!(f, "Dots can't be used in aspects"),
18 }
19 }
20}
21
22pub fn expand_name(
26 app_name: &str,
27 aspects: &[&str],
28 identity_hash: Option<&[u8; 16]>,
29) -> Result<String, DestinationError> {
30 if app_name.contains('.') {
31 return Err(DestinationError::DotInAppName);
32 }
33
34 let mut name = String::from(app_name);
35 for aspect in aspects {
36 if aspect.contains('.') {
37 return Err(DestinationError::DotInAspect);
38 }
39 name.push('.');
40 name.push_str(aspect);
41 }
42
43 if let Some(hash) = identity_hash {
44 name.push('.');
45 for b in hash {
46 use core::fmt::Write;
47 write!(name, "{:02x}", b).unwrap();
48 }
49 }
50
51 Ok(name)
52}
53
54pub fn name_hash(
58 app_name: &str,
59 aspects: &[&str],
60) -> [u8; constants::NAME_HASH_LENGTH / 8] {
61 hash::name_hash(app_name, aspects)
62}
63
64pub fn destination_hash(
70 app_name: &str,
71 aspects: &[&str],
72 identity_hash: Option<&[u8; 16]>,
73) -> [u8; constants::TRUNCATED_HASHLENGTH / 8] {
74 let nh = name_hash(app_name, aspects);
75
76 let mut addr_material = alloc::vec::Vec::new();
77 addr_material.extend_from_slice(&nh);
78 if let Some(ih) = identity_hash {
79 addr_material.extend_from_slice(ih);
80 }
81
82 hash::truncated_hash(&addr_material)
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_expand_name_basic() {
91 let name = expand_name("app", &["aspect"], None).unwrap();
92 assert_eq!(name, "app.aspect");
93 }
94
95 #[test]
96 fn test_expand_name_multiple_aspects() {
97 let name = expand_name("app", &["a", "b"], None).unwrap();
98 assert_eq!(name, "app.a.b");
99 }
100
101 #[test]
102 fn test_expand_name_with_identity() {
103 let hash = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
104 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10];
105 let name = expand_name("app", &["a", "b"], Some(&hash)).unwrap();
106 assert_eq!(name, "app.a.b.0102030405060708090a0b0c0d0e0f10");
107 }
108
109 #[test]
110 fn test_expand_name_dot_in_app_name() {
111 assert!(expand_name("app.bad", &["aspect"], None).is_err());
112 }
113
114 #[test]
115 fn test_expand_name_dot_in_aspect() {
116 assert!(expand_name("app", &["bad.aspect"], None).is_err());
117 }
118
119 #[test]
120 fn test_destination_hash_plain() {
121 let dh = destination_hash("app", &["aspect"], None);
123 assert_eq!(dh.len(), 16);
124
125 let dh2 = destination_hash("app", &["aspect"], None);
127 assert_eq!(dh, dh2);
128 }
129
130 #[test]
131 fn test_destination_hash_with_identity() {
132 let id_hash = [0x42; 16];
133 let dh = destination_hash("app", &["aspect"], Some(&id_hash));
134 assert_eq!(dh.len(), 16);
135
136 let id_hash2 = [0x43; 16];
138 let dh2 = destination_hash("app", &["aspect"], Some(&id_hash2));
139 assert_ne!(dh, dh2);
140 }
141
142 #[test]
143 fn test_destination_hash_computation() {
144 let nh = name_hash("app", &["aspect"]);
146 let id_hash = [0xAA; 16];
147
148 let mut material = alloc::vec::Vec::new();
149 material.extend_from_slice(&nh);
150 material.extend_from_slice(&id_hash);
151
152 let expected = crate::hash::truncated_hash(&material);
153 let actual = destination_hash("app", &["aspect"], Some(&id_hash));
154 assert_eq!(actual, expected);
155 }
156}