1use bytes::Bytes;
2use iroh_docs::DocTicket;
3use log::error;
4use miette::IntoDiagnostic;
5use path_clean::PathClean;
6use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
7use std::ffi::CString;
8use std::path::PathBuf;
9
10pub fn normalise_path(path: &PathBuf) -> PathBuf {
20 PathBuf::from("/").join(path).clean()
21}
22
23pub fn path_to_entry_key(path: &PathBuf) -> Bytes {
33 let path = normalise_path(path);
34 let mut path_bytes = path.into_os_string().into_encoded_bytes();
35 path_bytes.push(b'\0');
36 path_bytes.into()
37}
38
39pub fn entry_key_to_path(key: &[u8]) -> miette::Result<PathBuf> {
49 Ok(PathBuf::from(
50 CString::from_vec_with_nul(key.to_vec())
51 .into_diagnostic()?
52 .into_string()
53 .into_diagnostic()?,
54 ))
55}
56
57pub fn path_to_entry_prefix(path: &PathBuf) -> Bytes {
67 let path = normalise_path(path);
68 let path_bytes = path.into_os_string().into_encoded_bytes();
69 path_bytes.into()
70}
71
72pub fn fmt(bytes: impl AsRef<[u8]>) -> String {
82 let mut text = data_encoding::BASE32_NOPAD.encode(bytes.as_ref());
83 text.make_ascii_lowercase();
84 text
85}
86
87pub fn fmt_short(bytes: impl AsRef<[u8]>) -> String {
97 let len = bytes.as_ref().len().min(10);
98 let mut text = data_encoding::BASE32_NOPAD.encode(&bytes.as_ref()[..len]);
99 text.make_ascii_lowercase();
100 text
101}
102
103pub fn parse_array<const N: usize>(input: &str) -> miette::Result<[u8; N]> {
113 data_encoding::BASE32_NOPAD
114 .decode(input.to_ascii_uppercase().as_bytes())
115 .into_diagnostic()?
116 .try_into()
117 .map_err(|_| {
118 miette::miette!(
119 "Unable to parse {input} as a base32-encoded byte array of length {N} … "
120 )
121 })
122}
123
124pub fn parse_array_hex_or_base32<const LEN: usize>(input: &str) -> miette::Result<[u8; LEN]> {
134 let mut bytes = [0u8; LEN];
135 if input.len() == LEN * 2 {
136 hex::decode_to_slice(input, &mut bytes).into_diagnostic()?;
137 Ok(bytes)
138 } else {
139 Ok(parse_array(input)?)
140 }
141}
142
143pub fn merge_tickets(tickets: &Vec<DocTicket>) -> Option<DocTicket> {
153 let ticket_parts: Vec<_> = tickets
154 .par_iter()
155 .map(|ticket| ticket.capability.clone())
156 .zip(tickets.par_iter().map(|ticket| ticket.nodes.clone()))
157 .collect();
158 ticket_parts
159 .into_iter()
160 .reduce(|mut merged_tickets, next_ticket| {
161 if let Err(e) = merged_tickets.0.merge(next_ticket.0) {
162 error!("{e}");
163 }
164 merged_tickets.1.extend_from_slice(&next_ticket.1);
165 merged_tickets
166 })
167 .map(|mut merged_tickets| {
168 merged_tickets.1.sort_unstable();
169 merged_tickets.1.dedup();
170 DocTicket {
171 capability: merged_tickets.0,
172 nodes: merged_tickets.1,
173 }
174 })
175}