1use anyhow::anyhow;
2use jam_std_common::hash_raw;
3use jam_types::{CoreIndex, Memo, ServiceId, VecSet, MEMO_LEN};
4use std::time::Duration;
5
6pub type Blob = Vec<u8>;
7
8#[derive(Clone, Debug)]
9#[allow(clippy::len_without_is_empty)]
10pub enum DataIdLen {
11 HashLen([u8; 32], usize),
12 Data(Vec<u8>),
13}
14
15impl DataIdLen {
16 pub fn hash(&self) -> [u8; 32] {
17 match self {
18 DataIdLen::HashLen(hash, _) => *hash,
19 DataIdLen::Data(data) => hash_raw(data),
20 }
21 }
22 pub fn len(&self) -> usize {
23 match self {
24 DataIdLen::HashLen(_, l) => *l,
25 DataIdLen::Data(data) => data.len(),
26 }
27 }
28 pub fn data(&self) -> Option<&[u8]> {
29 match self {
30 DataIdLen::HashLen(..) => None,
31 DataIdLen::Data(ref data) => Some(data),
32 }
33 }
34 pub fn into_data(self) -> Option<Vec<u8>> {
35 match self {
36 DataIdLen::HashLen(..) => None,
37 DataIdLen::Data(d) => Some(d),
38 }
39 }
40}
41
42pub fn data_id_len(s: &str) -> anyhow::Result<DataIdLen> {
43 match s {
44 _ if s.starts_with("0x") &&
45 s.len() > 67 &&
46 s.bytes().skip(2).take(64).all(|x| x.is_ascii_hexdigit()) &&
47 s.find(':') == Some(66) &&
48 s[67..].parse::<usize>().is_ok() =>
49 {
50 let mut hash = [0u8; 32];
51 for (i, c) in s.as_bytes()[2..66].chunks(2).enumerate() {
52 hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
53 }
54 Ok(DataIdLen::HashLen(hash, s[67..].parse::<usize>().expect("see above")))
55 },
56 _ if s.len() > 65 &&
57 s.bytes().take(64).all(|x| x.is_ascii_hexdigit()) &&
58 s.find(':') == Some(64) &&
59 s[65..].parse::<usize>().is_ok() =>
60 {
61 let mut hash = [0u8; 32];
62 for (i, c) in s.as_bytes()[..64].chunks(2).enumerate() {
63 hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
64 }
65 Ok(DataIdLen::HashLen(hash, s[65..].parse::<usize>().expect("see above")))
66 },
67 _ => Ok(DataIdLen::Data(blob(s)?)),
68 }
69}
70
71#[allow(dead_code)]
72#[derive(Clone, Debug)]
73pub enum DataId {
74 Hash([u8; 32]),
75 Data(Vec<u8>),
76}
77
78#[allow(dead_code)]
79#[allow(clippy::len_without_is_empty)]
80impl DataId {
81 pub fn hash(&self) -> [u8; 32] {
82 match self {
83 DataId::Hash(hash) => *hash,
84 DataId::Data(data) => hash_raw(data),
85 }
86 }
87 pub fn len(&self) -> Option<usize> {
88 match self {
89 DataId::Hash(_) => None,
90 DataId::Data(data) => Some(data.len()),
91 }
92 }
93 pub fn data(&self) -> Option<&[u8]> {
94 match self {
95 DataId::Hash(..) => None,
96 DataId::Data(ref data) => Some(data),
97 }
98 }
99}
100
101pub fn data_id(s: &str) -> anyhow::Result<DataId> {
102 match s {
103 _ if s.len() == 64 && s.bytes().all(|x| x.is_ascii_hexdigit()) => {
104 let mut hash = [0u8; 32];
105 for (i, c) in s.as_bytes().chunks(2).enumerate() {
106 hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
107 }
108 Ok(DataId::Hash(hash))
109 },
110 _ if s.starts_with("0x") &&
111 s.len() == 66 &&
112 s.bytes().skip(2).all(|x| x.is_ascii_hexdigit()) =>
113 {
114 let mut hash = [0u8; 32];
115 for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() {
116 hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
117 }
118 Ok(DataId::Hash(hash))
119 },
120 _ => Ok(DataId::Data(blob(s)?)),
121 }
122}
123
124pub fn blob(s: &str) -> anyhow::Result<Blob> {
125 match s {
126 _ if std::path::Path::new(s).exists() => Ok(std::fs::read(s)?),
127 _ if s.starts_with("0x") &&
128 s.len().is_multiple_of(2) &&
129 s.bytes().skip(2).all(|x| x.is_ascii_hexdigit()) =>
130 {
131 let mut inner = Vec::with_capacity((s.len() - 2) / 2);
132 for c in s.as_bytes()[2..].chunks(2) {
133 inner.push(u8::from_str_radix(std::str::from_utf8(c)?, 16)?);
134 }
135 Ok(inner)
136 },
137 _ => Ok(s.as_bytes().to_vec()),
138 }
139}
140
141pub fn gas(s: &str) -> anyhow::Result<u64> {
142 if s == "max" {
143 Ok(u64::MAX)
144 } else {
145 Ok(s.parse::<u64>()?)
146 }
147}
148
149pub fn memo(s: &str) -> anyhow::Result<Memo> {
150 match s {
151 _ if s.len() == MEMO_LEN * 2 && s.bytes().all(|x| x.is_ascii_hexdigit()) => {
152 let mut inner = Memo::default();
153 for (i, c) in s.as_bytes().chunks(2).enumerate() {
154 inner[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
155 }
156 Ok(inner)
157 },
158 _ if s.len() <= MEMO_LEN => {
159 let mut inner = Memo::default();
160 inner.0[..s.len()].copy_from_slice(s.as_bytes());
161 Ok(inner)
162 },
163 _ => Err(anyhow!("Memo too long")),
164 }
165}
166
167pub fn service_id(s: &str) -> anyhow::Result<ServiceId> {
168 Ok(ServiceId::from_str_radix(s, 16)?)
169}
170
171pub fn exact_bytes(s: &str) -> anyhow::Result<u64> {
172 let i = s
173 .rfind(char::is_numeric)
174 .ok_or_else(|| anyhow!("Failed to parse number from {s:?}"))?;
175 let (number_str, prefix) = s.split_at(i + 1);
176 let prefix = prefix.trim();
177 if !matches!(prefix.chars().last(), Some('B' | 'b')) {
178 return Err(anyhow!("Failed to parse size in bytes: invalid prefix {prefix:?}"))
179 }
180 let prefix = &prefix[..prefix.len() - 1];
181 let scale = get_iec_scale(prefix)
182 .ok_or_else(|| anyhow!("Failed to parse size in bytes: invalid prefix {prefix:?}"))?;
183 let number: u64 = number_str.trim().parse()?;
184 number
185 .checked_mul(scale)
186 .ok_or_else(|| anyhow!("The size is too big: {number} * {scale}"))
187}
188
189fn get_iec_scale(prefix: &str) -> Option<u64> {
190 for (iec_prefix, scale) in IEC_TABLE.iter() {
191 if iec_prefix.eq_ignore_ascii_case(prefix) {
192 return Some(*scale)
193 }
194 }
195 None
196}
197
198pub(crate) const IEC_TABLE: [(&str, u64); 7] = [
199 ("Ei", 1024_u64.pow(6)),
200 ("Pi", 1024_u64.pow(5)),
201 ("Ti", 1024_u64.pow(4)),
202 ("Gi", 1024_u64.pow(3)),
203 ("Mi", 1024_u64.pow(2)),
204 ("Ki", 1024_u64),
205 ("", 1_u64),
206];
207
208pub fn exact_duration(s: &str) -> anyhow::Result<Duration> {
209 let (number_str, unit) = match s.find(|ch: char| !ch.is_numeric()) {
210 Some(i) => (&s[..i], s[i..].trim()),
211 None => (s, ""),
212 };
213 let scale =
214 get_duration_scale(unit).ok_or_else(|| anyhow!("Invalid duration unit {unit:?}"))?;
215 let number: u64 = number_str.trim().parse()?;
216 let nanos = number
217 .checked_mul(scale)
218 .ok_or_else(|| anyhow!("The duration is too big: {number} * {scale}"))?;
219 Ok(Duration::from_nanos(nanos))
220}
221
222fn get_duration_scale(their_unit: &str) -> Option<u64> {
223 for (our_unit, scale) in DURATION_TABLE.iter() {
224 if our_unit.eq_ignore_ascii_case(their_unit) {
225 return Some(*scale)
226 }
227 }
228 None
229}
230
231pub(crate) const DURATION_TABLE: [(&str, u64); 9] = [
232 ("w", 60 * 60 * 24 * 7 * 1_000_000_000),
233 ("d", 60 * 60 * 24 * 1_000_000_000),
234 ("h", 60 * 60 * 1_000_000_000),
235 ("m", 60 * 1_000_000_000),
236 ("s", 1_000_000_000),
237 ("", 1_000_000_000),
238 ("ms", 1_000_000),
239 ("µs", 1_000),
240 ("ns", 1),
241];
242
243pub fn ed25519_public_key(s: &str) -> anyhow::Result<[u8; 32]> {
245 let s = s.trim();
246 hex::FromHex::from_hex(s).map_err(|e| anyhow!("Invalid Ed25519 public key {s:?}: {e}"))
247}
248
249pub fn cores(s: &str) -> anyhow::Result<VecSet<CoreIndex>> {
254 let error = || {
255 anyhow!(
256 "Expects comma-separated list of cores or core ranges, e.g. \"1,2,4-6,10\". \
257 Ranges are inclusive."
258 )
259 };
260 let mut cores = VecSet::new();
261 for core_spec in s.split(',') {
262 let range = if core_spec.contains('-') {
263 let mut iter = core_spec.splitn(2, '-');
264 let min_core: CoreIndex =
265 iter.next().ok_or_else(error)?.parse().map_err(|_| error())?;
266 let max_core: CoreIndex =
267 iter.next().ok_or_else(error)?.parse().map_err(|_| error())?;
268 min_core..=max_core
269 } else {
270 let core: CoreIndex = core_spec.parse()?;
271 core..=core
272 };
273 for core in range {
274 cores.insert(core);
275 }
276 }
277 Ok(cores)
278}
279
280pub fn import_spec(input: &str) -> anyhow::Result<jam_types::ImportSpec> {
281 let mut parts = input.splitn(3, ':');
282 let malformed = anyhow!("import must be of form \"target_prefix:hash:index\"");
283 let Some(prefix) = parts.next() else {
284 return Err(malformed);
285 };
286 let is_segment_root = if prefix == "wp" {
287 false
288 } else if prefix == "sr" {
289 true
290 } else {
291 return Err(anyhow!(
292 "invalid prefix for imports, must be `sr` (segment root) or `wp` (work package)"
293 ))
294 };
295 let Some(hash_str) = parts.next() else {
296 return Err(malformed);
297 };
298 let hash: [u8; 32] = decode_hex(hash_str)?;
299 let Some(index_str) = parts.next() else {
300 return Err(malformed);
301 };
302 let index = index_str.parse::<u16>()?;
303 Ok(if is_segment_root {
304 jam_types::ImportSpec {
305 root: jam_types::RootIdentifier::Direct(jam_types::SegmentTreeRoot(hash)),
306 index,
307 }
308 } else {
309 jam_types::ImportSpec {
310 root: jam_types::RootIdentifier::Indirect(jam_types::WorkPackageHash(hash)),
311 index,
312 }
313 })
314}
315
316fn decode_hex<const N: usize>(s: &str) -> anyhow::Result<[u8; N]> {
318 let s = s.strip_prefix("0x").unwrap_or(s);
319 if s.len() != N * 2 {
320 return Err(anyhow!("Invalid hex length: expected {} characters, got {}", N * 2, s.len()))
321 }
322 let mut result = [0u8; N];
323 for i in 0..N {
324 let byte_str = &s[i * 2..(i + 1) * 2];
325 result[i] = u8::from_str_radix(byte_str, 16)
326 .map_err(|_| anyhow!("Invalid hex character at position {}", i * 2))?;
327 }
328 Ok(result)
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn exact_bytes_works() {
337 assert_eq!(1, exact_bytes("1B").unwrap());
338 assert_eq!(1024, exact_bytes("1KiB").unwrap());
339 assert_eq!(4 * 1024 * 1024, exact_bytes(" 4 mib ").unwrap());
340 }
341
342 #[test]
343 fn exact_duration_works() {
344 assert_eq!(Duration::from_secs(1), exact_duration("1 s").unwrap());
345 assert_eq!(Duration::from_secs(60), exact_duration("1m").unwrap());
346 assert_eq!(Duration::from_secs(60 * 60), exact_duration("1h").unwrap());
347 assert_eq!(Duration::from_secs(60 * 60 * 24), exact_duration("1d").unwrap());
348 assert_eq!(Duration::from_secs(60 * 60 * 24 * 7), exact_duration("1w").unwrap());
349 }
350
351 #[test]
352 fn cores_work() {
353 assert_eq!(&[1, 2, 4, 5, 6, 10], cores("1,2,4-6,10").unwrap().as_ref());
354 assert_eq!(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], cores("1-4,2-10").unwrap().as_ref());
355 }
356
357 #[test]
358 fn ed25519_public_key_works() {
359 assert_eq!(
360 [0x11_u8; 32],
361 ed25519_public_key("1111111111111111111111111111111111111111111111111111111111111111")
362 .unwrap()
363 );
364 }
365}