oku_fs/fs/util.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
use bytes::Bytes;
use iroh_docs::DocTicket;
use miette::IntoDiagnostic;
use path_clean::PathClean;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use std::ffi::CString;
use std::path::PathBuf;
pub(super) fn normalise_path(path: &PathBuf) -> PathBuf {
PathBuf::from("/").join(path).clean()
}
/// Converts a path to a key for an entry in a file system replica.
///
/// # Arguments
///
/// * `path` - The path to convert to a key.
///
/// # Returns
///
/// A null-terminated byte string representing the path.
pub fn path_to_entry_key(path: &PathBuf) -> Bytes {
let path = normalise_path(path);
let mut path_bytes = path.into_os_string().into_encoded_bytes();
path_bytes.push(b'\0');
path_bytes.into()
}
/// Converts a key of a replica entry into a path within a replica.
///
/// # Arguments
///
/// * `key` - The replica entry key, being a null-terminated byte string.
///
/// # Returns
///
/// A path pointing to the file with the key.
pub fn entry_key_to_path(key: &[u8]) -> miette::Result<PathBuf> {
Ok(PathBuf::from(
CString::from_vec_with_nul(key.to_vec())
.into_diagnostic()?
.into_string()
.into_diagnostic()?,
))
}
/// Converts a path to a key prefix for entries in a file system replica.
///
/// # Arguments
///
/// * `path` - The path to convert to a key prefix.
///
/// # Returns
///
/// A byte string representing the path, without a null byte at the end.
pub fn path_to_entry_prefix(path: &PathBuf) -> Bytes {
let path = normalise_path(path);
let path_bytes = path.into_os_string().into_encoded_bytes();
path_bytes.into()
}
/// Format bytes as a base32-encoded lowercase string.
///
/// # Arguments
///
/// * `bytes` - The bytes to encode.
///
/// # Return
///
/// The bytes encoded as a lowercase string, represented in base32.
pub fn fmt(bytes: impl AsRef<[u8]>) -> String {
let mut text = data_encoding::BASE32_NOPAD.encode(bytes.as_ref());
text.make_ascii_lowercase();
text
}
/// Format first ten bytes of a byte list as a base32-encoded lowercase string.
///
/// # Arguments
///
/// * `bytes` - The byte list to encode.
///
/// # Return
///
/// The first ten bytes encoded as a lowercase string, represented in base32.
pub fn fmt_short(bytes: impl AsRef<[u8]>) -> String {
let len = bytes.as_ref().len().min(10);
let mut text = data_encoding::BASE32_NOPAD.encode(&bytes.as_ref()[..len]);
text.make_ascii_lowercase();
text
}
/// Parse a string as a base32-encoded byte array of length `N`.
///
/// # Arguments
///
/// * `input` - The string to parse.
///
/// # Returns
///
/// An array of bytes of length `N`.
pub fn parse_array<const N: usize>(input: &str) -> miette::Result<[u8; N]> {
data_encoding::BASE32_NOPAD
.decode(input.to_ascii_uppercase().as_bytes())
.into_diagnostic()?
.try_into()
.map_err(|_| {
miette::miette!(
"Unable to parse {input} as a base32-encoded byte array of length {N} … "
)
})
}
/// Parse a string either as a hex-encoded or base32-encoded byte array of length `LEN`.
///
/// # Arguments
///
/// * `input` - The string to parse.
///
/// # Returns
///
/// An array of bytes of length `LEN`.
pub fn parse_array_hex_or_base32<const LEN: usize>(input: &str) -> miette::Result<[u8; LEN]> {
let mut bytes = [0u8; LEN];
if input.len() == LEN * 2 {
hex::decode_to_slice(input, &mut bytes).into_diagnostic()?;
Ok(bytes)
} else {
Ok(parse_array(input)?)
}
}
/// Merge multiple tickets into one, returning `None` if no tickets were given.
///
/// # Arguments
///
/// * `tickets` - A vector of tickets to merge.
///
/// # Returns
///
/// `None` if no tickets were given, or a ticket with a merged capability and merged list of nodes.
pub fn merge_tickets(tickets: &Vec<DocTicket>) -> Option<DocTicket> {
let ticket_parts: Vec<_> = tickets
.par_iter()
.map(|ticket| ticket.capability.clone())
.zip(tickets.par_iter().map(|ticket| ticket.nodes.clone()))
.collect();
ticket_parts
.into_iter()
.reduce(|mut merged_tickets, next_ticket| {
let _ = merged_tickets.0.merge(next_ticket.0);
merged_tickets.1.extend_from_slice(&next_ticket.1);
merged_tickets
})
.map(|mut merged_tickets| {
merged_tickets.1.sort_unstable();
merged_tickets.1.dedup();
DocTicket {
capability: merged_tickets.0,
nodes: merged_tickets.1,
}
})
}