use base64::Engine;
macro_rules! ensure {
($cond:expr_2021, $err:expr_2021 $(,)?) => {
if !$cond {
return std::result::Result::Err($err.into());
}
};
}
macro_rules! bail {
($err:expr_2021 $(,)?) => {{
return std::result::Result::Err($err.into());
}};
}
#[expect(unused)]
pub fn sodium_increment_le(n: &mut [u8]) {
let mut c = 1u16;
for b in n {
c += *b as u16;
*b = c as u8;
c >>= 8;
}
}
pub fn sodium_increment_be(n: &mut [u8]) {
let mut c = 1u16;
for b in n.iter_mut().rev() {
c += *b as u16;
*b = c as u8;
c >>= 8;
}
}
pub fn hashcash(resource: String, bits: u32) -> String {
use rand::{RngExt, distr::StandardUniform};
use sha1::{Digest, Sha1};
if bits > 32 {
tracing::warn!("Minting a hashcash token with {} bits. If the application is frozen, you'll know why", bits);
}
use time::format_description::{Component, FormatItem};
let format = [
FormatItem::Component(Component::CalendarYearFullStandardRange(time::format_description::modifier::CalendarYearFullStandardRange::default())),
FormatItem::Component(Component::MonthNumerical(time::format_description::modifier::MonthNumerical::default())),
FormatItem::Component(Component::Day(time::format_description::modifier::Day::default())),
];
let base64_engine = base64::engine::general_purpose::STANDARD;
let date = base64_engine.encode(time::OffsetDateTime::now_utc().date().format(&format[..]).unwrap());
let rand: String = base64_engine.encode(rand::rng().sample_iter(StandardUniform).take(16).collect::<Vec<u8>>());
let mut counter = [0; 8];
let mut hasher = Sha1::new();
loop {
sodium_increment_be(&mut counter);
let stamp = format!("1:{}:{}:{}::{}:{}", bits, date, resource, rand, base64_engine.encode(counter));
hasher.update(&stamp);
let result = hasher.finalize_reset();
let mut leading_zeros = 0;
for byte in result {
let front_zeros = byte.leading_zeros();
leading_zeros += front_zeros;
if front_zeros < 8 {
break;
}
}
if leading_zeros >= bits {
return stamp;
}
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("Timed out")]
pub(crate) struct TimeoutError;
pub(crate) async fn sleep(timeout: std::time::Duration) {
#[cfg(target_family = "wasm")]
wasmtimer::tokio::sleep(timeout).await;
#[cfg(not(target_family = "wasm"))]
async_io::Timer::after(timeout).await;
}
pub(crate) fn timeout<'a, R, F: std::future::Future<Output = R> + 'a>(
timeout: std::time::Duration, future: F,
) -> impl Future<Output = Result<R, TimeoutError>> + 'a {
let timeout_future = async move {
sleep(timeout).await;
Err(TimeoutError)
};
futures_lite::future::or(async { Ok(future.await) }, timeout_future)
}
#[cfg(any(test, feature = "forwarding"))]
fn executor() -> &'static async_executor::Executor<'static> {
const NUM_THREADS: usize = 3;
static EXECUTOR: std::sync::LazyLock<async_executor::Executor> = std::sync::LazyLock::new(|| {
let ex = async_executor::Executor::new();
for n in 1..=NUM_THREADS {
std::thread::Builder::new()
.name(format!("magic-wormhole-{}", n))
.spawn(|| {
loop {
std::panic::catch_unwind(|| async_io::block_on(executor().run(futures_lite::future::pending::<()>()))).ok();
}
})
.expect("cannot spawn executor thread");
}
ex.spawn(async_process::driver()).detach();
ex
});
&EXECUTOR
}
#[cfg(any(test, feature = "forwarding"))]
pub(crate) fn spawn<T: Send + 'static>(future: impl Future<Output = T> + Send + 'static) -> async_task::Task<T> {
executor().spawn(future)
}