rose-squared-sdk 0.1.0

Privacy-preserving encrypted search SDK implementing the SWiSSSE protocol with forward/backward security and volume-hiding, compilable to WebAssembly
Documentation

// src/wasm/indexed_db_store.rs

use async_trait::async_trait;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use js_sys::Promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{IdbDatabase, IdbObjectStore, IdbOpenDbRequest, IdbRequest, IdbTransaction, IdbTransactionMode, IdbVersionChangeEvent};
use crate::{
    crypto::primitives::{EncValue, Tag},
    server::edb::{EncryptedStore, RawEdbEntry},
    VaultError,
};

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

const DB_NAME: &str = "rose-squared-db";
const OBJECT_STORE_NAME: &str = "edb";

pub struct IndexedDbStore {
    db: IdbDatabase,
}

impl IndexedDbStore {
    pub async fn new() -> Result<Self, VaultError> {
        log("Creating new IndexedDbStore");
        let window = web_sys::window().ok_or(VaultError::StorageError("no window".to_string()))?;
        let idb_factory = window.indexed_db()
            .map_err(|e| VaultError::StorageError(format!("{:?}", e)))?
            .ok_or(VaultError::StorageError("no indexeddb factory".to_string()))?;

        let open_request: IdbOpenDbRequest = idb_factory.open_with_u32(DB_NAME, 1)
            .map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;

        let onupgradeneeded = Closure::wrap(Box::new(move |event: &IdbVersionChangeEvent| {
            log("onupgradeneeded");
            if let Some(target) = event.target() {
                if let Ok(open_request) = target.dyn_into::<IdbOpenDbRequest>() {
                    if let Ok(result) = open_request.result() {
                        let db: IdbDatabase = result.into();
                        if !db.object_store_names().contains(&OBJECT_STORE_NAME.to_string()) {
                            if let Err(e) = db.create_object_store(OBJECT_STORE_NAME) {
                                log(&format!("Failed to create object store: {:?}", e));
                            }
                        }
                    }
                }            }
        }) as Box<dyn FnMut(&IdbVersionChangeEvent)>);

        open_request.set_onupgradeneeded(Some(onupgradeneeded.as_ref().unchecked_ref()));
        onupgradeneeded.forget();

        let db = future_from_request(open_request.into()).await?;

        Ok(Self { db: db.into() })
    }

    fn transaction(&self, mode: IdbTransactionMode) -> Result<IdbTransaction, VaultError> {
        let store_names = js_sys::Array::new();
        store_names.push(&JsValue::from_str(OBJECT_STORE_NAME));
        self.db
            .transaction_with_str_sequence_and_mode(&store_names, mode)
            .map_err(|e| VaultError::StorageError(format!("{:?}", e)))
    }

    fn store(&self, tx: &IdbTransaction) -> Result<IdbObjectStore, VaultError> {
        tx.object_store(OBJECT_STORE_NAME)
            .map_err(|e| VaultError::StorageError(format!("{:?}", e)))
    }
}

async fn future_from_request(request: IdbRequest) -> Result<JsValue, VaultError> {
    let promise = Promise::new(&mut |resolve, reject| {
        let onsuccess = Closure::wrap(Box::new(move |event: web_sys::Event| {
            if let Some(target) = event.target() {
                if let Ok(req) = target.dyn_into::<IdbRequest>() {
                    if let Ok(res) = req.result() {
                        let _ = resolve.call1(&JsValue::NULL, &res);
                        return;
                    }
                }
            }
            let _ = resolve.call1(&JsValue::NULL, &JsValue::NULL);
        }) as Box<dyn FnMut(web_sys::Event)>);

        let onerror = Closure::wrap(Box::new(move |event: web_sys::Event| {
            let _ = reject.call1(&JsValue::NULL, &event.into());
        }) as Box<dyn FnMut(web_sys::Event)>);

        request.set_onsuccess(Some(onsuccess.as_ref().unchecked_ref()));
        request.set_onerror(Some(onerror.as_ref().unchecked_ref()));

        onsuccess.forget();
        onerror.forget();
    });
    JsFuture::from(promise).await
        .map_err(|e| VaultError::StorageError(format!("{:?}", e)))
}


#[async_trait(?Send)]
impl EncryptedStore for IndexedDbStore {
    async fn get(&self, tag: &Tag) -> Result<Option<EncValue>, VaultError> {
        let tx = self.transaction(IdbTransactionMode::Readonly)?;
        let store = self.store(&tx)?;
        let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        let request = store.get(&key)
            .map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;

        let result = future_from_request(request).await?;

        if result.is_undefined() || result.is_null() {
            Ok(None)
        } else {
            let value: Vec<u8> = serde_wasm_bindgen::from_value(result).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
            Ok(Some(EncValue(value)))
        }
    }

    async fn put(&self, tag: Tag, value: EncValue) -> Result<(), VaultError> {
        let tx = self.transaction(IdbTransactionMode::Readwrite)?;
        let store = self.store(&tx)?;
        let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        let val = serde_wasm_bindgen::to_value(&value.0).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        store.put_with_key(&val, &key)
            .map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        Ok(())
    }

    async fn delete(&self, tag: &Tag) -> Result<(), VaultError> {
        let tx = self.transaction(IdbTransactionMode::Readwrite)?;
        let store = self.store(&tx)?;
        let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        store.delete(&key)
            .map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        Ok(())
    }

    async fn atomic_update(
        &self,
        puts: Vec<RawEdbEntry>,
        removes: Vec<Tag>,
    ) -> Result<(), VaultError> {
        let tx = self.transaction(IdbTransactionMode::Readwrite)?;
        let store = self.store(&tx)?;

        for tag in removes {
            let key = serde_wasm_bindgen::to_value(&tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
            store.delete(&key)
                .map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        }

        for entry in puts {
            let key = serde_wasm_bindgen::to_value(&entry.tag.0.to_vec()).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
            let val = serde_wasm_bindgen::to_value(&entry.value.0).map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
            store.put_with_key(&val, &key)
                .map_err(|e| VaultError::StorageError(format!("{:?}", e)))?;
        }

        Ok(())
    }
}