1use std::sync::atomic::{AtomicU32, Ordering};
13
14pub trait Runtime: Send + Sync {
15 fn now_unix_seconds(&self) -> i64;
19 fn next_inode_generation(&self) -> u32;
21}
22
23pub struct SystemRuntime;
26static COUNTER: AtomicU32 = AtomicU32::new(1);
27
28impl Runtime for SystemRuntime {
29 fn now_unix_seconds(&self) -> i64 {
30 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
31 {
32 (js_sys::Date::now() / 1000.0).floor() as i64
33 }
34 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
35 {
36 let now = std::time::SystemTime::now();
37 match now.duration_since(std::time::UNIX_EPOCH) {
38 Ok(d) => i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
39 Err(e) => i64::try_from(e.duration().as_secs()).map_or(i64::MIN, |s| -s),
41 }
42 }
43 }
44 fn next_inode_generation(&self) -> u32 {
45 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
46 let seed = {
47 static SEED: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
50 *SEED.get_or_init(|| (js_sys::Math::random() * 4294967296.0) as u32)
51 };
52 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
53 let seed = std::process::id();
54 seed.wrapping_add(COUNTER.fetch_add(1, Ordering::Relaxed))
55 }
56}
57
58pub(crate) fn fill_random(out: &mut [u8]) {
66 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
67 {
68 if !fill_from_web_crypto(out) {
69 for b in out.iter_mut() {
70 *b = (js_sys::Math::random() * 256.0) as u8;
71 }
72 }
73 }
74 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
75 {
76 if let Ok(mut f) = std::fs::File::open("/dev/urandom") {
77 use std::io::Read;
78 if f.read_exact(out).is_ok() {
79 return;
80 }
81 }
82 let mut state = std::time::SystemTime::now()
84 .duration_since(std::time::UNIX_EPOCH)
85 .map(|d| d.as_nanos() as u64)
86 .unwrap_or(0xDEADBEEF)
87 ^ (std::process::id() as u64).wrapping_mul(0x9E3779B97F4A7C15);
88 for b in out.iter_mut() {
89 state = state
90 .wrapping_mul(6364136223846793005)
91 .wrapping_add(1442695040888963407);
92 *b = (state >> 56) as u8;
93 }
94 }
95}
96
97#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
100fn fill_from_web_crypto(out: &mut [u8]) -> bool {
101 use js_sys::wasm_bindgen::{JsCast, JsValue};
102 use js_sys::{Function, Reflect, Uint8Array};
103
104 let Ok(crypto) = Reflect::get(&js_sys::global(), &JsValue::from_str("crypto")) else {
105 return false;
106 };
107 if !crypto.is_object() {
108 return false;
109 }
110 let Ok(get) = Reflect::get(&crypto, &JsValue::from_str("getRandomValues")) else {
111 return false;
112 };
113 let Ok(get) = get.dyn_into::<Function>() else {
114 return false;
115 };
116 let bytes = Uint8Array::new_with_length(out.len() as u32);
117 let arg: &JsValue = bytes.as_ref();
118 if get.call1(&crypto, arg).is_err() {
119 return false;
120 }
121 bytes.copy_to(out);
122 true
123}
124
125pub(crate) struct Stopwatch {
132 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
133 started_ms: f64,
134 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
135 started: std::time::Instant,
136}
137
138impl Stopwatch {
139 pub(crate) fn start() -> Self {
140 Self {
141 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
142 started_ms: js_sys::Date::now(),
143 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
144 started: std::time::Instant::now(),
145 }
146 }
147
148 pub(crate) fn elapsed(&self) -> std::time::Duration {
149 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
150 {
151 let ms = (js_sys::Date::now() - self.started_ms).max(0.0);
152 std::time::Duration::from_secs_f64(ms / 1000.0)
153 }
154 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
155 {
156 self.started.elapsed()
157 }
158 }
159}