use bzip2_rs::DecoderReader;
use std::{collections::HashMap, io::Read};
use crate::error::Error;
use base64::{engine::general_purpose, Engine as _};
use reqwest::multipart::{Form, Part};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use spiral_rs::{
client::Client,
key_value::{extract_result_impl, row_from_key, varint_decode},
params::Params,
util::params_from_json_obj,
};
pub(crate) async fn http_get_string(url: &str, api_key: &str) -> Result<String, Error> {
let req = reqwest::Client::new().get(url).header("x-api-key", api_key);
let res = req.send().await?.error_for_status()?.text().await?;
Ok(res)
}
pub(crate) async fn http_post_bytes(
url: &str,
api_key: &str,
data: Vec<u8>,
) -> Result<Vec<u8>, Error> {
let req = reqwest::Client::new()
.post(url)
.body(data)
.header("Content-Type", "application/octet-stream")
.header("x-api-key", api_key);
let res = req.send().await?.error_for_status()?;
let resp_body = res.bytes().await?;
Ok(resp_body.to_vec())
}
pub(crate) async fn http_post_string(
url: &str,
api_key: &str,
data: String,
) -> Result<String, Error> {
let req = reqwest::Client::new()
.post(url)
.body(data)
.header("x-api-key", api_key);
let res = req.send().await?.error_for_status()?.text().await?;
Ok(res)
}
pub(crate) async fn http_post_form_data(
url: &str,
api_key: &str,
data: Vec<u8>,
fields: HashMap<String, String>,
) -> Result<Vec<u8>, Error> {
let mut form_data = Form::new();
for (key, value) in fields {
form_data = form_data.text(key, value);
}
form_data = form_data.part("file", Part::bytes(data));
let req = reqwest::Client::new()
.post(url)
.multipart(form_data)
.header("x-api-key", api_key);
let res = req.send().await?.error_for_status()?;
let resp_body = res.bytes().await?;
Ok(resp_body.to_vec())
}
fn decompress(data: &[u8]) -> Result<Vec<u8>, Error> {
let mut decoder = DecoderReader::new(data);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)?;
Ok(decompressed)
}
fn serialize_chunks(data: &[Vec<u8>]) -> Vec<u8> {
let mut serialized = Vec::new();
serialized.extend(u64::to_le_bytes(data.len() as u64).to_vec());
for chunk in data {
serialized.extend(u64::to_le_bytes(chunk.len() as u64).to_vec());
serialized.extend(chunk);
}
serialized
}
fn deserialize_chunks(data: &[u8]) -> Vec<Vec<u8>> {
let mut chunks = Vec::new();
let mut offset = 0;
let num_chunks = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
offset += 8;
for _ in 0..num_chunks {
let chunk_len = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
offset += 8;
chunks.push(data[offset..offset + chunk_len as usize].to_vec());
offset += chunk_len as usize;
}
chunks
}
fn split_metadata(data: &[u8]) -> (&[u8], &[u8]) {
let (value, bytes_used) = varint_decode(data);
let metadata_len = value as usize;
if metadata_len == 0 {
return (&[], data);
}
let metadata = &data[bytes_used..bytes_used + metadata_len];
let data = &data[bytes_used + metadata_len..];
(metadata, data)
}
fn is_all_zeros(decrypted: &[u8]) -> bool {
decrypted.iter().all(|&x| x == 0)
}
pub(crate) async fn get_meta(url: &str, api_key: &str) -> Result<String, Error> {
http_get_string(&format!("{}/meta", url), api_key).await
}
fn is_blyss_url(url: &str) -> bool {
url.contains("blyss.dev/")
}
#[derive(Serialize, Deserialize)]
struct PrelimSetupBody {
length: usize,
}
async fn perform_setup(url: &str, api_key: &str, setup_data: Vec<u8>) -> Result<String, Error> {
if !is_blyss_url(url) {
let setup_resp = http_post_bytes(&format!("{}/setup", url), api_key, setup_data).await?;
let setup_resp_str = String::from_utf8(setup_resp)?;
let uuid = serde_json::from_str::<Value>(&setup_resp_str)?
.get("uuid")
.ok_or(Error::Unknown)?
.as_str()
.ok_or(Error::Unknown)?
.to_string();
return Ok(uuid);
}
let prelim_setup_body = serde_json::to_string(&PrelimSetupBody {
length: setup_data.len(),
})?;
let setup_resp =
http_post_string(&format!("{}/setup", url), api_key, prelim_setup_body).await?;
let setup_resp_value: Value = serde_json::from_str(&setup_resp)?;
let fields: HashMap<String, String> = serde_json::from_value(
setup_resp_value
.get("fields")
.ok_or(Error::Unknown)?
.clone(),
)?;
let s3_url: String =
serde_json::from_value(setup_resp_value.get("url").ok_or(Error::Unknown)?.clone())?;
http_post_form_data(&s3_url, api_key, setup_data, fields).await?;
let uuid = setup_resp_value
.get("uuid")
.ok_or(Error::Unknown)?
.as_str()
.ok_or(Error::Unknown)?
.to_owned();
Ok(uuid)
}
async fn private_read<'a>(
client: &Client<'a>,
params: &Params,
uuid: &str,
url: &str,
api_key: &str,
keys: &[String],
) -> Result<Vec<Vec<u8>>, Error> {
let queries: Vec<_> = keys
.iter()
.map(|key| {
let idx_target = row_from_key(¶ms, key);
let query = client.generate_query(idx_target);
let query_data = query.serialize();
let uuid_and_query_data: Vec<_> = (uuid.as_bytes().to_vec().into_iter())
.chain(query_data)
.collect();
uuid_and_query_data
})
.collect();
let full_query_data = serialize_chunks(&queries);
let resp_data_b64 =
http_post_bytes(&format!("{}/private-read", url), api_key, full_query_data).await?;
let resp_data = general_purpose::STANDARD.decode(resp_data_b64)?;
let resp_chunks = deserialize_chunks(&resp_data);
let mut results = Vec::new();
for (i, chunk) in resp_chunks.iter().enumerate() {
let decrypted = client.decode_response(&chunk);
if is_all_zeros(&decrypted) {
results.push(vec![]);
continue;
}
let decompressed = decompress(&decrypted)?;
let result = extract_result_impl(&keys[i], &decompressed);
if let Ok(result) = result {
let (_metadata, data) = split_metadata(&result);
results.push(data.to_vec());
} else {
results.push(vec![]);
}
}
Ok(results)
}
pub struct ApiClient {
pub url: String,
api_key: String,
params: &'static Params,
client: Client<'static>,
uuid: Option<String>,
}
impl ApiClient {
pub async fn new(url: &str, api_key: &str) -> Result<Self, Error> {
let metadata = get_meta(url, api_key).await?;
let params_value = serde_json::from_str::<Value>(&metadata)?
.get("pir_scheme")
.ok_or(Error::Unknown)?
.clone();
let params = params_from_json_obj(¶ms_value);
let boxed_params = Box::leak(Box::new(params));
Ok(Self {
url: url.to_string(),
api_key: api_key.to_string(),
params: boxed_params,
client: Client::init(boxed_params),
uuid: None,
})
}
fn has_set_up(&self) -> bool {
self.uuid.is_some()
}
pub async fn setup(&mut self) -> Result<(), Error> {
let setup = self.client.generate_keys();
let setup_data = setup.serialize();
let uuid = perform_setup(&self.url, &self.api_key, setup_data).await?;
self.uuid = Some(uuid);
Ok(())
}
pub async fn private_read(&self, keys: &[String]) -> Result<Vec<Vec<u8>>, Error> {
if !self.has_set_up() {
return Err(Error::NeedSetup);
}
private_read(
&self.client,
&self.params,
self.uuid.as_ref().unwrap(),
&self.url,
&self.api_key,
keys,
)
.await
}
}