1use anyhow::anyhow;
2use jam_std_common::hash_raw;
3use jam_types::{Memo, ServiceId, 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 secs = number
217 .checked_mul(scale)
218 .ok_or_else(|| anyhow!("The duration is too big: {number} * {scale}"))?;
219 Ok(Duration::from_secs(secs))
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); 6] =
232 [("w", 60 * 60 * 24 * 7), ("d", 60 * 60 * 24), ("h", 60 * 60), ("m", 60), ("s", 1), ("", 1)];
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn exact_bytes_works() {
240 assert_eq!(1, exact_bytes("1B").unwrap());
241 assert_eq!(1024, exact_bytes("1KiB").unwrap());
242 assert_eq!(4 * 1024 * 1024, exact_bytes(" 4 mib ").unwrap());
243 }
244
245 #[test]
246 fn exact_duration_works() {
247 assert_eq!(Duration::from_secs(1), exact_duration("1 s").unwrap());
248 assert_eq!(Duration::from_secs(60), exact_duration("1m").unwrap());
249 assert_eq!(Duration::from_secs(60 * 60), exact_duration("1h").unwrap());
250 assert_eq!(Duration::from_secs(60 * 60 * 24), exact_duration("1d").unwrap());
251 assert_eq!(Duration::from_secs(60 * 60 * 24 * 7), exact_duration("1w").unwrap());
252 }
253}