1use core::ffi::{c_char, c_int, c_long, c_longlong, c_void};
4use core::ptr;
5use core::time::Duration;
6
7use js_sys::{Date, Math, Number};
8use rsqlite_vfs::OsCallback;
9use wasm_bindgen::prelude::wasm_bindgen;
10use wasm_bindgen::JsValue;
11
12pub struct WasmOsCallback;
13
14impl OsCallback for WasmOsCallback {
15 #[cfg(target_feature = "atomics")]
17 fn sleep(dur: Duration) {
18 let mut nanos = dur.as_nanos();
19 while nanos > 0 {
20 let amt = core::cmp::min(i64::MAX as u128, nanos);
21 let mut x = 0;
22 let val = unsafe { core::arch::wasm32::memory_atomic_wait32(&mut x, 0, amt as i64) };
24 debug_assert_eq!(val, 2);
25 nanos -= amt;
26 }
27 }
28
29 #[cfg(not(target_feature = "atomics"))]
30 fn sleep(_dur: Duration) {}
31
32 fn random(buf: &mut [u8]) {
33 fn fallback(buf: &mut [u8]) {
34 for b in buf {
36 *b = (Math::random() * 255000.0) as u32 as u8;
37 }
38 }
39
40 #[cfg(not(target_feature = "atomics"))]
41 get_random_values(buf).unwrap_or_else(|_| fallback(buf));
42
43 #[cfg(target_feature = "atomics")]
44 {
45 let array = js_sys::Uint8Array::new_with_length(buf.len() as _);
46 if get_random_values(&array).is_ok() {
47 array.copy_to(buf);
48 } else {
49 fallback(buf);
50 }
51 }
52 }
53
54 fn epoch_timestamp_in_ms() -> i64 {
55 Date::new_0().get_time() as i64
56 }
57}
58
59#[allow(non_camel_case_types)]
60type c_size_t = usize;
61
62#[allow(non_camel_case_types)]
63type c_time_t = c_longlong;
64
65#[wasm_bindgen]
66extern "C" {
67 #[cfg(not(target_feature = "atomics"))]
69 #[wasm_bindgen(js_namespace = ["globalThis", "crypto"], js_name = getRandomValues, catch)]
70 fn get_random_values(buf: &mut [u8]) -> Result<(), JsValue>;
71 #[cfg(target_feature = "atomics")]
72 #[wasm_bindgen(js_namespace = ["globalThis", "crypto"], js_name = getRandomValues, catch)]
73 fn get_random_values(buf: &js_sys::Uint8Array) -> Result<(), JsValue>;
74}
75
76fn yday_from_date(date: &Date) -> u32 {
77 const MONTH_DAYS_LEAP_CUMULATIVE: [u32; 12] =
78 [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];
79
80 const MONTH_DAYS_REGULAR_CUMULATIVE: [u32; 12] =
81 [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
82
83 let year = date.get_full_year();
84 let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
85
86 let month_days_cumulative = if leap {
87 MONTH_DAYS_LEAP_CUMULATIVE
88 } else {
89 MONTH_DAYS_REGULAR_CUMULATIVE
90 };
91 month_days_cumulative[date.get_month() as usize] + date.get_date() - 1
92}
93
94unsafe fn localtime_js(t: c_time_t, tm: *mut tm) {
99 let date = Date::new(&Number::from((t * 1000) as f64).into());
100
101 (*tm).tm_sec = date.get_seconds() as _;
102 (*tm).tm_min = date.get_minutes() as _;
103 (*tm).tm_hour = date.get_hours() as _;
104 (*tm).tm_mday = date.get_date() as _;
105 (*tm).tm_mon = date.get_month() as _;
106 (*tm).tm_year = (date.get_full_year() - 1900) as _;
107 (*tm).tm_wday = date.get_day() as _;
108 (*tm).tm_yday = yday_from_date(&date) as _;
109
110 let start = Date::new_with_year_month_day(date.get_full_year(), 0, 1);
111 let tz_offset = date.get_timezone_offset();
112 let summer_offset =
113 Date::new_with_year_month_day(date.get_full_year(), 6, 1).get_timezone_offset();
114 let winter_offset = start.get_timezone_offset();
115 (*tm).tm_isdst =
116 i32::from(summer_offset != winter_offset && tz_offset == winter_offset.min(summer_offset));
117
118 (*tm).tm_gmtoff = -(tz_offset * 60.0) as _;
119}
120
121#[repr(C)]
123pub struct tm {
124 pub tm_sec: c_int,
125 pub tm_min: c_int,
126 pub tm_hour: c_int,
127 pub tm_mday: c_int,
128 pub tm_mon: c_int,
129 pub tm_year: c_int,
130 pub tm_wday: c_int,
131 pub tm_yday: c_int,
132 pub tm_isdst: c_int,
133 pub tm_gmtoff: c_long,
134 pub tm_zone: *mut c_char,
135}
136
137#[no_mangle]
139pub unsafe extern "C" fn rust_sqlite_wasm_getentropy(
140 buf: *mut u8,
141 buf_len: c_size_t,
142) -> core::ffi::c_ushort {
143 const FUNCTION_NOT_SUPPORT: core::ffi::c_ushort = 52;
145
146 #[cfg(target_feature = "atomics")]
147 {
148 let array = js_sys::Uint8Array::new_with_length(buf_len as u32);
149 if get_random_values(&array).is_err() {
150 return FUNCTION_NOT_SUPPORT;
151 }
152 array.copy_to(core::slice::from_raw_parts_mut(buf, buf_len));
153 }
154
155 #[cfg(not(target_feature = "atomics"))]
156 if get_random_values(core::slice::from_raw_parts_mut(buf, buf_len)).is_err() {
157 return FUNCTION_NOT_SUPPORT;
158 }
159
160 0
161}
162
163#[no_mangle]
164pub unsafe extern "C" fn rust_sqlite_wasm_assert_fail(
165 expr: *const c_char,
166 file: *const c_char,
167 line: c_int,
168 func: *const c_char,
169) {
170 let expr = core::ffi::CStr::from_ptr(expr).to_string_lossy();
171 let file = core::ffi::CStr::from_ptr(file).to_string_lossy();
172 let func = core::ffi::CStr::from_ptr(func).to_string_lossy();
173 panic!("Assertion failed: {expr} ({file}: {func}: {line})");
174}
175
176#[no_mangle]
177pub unsafe extern "C" fn rust_sqlite_wasm_abort() {
178 core::unreachable!();
179}
180
181#[no_mangle]
183pub unsafe extern "C" fn rust_sqlite_wasm_localtime(t: *const c_time_t) -> *mut tm {
184 static mut TM: tm = tm {
186 tm_sec: 0,
187 tm_min: 0,
188 tm_hour: 0,
189 tm_mday: 0,
190 tm_mon: 0,
191 tm_year: 0,
192 tm_wday: 0,
193 tm_yday: 0,
194 tm_isdst: 0,
195 tm_gmtoff: 0,
196 tm_zone: ptr::null_mut(),
197 };
198 localtime_js(*t, ptr::addr_of_mut!(TM));
199 ptr::addr_of_mut!(TM)
200}
201
202const ALIGN: usize = core::mem::size_of::<usize>() * 2;
204
205#[no_mangle]
206pub unsafe extern "C" fn rust_sqlite_wasm_malloc(size: c_size_t) -> *mut c_void {
207 let layout = core::alloc::Layout::from_size_align_unchecked(size + ALIGN, ALIGN);
208 let ptr = alloc::alloc::alloc(layout);
209
210 if ptr.is_null() {
211 return ptr::null_mut();
212 }
213 *ptr.cast::<usize>() = size;
215
216 ptr.add(ALIGN).cast()
217}
218
219#[no_mangle]
220pub unsafe extern "C" fn rust_sqlite_wasm_free(ptr: *mut c_void) {
221 let ptr: *mut u8 = ptr.sub(ALIGN).cast();
223 let size = *(ptr.cast::<usize>());
224
225 let layout = core::alloc::Layout::from_size_align_unchecked(size + ALIGN, ALIGN);
226 alloc::alloc::dealloc(ptr, layout);
227}
228
229#[no_mangle]
230pub unsafe extern "C" fn rust_sqlite_wasm_realloc(
231 ptr: *mut c_void,
232 new_size: c_size_t,
233) -> *mut c_void {
234 let ptr: *mut u8 = ptr.sub(ALIGN).cast();
236 let size = *(ptr.cast::<usize>());
237
238 let layout = core::alloc::Layout::from_size_align_unchecked(size + ALIGN, ALIGN);
239 let ptr = alloc::alloc::realloc(ptr, layout, new_size + ALIGN);
240
241 if ptr.is_null() {
242 return ptr::null_mut();
243 }
244 *ptr.cast::<usize>() = new_size;
245
246 ptr.add(ALIGN).cast()
247}
248
249#[no_mangle]
250pub unsafe extern "C" fn rust_sqlite_wasm_calloc(num: c_size_t, size: c_size_t) -> *mut c_void {
251 let total = num * size;
252 let ptr: *mut u8 = rust_sqlite_wasm_malloc(total).cast();
253 if !ptr.is_null() {
254 ptr::write_bytes(ptr, 0, total);
255 }
256 ptr.cast()
257}
258
259#[no_mangle]
264pub unsafe extern "C" fn sqlite3_os_init() -> core::ffi::c_int {
265 rsqlite_vfs::memvfs::install::<WasmOsCallback>();
266 crate::bindings::SQLITE_OK
267}
268
269#[no_mangle]
274pub unsafe extern "C" fn sqlite3_os_end() -> core::ffi::c_int {
275 rsqlite_vfs::memvfs::uninstall();
276 crate::bindings::SQLITE_OK
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use core::ffi::CStr;
283
284 use crate::{
285 sqlite3_column_count, sqlite3_column_name, sqlite3_column_text, sqlite3_column_type,
286 sqlite3_initialize, sqlite3_open, sqlite3_prepare_v3, sqlite3_shutdown, sqlite3_step,
287 SQLITE_OK, SQLITE_ROW, SQLITE_TEXT,
288 };
289
290 use wasm_bindgen_test::{console_log, wasm_bindgen_test};
291
292 #[wasm_bindgen_test]
293 fn test_initialize_shutdown() {
294 unsafe {
295 assert_eq!(sqlite3_initialize(), SQLITE_OK, "failed to initialize");
296 assert_eq!(sqlite3_shutdown(), SQLITE_OK, "failed to shutdown");
297 }
298 }
299
300 #[wasm_bindgen_test]
301 fn test_random_get() {
302 let mut buf = [0u8; 10];
303 unsafe { rust_sqlite_wasm_getentropy(buf.as_mut_ptr(), buf.len()) };
304 console_log!("test_random_get: {buf:?}");
305 }
306
307 #[wasm_bindgen_test]
308 fn test_memory() {
309 unsafe {
310 let ptr1 = rust_sqlite_wasm_malloc(10);
311 let ptr2 = rust_sqlite_wasm_realloc(ptr1, 100);
312 rust_sqlite_wasm_free(ptr2);
313 console_log!("test_memory: {ptr1:?} {ptr2:?}");
314
315 let ptr: *mut u8 = rust_sqlite_wasm_calloc(2, 8).cast();
316 let buf = core::slice::from_raw_parts(ptr, 2 * 8);
317
318 assert!(buf.iter().all(|&x| x == 0));
319 }
320 }
321
322 #[wasm_bindgen_test]
323 fn test_localtime_sqlite() {
324 unsafe {
325 let mut db = core::ptr::null_mut();
326 let ret = sqlite3_open(c":memory:".as_ptr().cast(), &mut db as *mut _);
327 assert_eq!(ret, SQLITE_OK);
328 let mut stmt = core::ptr::null_mut();
329 let ret = sqlite3_prepare_v3(
330 db,
331 c"SELECT datetime('now', 'localtime');".as_ptr().cast(),
332 -1,
333 0,
334 &mut stmt as *mut _,
335 core::ptr::null_mut(),
336 );
337 assert_eq!(ret, SQLITE_OK);
338 while sqlite3_step(stmt) == SQLITE_ROW {
339 let count = sqlite3_column_count(stmt);
340 for col in 0..count {
341 let name = sqlite3_column_name(stmt, col);
342 let ty = sqlite3_column_type(stmt, col);
343 assert_eq!(ty, SQLITE_TEXT);
344 console_log!(
345 "col {:?}, time: {:?}",
346 CStr::from_ptr(name),
347 CStr::from_ptr(sqlite3_column_text(stmt, col).cast())
348 );
349 }
350 }
351 }
352 }
353
354 #[wasm_bindgen_test]
355 fn test_localtime() {
356 let mut tm = tm {
357 tm_sec: 0,
358 tm_min: 0,
359 tm_hour: 0,
360 tm_mday: 0,
361 tm_mon: 0,
362 tm_year: 0,
363 tm_wday: 0,
364 tm_yday: 0,
365 tm_isdst: 0,
366 tm_gmtoff: 0,
367 tm_zone: core::ptr::null_mut(),
368 };
369 unsafe {
370 localtime_js(1733976732, &mut tm as *mut tm);
371 };
372 let gmtoff = tm.tm_gmtoff / 3600;
373
374 assert_eq!(tm.tm_year, 2024 - 1900);
375 assert_eq!(tm.tm_mon, 12 - 1);
376 assert_eq!(tm.tm_mday, 12);
377 assert_eq!(tm.tm_hour as core::ffi::c_long, 12 - 8 + gmtoff);
378 assert_eq!(tm.tm_min, 12);
379 assert_eq!(tm.tm_sec, 12);
380 assert_eq!(tm.tm_wday, 4);
381 assert_eq!(tm.tm_yday, 346);
382 }
383}