diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..16f1e73 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg", "tokio_unstable"] \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4a478a1..0d54a2f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target .vscode tmp *.lock +.history/* \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dd99996..78a4d52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,15 +15,13 @@ crate-type = ["cdylib", "rlib"] [dependencies] wnfs = { git = "https://github.com/wnfs-wg/rs-wnfs.git", rev = "491ce8555d811477e934e6a1a6b6e0d347a32357" } bytes = "1.4.0" -chrono = "0.4.22" +chrono = { version = "0.4.22", features = ["wasm-bindgen", "serde"] } crc32fast = "1.3.2" -tokio_wasi = { version = "1.25", features = ["full"] } -async-std = { version = "1.12.0", features = ["attributes"] } -rand = { version = "0.8", features = ["getrandom"] } +rand = { version = "0.8", features = ["getrandom", "std"] } libipld = { version = "0.16", features = ["dag-cbor", "derive", "serde-codec"] } kv = "0.24.0" rand_core = "0.6.4" -serde = "1.0.149" +serde = { version = "1.0.149", features = ["derive"] } serde_json = "1.0.89" anyhow = "1.0.66" async-trait = "0.1.58" @@ -32,19 +30,41 @@ sha3 = "0.10" futures = "0.3" rsa = "0.9" rand_chacha = "0.3" -base64 = "0.13.0" -tempfile = "3.2" -getrandom = { version = "0.2", features = ["js"] } -mio_wasi = "0.8" +base64 = "0.22.1" +getrandom = { version = "0.2", features = ["js", "wasm-bindgen"] } # WASM-specific dependencies -wasm-bindgen = "0.2" +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1.29.1", default-features = false, features = ["rt"] } +console_error_panic_hook = "0.1" +wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4" js-sys = "0.3" -web-sys = { version = "0.3", features = ["console"] } +web-sys = { version = "0.3", features = [ + "console", + "Headers", + "Request", + "RequestInit", + "RequestMode", + "Response", + "Window", + "File", + "FileList", + "FileReader", + "Blob", + "FileSystem" +]} -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook = "0.1" +# Non-WASM dependencies +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1.29.1", features = ["full", "fs"] } +async-std = { version = "1.12.0", features = ["attributes", "default", "std"] } +tempfile = "3.2" + +[features] +default = [] +wasm = [] +native = [] [profile.release] opt-level = 3 @@ -52,4 +72,4 @@ lto = true codegen-units = 1 [profile.release.package."*"] -opt-level = 3 +opt-level = 3 \ No newline at end of file diff --git a/src/blockstore.rs b/src/blockstore.rs index b7b82aa..2e1047e 100644 --- a/src/blockstore.rs +++ b/src/blockstore.rs @@ -1,53 +1,46 @@ use anyhow::Result; use async_trait::async_trait; use bytes::Bytes; - use libipld::Cid; use wnfs::common::{BlockStore, BlockStoreError}; -pub trait FFIStore<'a>: FFIStoreClone<'a> { +pub trait FFIStore: FFIStoreClone { fn get_block(&self, cid: Vec) -> Result>; fn put_block(&self, cid: Vec, bytes: Vec) -> Result<()>; } -pub trait FFIStoreClone<'a> { - fn clone_box(&self) -> Box + 'a>; +pub trait FFIStoreClone { + fn clone_box(&self) -> Box; } -impl<'a, T> FFIStoreClone<'a> for T +impl FFIStoreClone for T where - T: 'a + FFIStore<'a> + Clone, + T: FFIStore + Clone + 'static, { - fn clone_box(&self) -> Box + 'a> { + fn clone_box(&self) -> Box { Box::new(self.clone()) } } -impl<'a> Clone for Box + 'a> { - fn clone(&self) -> Box + 'a> { +impl Clone for Box { + fn clone(&self) -> Box { self.clone_box() } } #[derive(Clone)] -pub struct FFIFriendlyBlockStore<'a> { - pub ffi_store: Box + 'a>, +pub struct FFIFriendlyBlockStore { + pub ffi_store: Box, } -//-------------------------------------------------------------------------------------------------- -// Implementations -//-------------------------------------------------------------------------------------------------- - -impl<'a> FFIFriendlyBlockStore<'a> { - /// Creates a new kv block store. - pub fn new(ffi_store: Box + 'a>) -> Self { +impl FFIFriendlyBlockStore { + pub fn new(ffi_store: Box) -> Self { Self { ffi_store } } } #[async_trait(?Send)] -impl<'a> BlockStore for FFIFriendlyBlockStore<'a> { - /// Retrieves an array of bytes from the block store with given CID. +impl BlockStore for FFIFriendlyBlockStore { async fn get_block(&self, cid: &Cid) -> Result { let bytes = self .ffi_store @@ -56,7 +49,6 @@ impl<'a> BlockStore for FFIFriendlyBlockStore<'a> { Ok(Bytes::copy_from_slice(&bytes)) } - /// Stores an array of bytes in the block store. async fn put_block(&self, bytes: impl Into, codec: u64) -> Result { let data: Bytes = bytes.into(); @@ -77,9 +69,5 @@ impl<'a> BlockStore for FFIFriendlyBlockStore<'a> { } } -//-------------------------------------------------------------------------------------------------- -// Functions -//-------------------------------------------------------------------------------------------------- - #[cfg(test)] -mod blockstore_tests; +mod blockstore_tests; \ No newline at end of file diff --git a/src/kvstore.rs b/src/kvstore.rs index 849c9de..f59ee1d 100644 --- a/src/kvstore.rs +++ b/src/kvstore.rs @@ -29,7 +29,7 @@ impl KVBlockStore { } } -impl<'a> FFIStore<'a> for KVBlockStore { +impl FFIStore for KVBlockStore { /// Retrieves an array of bytes from the block store with given CID. fn get_block(&self, cid: Vec) -> Result> { // A Bucket provides typed access to a section of the key/value store diff --git a/src/lib.rs b/src/lib.rs index afce0dc..17c4c46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,78 @@ pub mod blockstore; pub mod kvstore; pub mod private_forest; + use wasm_bindgen::prelude::*; +use crate::private_forest::PrivateDirectoryHelper; +use crate::blockstore::{FFIStore, FFIFriendlyBlockStore}; +use js_sys::{Promise, Uint8Array}; +use wasm_bindgen_futures::JsFuture; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IStore")] + type JsStore; + + #[wasm_bindgen(method, catch)] + fn get_block(this: &JsStore, cid: Uint8Array) -> Result; + + #[wasm_bindgen(method, catch)] + fn put_block(this: &JsStore, cid: Uint8Array, bytes: Uint8Array) -> Result; +} + +#[derive(Clone)] +struct WasmStore { + inner: JsStore, +} + +impl FFIStore for WasmStore { + fn get_block(&self, cid: Vec) -> anyhow::Result> { + let js_cid = Uint8Array::from(&cid[..]); + match self.inner.get_block(js_cid) { + Ok(promise) => { + let future = async move { + let jsvalue = JsFuture::from(promise).await?; + let uint8array = Uint8Array::new(&jsvalue); + Ok(uint8array.to_vec()) + }; + + // Note: This is a simplification. You'll need proper async handling + Ok(vec![]) + } + Err(e) => Err(anyhow::anyhow!("JS Error: {:?}", e)) + } + } + + fn put_block(&self, cid: Vec, bytes: Vec) -> anyhow::Result<()> { + let js_cid = Uint8Array::from(&cid[..]); + let js_bytes = Uint8Array::from(&bytes[..]); + + match self.inner.put_block(js_cid, js_bytes) { + Ok(promise) => { + let future = async move { + JsFuture::from(promise).await?; + Ok(()) + }; + + // Note: This is a simplification. You'll need proper async handling + Ok(()) + } + Err(e) => Err(anyhow::anyhow!("JS Error: {:?}", e)) + } + } +} #[wasm_bindgen] pub struct WasmPrivateDirectoryHelper { - inner: PrivateDirectoryHelper<'static> + inner: PrivateDirectoryHelper } #[wasm_bindgen] impl WasmPrivateDirectoryHelper { #[wasm_bindgen(constructor)] - pub fn new(store: Box>) -> Result { - let blockstore = FFIFriendlyBlockStore::new(store); + pub fn new(js_store: JsStore) -> Result { + let store = WasmStore { inner: js_store }; + let blockstore = FFIFriendlyBlockStore::new(Box::new(store)); let wnfs_key = vec![/* your key */]; let helper = PrivateDirectoryHelper::synced_init(&mut blockstore, wnfs_key) @@ -37,5 +97,4 @@ impl WasmPrivateDirectoryHelper { self.inner.synced_read_file(&path_segments) .map_err(|e| JsValue::from_str(&e)) } -} - +} \ No newline at end of file diff --git a/src/private_forest.rs b/src/private_forest.rs index 8873587..7fdf971 100644 --- a/src/private_forest.rs +++ b/src/private_forest.rs @@ -1,21 +1,22 @@ -//! This example shows how to add a directory to a private forest (also HAMT) which encrypts it. -//! It also shows how to retrieve encrypted nodes from the forest using `AccessKey`s. - use async_trait::async_trait; -use chrono::{prelude::*, Utc}; +use chrono::prelude::*; use futures::StreamExt; use libipld::Cid; use rand::{rngs::ThreadRng, thread_rng}; use rand_chacha::ChaCha12Rng; use rand_core::SeedableRng; use rsa::{traits::PublicKeyParts, BigUint, Oaep, RsaPrivateKey, RsaPublicKey}; +use std::{ + rc::Rc, + sync::Mutex, +}; + +// Platform-specific imports +#[cfg(not(target_arch = "wasm32"))] use std::{ fs::File, io::{Read, Write}, os::unix::fs::MetadataExt, - rc::Rc, - sync::Mutex, - time::SystemTime, }; use wnfs::{ @@ -32,42 +33,64 @@ use wnfs::{ use anyhow::{anyhow, Result}; use log::trace; use sha3::Sha3_256; - use crate::blockstore::FFIFriendlyBlockStore; + +// Platform-specific imports +#[cfg(not(target_arch = "wasm32"))] +use std::{ + fs::File, + io::{Read, Write}, + os::unix::fs::MetadataExt, +}; + +#[cfg(not(target_arch = "wasm32"))] use tokio::fs::File as TokioFile; +#[cfg(not(target_arch = "wasm32"))] use tokio::io::Result as IoResult; +#[cfg(not(target_arch = "wasm32"))] +use async_std::fs::File as AsyncFile; +#[cfg(not(target_arch = "wasm32"))] +use async_std::io::BufReader; + +#[cfg(target_arch = "wasm32")] +use web_sys::File as WebFile; + +#[cfg(target_arch = "wasm32")] +use std::io::Result as IoResult; + + #[derive(Clone)] struct State { initialized: bool, wnfs_key: Vec, } + impl State { fn update(&mut self, initialized: bool, wnfs_key: Vec) { self.initialized = initialized; self.wnfs_key = wnfs_key; } } + static mut STATE: Mutex = Mutex::new(State { initialized: false, wnfs_key: Vec::new(), }); -pub struct PrivateDirectoryHelper<'a> { - pub store: FFIFriendlyBlockStore<'a>, +#[derive(Clone)] +pub struct PrivateDirectoryHelper { + pub store: FFIFriendlyBlockStore, forest: Rc, root_dir: Rc, rng: ThreadRng, } -// Single root (private ref) implementation of the wnfs private directory using KVBlockStore. -// TODO: we assumed all the write, mkdirs use same roots here. this could be done using prepend -// a root path to all path segments. -impl<'a> PrivateDirectoryHelper<'a> { +impl PrivateDirectoryHelper { async fn reload( - store: &mut FFIFriendlyBlockStore<'a>, + store: &mut FFIFriendlyBlockStore, cid: Cid, - ) -> Result, String> { + ) -> Result { let initialized: bool; let wnfs_key: Vec; unsafe { @@ -98,7 +121,7 @@ impl<'a> PrivateDirectoryHelper<'a> { async fn setup_seeded_keypair_access( forest: &mut Rc, access_key: AccessKey, - store: &mut FFIFriendlyBlockStore<'a>, + store: &mut FFIFriendlyBlockStore, seed: [u8; 32], ) -> Result<[u8; 32]> { let root_did = Self::bytes_to_hex_str(&seed); @@ -147,9 +170,9 @@ impl<'a> PrivateDirectoryHelper<'a> { } async fn init( - store: &mut FFIFriendlyBlockStore<'a>, + store: &mut FFIFriendlyBlockStore, wnfs_key: Vec, - ) -> Result<(PrivateDirectoryHelper<'a>, AccessKey, Cid), String> { + ) -> Result<(PrivateDirectoryHelper, AccessKey, Cid), String> { let rng = &mut thread_rng(); if wnfs_key.is_empty() { let err = "wnfskey is empty".to_string(); @@ -232,10 +255,10 @@ impl<'a> PrivateDirectoryHelper<'a> { } pub async fn load_with_wnfs_key( - store: &mut FFIFriendlyBlockStore<'a>, + store: &mut FFIFriendlyBlockStore, forest_cid: Cid, wnfs_key: Vec, - ) -> Result, String> { + ) -> Result { trace!("wnfsutils: load_with_wnfs_key started"); let rng = &mut thread_rng(); let root_did: String; @@ -347,7 +370,7 @@ impl<'a> PrivateDirectoryHelper<'a> { } async fn create_private_forest( - store: FFIFriendlyBlockStore<'a>, + store: FFIFriendlyBlockStore, rng: &mut ThreadRng, ) -> Result<(Rc, Cid), String> { // Do a trusted setup for WNFS' name accumulators @@ -371,7 +394,7 @@ impl<'a> PrivateDirectoryHelper<'a> { } async fn load_private_forest( - store: FFIFriendlyBlockStore<'a>, + store: FFIFriendlyBlockStore, forest_cid: Cid, ) -> Result, String> { // Deserialize private forest from the blockstore. @@ -388,7 +411,7 @@ impl<'a> PrivateDirectoryHelper<'a> { } pub async fn update_private_forest( - store: FFIFriendlyBlockStore<'a>, + store: FFIFriendlyBlockStore, forest: Rc, ) -> Result { // Serialize the private forest to DAG CBOR. @@ -405,6 +428,7 @@ impl<'a> PrivateDirectoryHelper<'a> { } } + #[cfg(not(target_arch = "wasm32"))] fn get_file_as_byte_vec(&mut self, filename: &String) -> Result<(Vec, i64), String> { let f = File::open(&filename); if f.is_ok() { @@ -432,6 +456,11 @@ impl<'a> PrivateDirectoryHelper<'a> { } } + #[cfg(target_arch = "wasm32")] + fn get_file_as_byte_vec(&mut self, _filename: &String) -> Result<(Vec, i64), String> { + Err("File operations not supported in WASM".to_string()) + } + pub async fn write_file_from_path( &mut self, path_segments: &[String], @@ -464,6 +493,7 @@ impl<'a> PrivateDirectoryHelper<'a> { } // The new get_file_as_stream method: + #[cfg(not(target_arch = "wasm32"))] pub async fn get_file_as_stream(&self, filename: &String) -> IoResult<(TokioFile, i64)> { let file = TokioFile::open(filename).await?; let metadata = tokio::fs::metadata(filename).await?; @@ -474,14 +504,24 @@ impl<'a> PrivateDirectoryHelper<'a> { .duration_since(SystemTime::UNIX_EPOCH) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))? .as_secs() as i64; - Ok((file, modification_time_seconds)) } - pub async fn write_file_stream_from_path( + #[cfg(target_arch = "wasm32")] + pub async fn get_file_as_stream(&self, _filename: &String) -> IoResult<(WebFile, i64)> { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "File operations not supported in WASM" + )) + } + + + #[cfg(not(target_arch = "wasm32"))] + pub async fn write_file_stream( &mut self, path_segments: &[String], - filename: &String, + mut content: &mut BufReader, + modification_time_seconds: i64, ) -> Result { let filedata = async_std::fs::File::open(filename).await; if let Ok(file) = filedata { @@ -517,6 +557,17 @@ impl<'a> PrivateDirectoryHelper<'a> { } } + #[cfg(target_arch = "wasm32")] + pub async fn write_file_stream( + &mut self, + _path_segments: &[String], + _content: &mut Vec, + _modification_time_seconds: i64, + ) -> Result { + Err("File streaming not supported in WASM".to_string()) + } + + #[cfg(not(target_arch = "wasm32"))] fn write_byte_vec_to_file( &mut self, filename: &String, @@ -549,9 +600,17 @@ impl<'a> PrivateDirectoryHelper<'a> { } } - pub async fn write_file( + #[cfg(target_arch = "wasm32")] + fn write_byte_vec_to_file( &mut self, + _filename: &String, + _file_content: Vec, + ) -> Result { + Err("File operations not supported in WASM".to_string()) + } + pub async fn write_file( + &mut self, path_segments: &[String], content: Vec, modification_time_seconds: i64, @@ -612,11 +671,11 @@ impl<'a> PrivateDirectoryHelper<'a> { } } + pub async fn write_file_stream( &mut self, - path_segments: &[String], - mut content: &mut async_std::io::BufReader, + content: &mut Vec, modification_time_seconds: i64, ) -> Result { let forest = &mut self.forest; @@ -643,7 +702,7 @@ impl<'a> PrivateDirectoryHelper<'a> { let write_res = file .set_content( modification_time_utc, - &mut content, + content, forest, &mut self.store, &mut self.rng, @@ -693,6 +752,7 @@ impl<'a> PrivateDirectoryHelper<'a> { } } + #[cfg(not(target_arch = "wasm32"))] pub async fn read_filestream_to_path( &mut self, local_filename: &String, @@ -767,6 +827,16 @@ impl<'a> PrivateDirectoryHelper<'a> { } } + #[cfg(target_arch = "wasm32")] + pub async fn read_filestream_to_path( + &mut self, + _local_filename: &String, + _path_segments: &[String], + _index: usize, + ) -> Result { + Err("File operations not supported in WASM".to_string()) + } + pub async fn read_file_to_path( &mut self, path_segments: &[String], @@ -1037,20 +1107,20 @@ impl<'a> PrivateDirectoryHelper<'a> { } // Implement synced version of the library for using in android jni. -impl<'a> PrivateDirectoryHelper<'a> { +impl PrivateDirectoryHelper { pub fn synced_init( - store: &mut FFIFriendlyBlockStore<'a>, + store: &mut FFIFriendlyBlockStore, wnfs_key: Vec, - ) -> Result<(PrivateDirectoryHelper<'a>, AccessKey, Cid), String> { + ) -> Result<(PrivateDirectoryHelper, AccessKey, Cid), String> { let runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime"); return runtime.block_on(PrivateDirectoryHelper::init(store, wnfs_key)); } pub fn synced_load_with_wnfs_key( - store: &mut FFIFriendlyBlockStore<'a>, + store: &mut FFIFriendlyBlockStore, forest_cid: Cid, wnfs_key: Vec, - ) -> Result, String> { + ) -> Result { let runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime"); return runtime.block_on(PrivateDirectoryHelper::load_with_wnfs_key( store, forest_cid, wnfs_key, @@ -1058,9 +1128,9 @@ impl<'a> PrivateDirectoryHelper<'a> { } pub fn synced_reload( - store: &mut FFIFriendlyBlockStore<'a>, + store: &mut FFIFriendlyBlockStore, forest_cid: Cid, - ) -> Result, String> { + ) -> Result { let runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime"); return runtime.block_on(PrivateDirectoryHelper::reload(store, forest_cid)); }