ndn_security/did/
encoding.rs1use ndn_packet::Name;
37use ndn_packet::name::NameComponent;
38
39use crate::did::resolver::DidError;
40
41const NAME_TLV_TYPE: u64 = 7;
43
44pub fn name_to_did(name: &Name) -> String {
59 let tlv = encode_name_tlv(name);
60 let encoded = base64_url_encode(&tlv);
61 format!("did:ndn:{encoded}")
62}
63
64pub fn did_to_name(did: &str) -> Result<Name, DidError> {
74 let rest = did
75 .strip_prefix("did:ndn:")
76 .ok_or_else(|| DidError::InvalidDid(did.to_string()))?;
77
78 if rest.contains(':') {
79 if let Some(encoded) = rest.strip_prefix("v1:") {
81 let bytes = base64_url_decode(encoded)
83 .map_err(|_| DidError::InvalidDid(format!("invalid base64url in {did}")))?;
84 decode_name_tlv(&bytes)
85 .map_err(|_| DidError::InvalidDid(format!("invalid TLV name in {did}")))
86 } else {
87 colon_decode(rest)
89 .ok_or_else(|| DidError::InvalidDid(format!("invalid did:ndn: {did}")))
90 }
91 } else {
92 let bytes = base64_url_decode(rest)
94 .map_err(|_| DidError::InvalidDid(format!("invalid base64url in {did}")))?;
95 decode_name_tlv(&bytes)
96 .map_err(|_| DidError::InvalidDid(format!("invalid TLV name in {did}")))
97 }
98}
99
100fn colon_decode(s: &str) -> Option<Name> {
103 if s.is_empty() {
104 return Some(Name::root());
105 }
106 let mut name = Name::root();
107 for part in s.split(':') {
108 if part.is_empty() {
109 return None;
110 }
111 name = name.append(part);
112 }
113 Some(name)
114}
115
116fn encode_name_tlv(name: &Name) -> Vec<u8> {
119 let mut inner: Vec<u8> = Vec::new();
120 for comp in name.components() {
121 write_tlv_to(&mut inner, comp.typ, &comp.value);
122 }
123 let mut out = Vec::new();
124 write_tlv_to(&mut out, NAME_TLV_TYPE, &inner);
125 out
126}
127
128fn decode_name_tlv(data: &[u8]) -> Result<Name, ()> {
129 let (typ, inner, _) = read_tlv(data).ok_or(())?;
130 if typ != NAME_TLV_TYPE {
131 return Err(());
132 }
133 let mut comps = Vec::new();
134 let mut rest = inner;
135 while !rest.is_empty() {
136 let (typ, val, remaining) = read_tlv(rest).ok_or(())?;
137 comps.push(NameComponent {
138 typ,
139 value: bytes::Bytes::copy_from_slice(val),
140 });
141 rest = remaining;
142 }
143 Ok(Name::from_components(comps))
144}
145
146fn write_tlv_to(buf: &mut Vec<u8>, typ: u64, value: &[u8]) {
147 write_varu64(buf, typ);
148 write_varu64(buf, value.len() as u64);
149 buf.extend_from_slice(value);
150}
151
152fn write_varu64(buf: &mut Vec<u8>, v: u64) {
153 if v <= 252 {
154 buf.push(v as u8);
155 } else if v <= 0xFFFF {
156 buf.push(0xFD);
157 buf.extend_from_slice(&(v as u16).to_be_bytes());
158 } else if v <= 0xFFFF_FFFF {
159 buf.push(0xFE);
160 buf.extend_from_slice(&(v as u32).to_be_bytes());
161 } else {
162 buf.push(0xFF);
163 buf.extend_from_slice(&v.to_be_bytes());
164 }
165}
166
167fn read_varu64(buf: &[u8]) -> Option<(u64, usize)> {
168 let first = *buf.first()?;
169 match first {
170 0..=252 => Some((first as u64, 1)),
171 0xFD => {
172 let b = buf.get(1..3)?;
173 Some((u16::from_be_bytes([b[0], b[1]]) as u64, 3))
174 }
175 0xFE => {
176 let b = buf.get(1..5)?;
177 Some((u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as u64, 5))
178 }
179 0xFF => {
180 let b = buf.get(1..9)?;
181 Some((u64::from_be_bytes(b.try_into().ok()?), 9))
182 }
183 }
184}
185
186fn read_tlv(buf: &[u8]) -> Option<(u64, &[u8], &[u8])> {
187 let (typ, t_sz) = read_varu64(buf)?;
188 let rest = &buf[t_sz..];
189 let (len, l_sz) = read_varu64(rest)?;
190 let rest = &rest[l_sz..];
191 let len = len as usize;
192 if rest.len() < len {
193 return None;
194 }
195 Some((typ, &rest[..len], &rest[len..]))
196}
197
198fn base64_url_encode(data: &[u8]) -> String {
199 use base64::Engine;
200 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(data)
201}
202
203fn base64_url_decode(s: &str) -> Result<Vec<u8>, ()> {
204 use base64::Engine;
205 base64::engine::general_purpose::URL_SAFE_NO_PAD
206 .decode(s)
207 .map_err(|_| ())
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use ndn_packet::Name;
214
215 #[test]
216 fn roundtrip_ascii_name() {
217 let name: Name = "/com/acme/alice".parse().unwrap();
218 let did = name_to_did(&name);
219 assert!(did.starts_with("did:ndn:"));
221 assert!(!did["did:ndn:".len()..].contains(':'));
222 let back = did_to_name(&did).unwrap();
223 assert_eq!(back, name);
224 }
225
226 #[test]
227 fn roundtrip_root() {
228 let name = Name::root();
229 let did = name_to_did(&name);
230 assert!(did.starts_with("did:ndn:"));
231 let back = did_to_name(&did).unwrap();
232 assert_eq!(back, name);
233 }
234
235 #[test]
236 fn roundtrip_versioned_component() {
237 let name: Name = "/com/acme".parse().unwrap();
238 let name = name.append_version(42);
239 let did = name_to_did(&name);
240 assert!(did.starts_with("did:ndn:"));
242 assert!(!did["did:ndn:".len()..].starts_with("v1:"));
243 let back = did_to_name(&did).unwrap();
244 assert_eq!(back, name);
245 }
246
247 #[test]
248 fn no_v1_ambiguity() {
249 let name: Name = "/v1/BwEA".parse().unwrap();
252 let did = name_to_did(&name);
253 assert!(did.starts_with("did:ndn:"));
254 assert!(!did.contains("v1:"));
256 let back = did_to_name(&did).unwrap();
257 assert_eq!(back, name);
258 }
259
260 #[test]
263 fn compat_simple_form() {
264 let name = did_to_name("did:ndn:com:acme:alice").unwrap();
266 assert_eq!(name, "/com/acme/alice".parse::<Name>().unwrap());
267 }
268
269 #[test]
270 fn compat_v1_binary_form() {
271 let original: Name = "/com/acme".parse().unwrap();
273 let original = original.append_version(42);
274 let tlv = encode_name_tlv(&original);
276 let b64 = base64_url_encode(&tlv);
277 let old_did = format!("did:ndn:v1:{b64}");
278 let back = did_to_name(&old_did).unwrap();
279 assert_eq!(back, original);
280 }
281}