use std::{
collections::{HashMap, HashSet},
str::FromStr,
};
use cosmian_crypto_core::{
reexport::rand_core::SeedableRng, CsRng, FixedSizeCBytes, RandomFixedSizeCBytes, SymmetricKey,
};
use cosmian_findex::{Data, IndexedValue, Keyword, Label};
use js_sys::{Array, Function, Promise, Uint8Array};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use super::types::InterruptInput;
use crate::{
db_interfaces::{
custom::wasm::WasmCallbacks,
rest::{AuthorizationToken, CallbackPrefix},
},
interfaces::wasm::{
types::{ArrayOfKeywords, Filter, IndexedData, IndexedValuesAndKeywords, SearchResults},
WasmError,
},
Configuration, InstantiatedFindex,
};
#[wasm_bindgen]
pub struct WasmFindex(InstantiatedFindex);
#[wasm_bindgen]
impl WasmFindex {
pub async fn new_with_custom_interface(
entry_callbacks: WasmCallbacks,
chain_callbacks: WasmCallbacks,
) -> Result<WasmFindex, JsError> {
let config = Configuration::Wasm(entry_callbacks, chain_callbacks);
InstantiatedFindex::new(config)
.await
.map(Self)
.map_err(WasmError::from)
.map_err(JsError::from)
}
pub async fn new_with_rest_interface(
token: String,
entry_url: String,
chain_url: String,
) -> Result<WasmFindex, JsError> {
let config =
Configuration::Rest(AuthorizationToken::from_str(&token)?, entry_url, chain_url);
InstantiatedFindex::new(config)
.await
.map(Self)
.map_err(WasmError::from)
.map_err(JsError::from)
}
}
#[wasm_bindgen]
impl WasmFindex {
pub async fn search(
&self,
key: Uint8Array,
label: String,
keywords: ArrayOfKeywords,
interrupt: Option<Function>,
) -> Result<SearchResults, JsError> {
let key = SymmetricKey::try_from_slice(&key.to_vec()).map_err(|e| {
WasmError(format!(
"Findex search: While parsing key for Findex search, {e}"
))
})?;
let label = Label::from(label.as_str());
let keywords = Array::from(&JsValue::from(keywords))
.iter()
.map(|word| Keyword::from(Uint8Array::new(&word).to_vec()))
.collect::<HashSet<_>>();
let user_interrupt = |res: HashMap<Keyword, HashSet<IndexedValue<Keyword, Data>>>| async {
if let Some(interrupt) = &interrupt {
let res = <InterruptInput>::try_from(res).map_err(|e| {
format!(
"Findex search: failed converting input of user interrupt into Js object: \
{e:?}"
)
})?;
let res = interrupt
.call1(&JsValue::null(), &res)
.map_err(|e| format!("failed calling user interrupt: {e:?}"))?;
let interruption_flag =
JsFuture::from(Promise::resolve(&res)).await.map_err(|e| {
format!(
"Findex search: failed getting the promised results from user interrupt: {e:?}"
)
})?;
interruption_flag.as_bool().ok_or_else(|| {
format!(
"Findex search: user interrupt does not return a boolean value: {interrupt:?}"
)
})
} else {
Ok(false)
}
};
let res = self
.0
.search(&key, &label, keywords.into(), &user_interrupt)
.await?;
<SearchResults>::try_from(&res).map_err(JsError::from)
}
pub async fn add(
&self,
key: Uint8Array,
label: String,
additions: IndexedValuesAndKeywords,
) -> Result<ArrayOfKeywords, JsError> {
log::info!("add: entering");
let key = SymmetricKey::try_from_slice(&key.to_vec())
.map_err(|e| WasmError(format!("Findex add: failed parsing key: {e}")))?;
let label = Label::from(label.as_str());
let additions =
<HashMap<IndexedValue<Keyword, Data>, HashSet<Keyword>>>::try_from(&additions)
.map_err(|e| {
WasmError(format!(
"Findex add: failed parsing additions from WASM: {e:?}"
))
})?;
log::info!("add: key, label and additions correctly parsed");
let keywords = self
.0
.add(&key, &label, additions.into())
.await
.map_err(|e| {
WasmError(format!(
"Findex add: failed adding data to the index: {e:?}"
))
})?;
log::info!("add: exiting successfully: keywords: {}", keywords);
Ok(<ArrayOfKeywords>::from(&keywords))
}
pub async fn delete(
&self,
key: Uint8Array,
label: String,
deletions: IndexedValuesAndKeywords,
) -> Result<ArrayOfKeywords, JsError> {
let key = SymmetricKey::try_from_slice(&key.to_vec())
.map_err(|e| WasmError(format!("Findex delete: failed parsing Findex key: {e}")))?;
let label = Label::from(label.as_str());
let deletions =
<HashMap<IndexedValue<Keyword, Data>, HashSet<Keyword>>>::try_from(&deletions)
.map_err(|e| {
WasmError(format!(
"Findex delete: failed parsing additions from WASM: {e:?}"
))
})?;
let res = self
.0
.delete(&key, &label, deletions.into())
.await
.map_err(|e| {
WasmError(format!(
"Findex delete: failed adding data to the index: {e:?}"
))
})?;
Ok(<ArrayOfKeywords>::from(&res))
}
pub async fn compact(
&self,
old_key: Uint8Array,
new_key: Uint8Array,
old_label: String,
new_label: String,
compacting_rate: f32,
data_filter: Option<Filter>,
) -> Result<(), JsError> {
let old_key = SymmetricKey::try_from_slice(&old_key.to_vec())
.map_err(|e| WasmError(format!("Findex compact: failed parsing old key: {e}")))?;
let new_key = SymmetricKey::try_from_slice(&new_key.to_vec())
.map_err(|e| WasmError(format!("Findex compact: failed parsing new key: {e}")))?;
let old_label = Label::from(old_label.as_str());
let new_label = Label::from(new_label.as_str());
let compacting_rate = compacting_rate as usize;
let data_filter = |data: HashSet<Data>| async {
if let Some(data_filter) = &data_filter {
let moved_data = data;
let data = <IndexedData>::from(&moved_data);
let js_function = Function::from(JsValue::from(data_filter));
let promise =
Promise::resolve(&js_function.call1(&JsValue::null(), &data).map_err(|e| {
format!("Findex compact: failed calling the obsolete data filter: {e:?}")
})?);
let filtered_data = JsFuture::from(promise).await.map_err(|e| {
format!(
"Findex compact: failed getting the promised results from the obsolete data \
filter: {e:?}"
)
})?;
let filtered_data = <HashSet<Data>>::try_from(IndexedData::from(filtered_data))
.map_err(|e| {
format!(
"Findex compact: failed converting Js array back to filtered data: {e:?}"
)
})?;
Ok(filtered_data)
} else {
Ok(data)
}
};
self.0
.compact(
&old_key,
&new_key,
&old_label,
&new_label,
compacting_rate as f64,
&data_filter,
)
.await
.map_err(|e| {
JsError::from(WasmError(format!(
"Findex compact: failed compacting: {e:?}"
)))
})
}
}
#[wasm_bindgen]
#[must_use]
#[derive(Debug, Clone)]
pub struct WasmToken(AuthorizationToken);
#[wasm_bindgen]
impl WasmToken {
pub fn random(index_id: String) -> Result<String, JsError> {
let mut rng = CsRng::from_entropy();
let findex_key = SymmetricKey::new(&mut rng);
let seeds = (0..4)
.map(|prefix_id| {
(
CallbackPrefix::try_from(prefix_id).expect("prefix IDs are correct"),
SymmetricKey::new(&mut rng),
)
})
.collect();
Ok(Self(AuthorizationToken::new(index_id, findex_key, seeds)?)
.0
.to_string())
}
pub fn create(
index_id: String,
fetch_entries_key: Option<Uint8Array>,
fetch_chains_key: Option<Uint8Array>,
upsert_entries_key: Option<Uint8Array>,
insert_chains_key: Option<Uint8Array>,
) -> Result<String, JsError> {
let mut rng = CsRng::from_entropy();
let findex_key = SymmetricKey::new(&mut rng);
let mut seeds = HashMap::new();
if let Some(key) = fetch_entries_key {
let key = SymmetricKey::try_from_slice(key.to_vec().as_slice())?;
seeds.insert(CallbackPrefix::FetchEntry, key);
}
if let Some(key) = fetch_chains_key {
let key = SymmetricKey::try_from_slice(key.to_vec().as_slice())?;
seeds.insert(CallbackPrefix::FetchChain, key);
}
if let Some(key) = upsert_entries_key {
let key = SymmetricKey::try_from_slice(key.to_vec().as_slice())?;
seeds.insert(CallbackPrefix::Upsert, key);
}
if let Some(key) = insert_chains_key {
let key = SymmetricKey::try_from_slice(key.to_vec().as_slice())?;
seeds.insert(CallbackPrefix::Insert, key);
}
let token = AuthorizationToken::new(index_id, findex_key, seeds)?;
Ok(token.to_string())
}
pub fn generate_reduced_token_string(
&self,
is_read: bool,
is_write: bool,
) -> Result<WasmToken, JsError> {
let mut new_token: WasmToken = self.clone();
new_token.0.reduce_permissions(is_read, is_write)?;
Ok(new_token)
}
}