#[cfg(not(feature = "wasm"))]
use std::time::SystemTime;
use std::{ops::Deref, str::FromStr};
use async_trait::async_trait;
use cosmian_crypto_core::bytes_ser_de::Serializable;
use cosmian_findex::{kmac, DbInterface, ENTRY_LENGTH, LINK_LENGTH};
pub use cosmian_findex::{TokenToEncryptedValueMap, TokenWithEncryptedValueList, Tokens};
#[cfg(feature = "wasm")]
use js_sys::Date;
use reqwest::Client;
use super::{upsert_data::UpsertData, AuthorizationToken, CallbackPrefix};
use crate::{
db_interfaces::DbInterfaceError,
ser_de::ffi_ser_de::{
deserialize_edx_lines, deserialize_token_set, serialize_edx_lines, serialize_token_set,
},
};
pub const REQUEST_SIGNATURE_TIMEOUT_AS_SECS: u64 = 60;
pub const SIGNATURE_LENGTH: usize = 32;
macro_rules! impl_rest_backend {
($type:ident, $value_length:ident, $name:literal, $table_bit:expr) => {
impl Deref for $type {
type Target = RestParameters;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl $type {
#[must_use]
pub fn new(parameters: RestParameters) -> Self {
Self(parameters)
}
async fn post(
&self,
callback: CallbackPrefix,
bytes: &[u8],
) -> Result<Vec<u8>, DbInterfaceError> {
let key = {
self.token
.get_key(&self.token.index_id, callback)
.ok_or_else(|| DbInterfaceError::MissingPermission(callback as i32))?
};
#[cfg(feature = "wasm")]
let current_timestamp = (Date::now() / 1000.0) as u64;
#[cfg(not(feature = "wasm"))]
let current_timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| {
DbInterfaceError::Other("SystemTime is before UNIX_EPOCH".to_string())
})?
.as_secs();
let expiration_timestamp_bytes =
(current_timestamp + REQUEST_SIGNATURE_TIMEOUT_AS_SECS).to_be_bytes();
let signature = kmac!(SIGNATURE_LENGTH, &key, &expiration_timestamp_bytes, bytes);
let mut body = Vec::with_capacity(
signature.len() + expiration_timestamp_bytes.len() + bytes.len(),
);
body.extend(&signature);
body.extend(&expiration_timestamp_bytes);
body.extend(bytes);
let url = {
format!(
"{}/indexes/{}/{}",
&self.url,
self.token.index_id,
callback.get_uri(),
)
};
let response = Client::new()
.post(url)
.body(body)
.send()
.await
.map_err(|err| {
DbInterfaceError::Other(format!(
"Unable to send the request to FindexREST: {err}"
))
})?;
if !response.status().is_success() {
return Err(DbInterfaceError::Other(format!(
"request to FindexREST server failed, status code is {}, response is {}",
response.status(),
response
.text()
.await
.unwrap_or_else(|_| "cannot parse response".to_owned())
)));
}
response.bytes().await.map(|r| r.to_vec()).map_err(|err| {
DbInterfaceError::Other(format!(
"Impossible to read the returned bytes from FindexREST server: {err}"
))
})
}
}
#[async_trait(?Send)]
impl DbInterface<$value_length> for $type {
type Error = DbInterfaceError;
async fn dump_tokens(&self) -> Result<cosmian_findex::Tokens, Self::Error> {
let bytes = self.post(CallbackPrefix::DumpTokens, &[]).await?;
deserialize_token_set(&bytes)
.map_err(Self::Error::from)
.map(Into::into)
}
async fn fetch(
&self,
tokens: $crate::db_interfaces::rest::stores::Tokens,
) -> Result<
$crate::db_interfaces::rest::stores::TokenWithEncryptedValueList<$value_length>,
Self::Error,
> {
let bytes = serialize_token_set(&tokens.into())?;
let res = self
.post(
(CallbackPrefix::FetchEntry as u8 + $table_bit).try_into()?,
&bytes,
)
.await?;
deserialize_edx_lines(&res)
.map_err(Self::Error::from)
.map(Into::into)
}
async fn upsert(
&self,
old_values: $crate::db_interfaces::rest::stores::TokenToEncryptedValueMap<
$value_length,
>,
new_values: $crate::db_interfaces::rest::stores::TokenToEncryptedValueMap<
$value_length,
>,
) -> Result<
$crate::db_interfaces::rest::stores::TokenToEncryptedValueMap<$value_length>,
Self::Error,
> {
let modifications = UpsertData::<$value_length>::new(old_values, new_values);
let bytes = modifications.serialize()?;
let res = self.post(CallbackPrefix::Upsert, &bytes).await?;
deserialize_edx_lines(&res)
.map(|v| v.into_iter().collect())
.map_err(Self::Error::from)
}
async fn insert(
&self,
values: $crate::db_interfaces::rest::stores::TokenToEncryptedValueMap<
$value_length,
>,
) -> Result<(), Self::Error> {
let bytes = serialize_edx_lines(&values.into())?;
let _ = self.post(CallbackPrefix::Insert, &bytes).await?;
Ok(())
}
async fn delete(
&self,
tokens: $crate::db_interfaces::rest::stores::Tokens,
) -> Result<(), Self::Error> {
let bytes = serialize_token_set(&tokens.into())?;
let _ = self
.post(
(CallbackPrefix::DeleteEntry as u8 + $table_bit).try_into()?,
&bytes,
)
.await?;
Ok(())
}
}
};
}
#[derive(Debug, PartialEq, Eq)]
pub struct RestParameters {
token: AuthorizationToken,
url: String,
}
impl RestParameters {
#[must_use]
pub fn new(token: AuthorizationToken, url: String) -> Self {
Self { token, url }
}
pub fn from(token: &str, url: String) -> Result<Self, DbInterfaceError> {
let token = AuthorizationToken::from_str(token)?;
Ok(Self { token, url })
}
}
#[derive(Debug)]
pub struct RestEntryBackend(RestParameters);
impl_rest_backend!(RestEntryBackend, ENTRY_LENGTH, "entry_table", 0);
#[derive(Debug)]
pub struct RestChainBackend(RestParameters);
impl_rest_backend!(RestChainBackend, LINK_LENGTH, "entry_table", 1);