use anyhow::anyhow;
use jam_std_common::hash_raw;
use jam_types::{Memo, ServiceId, MEMO_LEN};
use std::time::Duration;
pub type Blob = Vec<u8>;
#[derive(Clone, Debug)]
#[allow(clippy::len_without_is_empty)]
pub enum DataIdLen {
HashLen([u8; 32], usize),
Data(Vec<u8>),
}
impl DataIdLen {
pub fn hash(&self) -> [u8; 32] {
match self {
DataIdLen::HashLen(hash, _) => *hash,
DataIdLen::Data(data) => hash_raw(data),
}
}
pub fn len(&self) -> usize {
match self {
DataIdLen::HashLen(_, l) => *l,
DataIdLen::Data(data) => data.len(),
}
}
pub fn data(&self) -> Option<&[u8]> {
match self {
DataIdLen::HashLen(..) => None,
DataIdLen::Data(ref data) => Some(data),
}
}
pub fn into_data(self) -> Option<Vec<u8>> {
match self {
DataIdLen::HashLen(..) => None,
DataIdLen::Data(d) => Some(d),
}
}
}
pub fn data_id_len(s: &str) -> anyhow::Result<DataIdLen> {
match s {
_ if s.starts_with("0x") &&
s.len() > 67 &&
s.bytes().skip(2).take(64).all(|x| x.is_ascii_hexdigit()) &&
s.find(':') == Some(66) &&
s[67..].parse::<usize>().is_ok() =>
{
let mut hash = [0u8; 32];
for (i, c) in s.as_bytes()[2..66].chunks(2).enumerate() {
hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
}
Ok(DataIdLen::HashLen(hash, s[67..].parse::<usize>().expect("see above")))
},
_ if s.len() > 65 &&
s.bytes().take(64).all(|x| x.is_ascii_hexdigit()) &&
s.find(':') == Some(64) &&
s[65..].parse::<usize>().is_ok() =>
{
let mut hash = [0u8; 32];
for (i, c) in s.as_bytes()[..64].chunks(2).enumerate() {
hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
}
Ok(DataIdLen::HashLen(hash, s[65..].parse::<usize>().expect("see above")))
},
_ => Ok(DataIdLen::Data(blob(s)?)),
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum DataId {
Hash([u8; 32]),
Data(Vec<u8>),
}
#[allow(dead_code)]
#[allow(clippy::len_without_is_empty)]
impl DataId {
pub fn hash(&self) -> [u8; 32] {
match self {
DataId::Hash(hash) => *hash,
DataId::Data(data) => hash_raw(data),
}
}
pub fn len(&self) -> Option<usize> {
match self {
DataId::Hash(_) => None,
DataId::Data(data) => Some(data.len()),
}
}
pub fn data(&self) -> Option<&[u8]> {
match self {
DataId::Hash(..) => None,
DataId::Data(ref data) => Some(data),
}
}
}
pub fn data_id(s: &str) -> anyhow::Result<DataId> {
match s {
_ if s.len() == 64 && s.bytes().all(|x| x.is_ascii_hexdigit()) => {
let mut hash = [0u8; 32];
for (i, c) in s.as_bytes().chunks(2).enumerate() {
hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
}
Ok(DataId::Hash(hash))
},
_ if s.starts_with("0x") &&
s.len() == 66 &&
s.bytes().skip(2).all(|x| x.is_ascii_hexdigit()) =>
{
let mut hash = [0u8; 32];
for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() {
hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
}
Ok(DataId::Hash(hash))
},
_ => Ok(DataId::Data(blob(s)?)),
}
}
pub fn blob(s: &str) -> anyhow::Result<Blob> {
match s {
_ if std::path::Path::new(s).exists() => Ok(std::fs::read(s)?),
_ if s.starts_with("0x") &&
s.len().is_multiple_of(2) &&
s.bytes().skip(2).all(|x| x.is_ascii_hexdigit()) =>
{
let mut inner = Vec::with_capacity((s.len() - 2) / 2);
for c in s.as_bytes()[2..].chunks(2) {
inner.push(u8::from_str_radix(std::str::from_utf8(c)?, 16)?);
}
Ok(inner)
},
_ => Ok(s.as_bytes().to_vec()),
}
}
pub fn gas(s: &str) -> anyhow::Result<u64> {
if s == "max" {
Ok(u64::MAX)
} else {
Ok(s.parse::<u64>()?)
}
}
pub fn memo(s: &str) -> anyhow::Result<Memo> {
match s {
_ if s.len() == MEMO_LEN * 2 && s.bytes().all(|x| x.is_ascii_hexdigit()) => {
let mut inner = Memo::default();
for (i, c) in s.as_bytes().chunks(2).enumerate() {
inner[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
}
Ok(inner)
},
_ if s.len() <= MEMO_LEN => {
let mut inner = Memo::default();
inner.0[..s.len()].copy_from_slice(s.as_bytes());
Ok(inner)
},
_ => Err(anyhow!("Memo too long")),
}
}
pub fn service_id(s: &str) -> anyhow::Result<ServiceId> {
Ok(ServiceId::from_str_radix(s, 16)?)
}
pub fn exact_bytes(s: &str) -> anyhow::Result<u64> {
let i = s
.rfind(char::is_numeric)
.ok_or_else(|| anyhow!("Failed to parse number from {s:?}"))?;
let (number_str, prefix) = s.split_at(i + 1);
let prefix = prefix.trim();
if !matches!(prefix.chars().last(), Some('B' | 'b')) {
return Err(anyhow!("Failed to parse size in bytes: invalid prefix {prefix:?}"));
}
let prefix = &prefix[..prefix.len() - 1];
let scale = get_iec_scale(prefix)
.ok_or_else(|| anyhow!("Failed to parse size in bytes: invalid prefix {prefix:?}"))?;
let number: u64 = number_str.trim().parse()?;
number
.checked_mul(scale)
.ok_or_else(|| anyhow!("The size is too big: {number} * {scale}"))
}
fn get_iec_scale(prefix: &str) -> Option<u64> {
for (iec_prefix, scale) in IEC_TABLE.iter() {
if iec_prefix.eq_ignore_ascii_case(prefix) {
return Some(*scale);
}
}
None
}
pub(crate) const IEC_TABLE: [(&str, u64); 7] = [
("Ei", 1024_u64.pow(6)),
("Pi", 1024_u64.pow(5)),
("Ti", 1024_u64.pow(4)),
("Gi", 1024_u64.pow(3)),
("Mi", 1024_u64.pow(2)),
("Ki", 1024_u64),
("", 1_u64),
];
pub fn exact_duration(s: &str) -> anyhow::Result<Duration> {
let (number_str, unit) = match s.find(|ch: char| !ch.is_numeric()) {
Some(i) => (&s[..i], s[i..].trim()),
None => (s, ""),
};
let scale =
get_duration_scale(unit).ok_or_else(|| anyhow!("Invalid duration unit {unit:?}"))?;
let number: u64 = number_str.trim().parse()?;
let secs = number
.checked_mul(scale)
.ok_or_else(|| anyhow!("The duration is too big: {number} * {scale}"))?;
Ok(Duration::from_secs(secs))
}
fn get_duration_scale(their_unit: &str) -> Option<u64> {
for (our_unit, scale) in DURATION_TABLE.iter() {
if our_unit.eq_ignore_ascii_case(their_unit) {
return Some(*scale);
}
}
None
}
pub(crate) const DURATION_TABLE: [(&str, u64); 6] =
[("w", 60 * 60 * 24 * 7), ("d", 60 * 60 * 24), ("h", 60 * 60), ("m", 60), ("s", 1), ("", 1)];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_bytes_works() {
assert_eq!(1, exact_bytes("1B").unwrap());
assert_eq!(1024, exact_bytes("1KiB").unwrap());
assert_eq!(4 * 1024 * 1024, exact_bytes(" 4 mib ").unwrap());
}
#[test]
fn exact_duration_works() {
assert_eq!(Duration::from_secs(1), exact_duration("1 s").unwrap());
assert_eq!(Duration::from_secs(60), exact_duration("1m").unwrap());
assert_eq!(Duration::from_secs(60 * 60), exact_duration("1h").unwrap());
assert_eq!(Duration::from_secs(60 * 60 * 24), exact_duration("1d").unwrap());
assert_eq!(Duration::from_secs(60 * 60 * 24 * 7), exact_duration("1w").unwrap());
}
}