use chacha::{ChaCha, KeyStream};
use std::io;
use std::time::Duration;
use async_trait::async_trait;
use bytes::{Bytes, BytesMut};
use futures::{stream::StreamExt, Stream};
use super::{LFSObject, Storage, StorageKey, StorageStream};
pub struct Backend<S> {
storage: S,
key: [u8; 32],
}
impl<S> Backend<S> {
pub fn new(key: [u8; 32], storage: S) -> Self {
Backend { key, storage }
}
}
fn xor_stream<S>(
mut chacha: ChaCha,
stream: S,
) -> impl Stream<Item = Result<Bytes, io::Error>>
where
S: Stream<Item = Result<Bytes, io::Error>>,
{
stream.map(move |bytes| {
let mut bytes = BytesMut::from(bytes?.as_ref());
chacha.xor_read(bytes.as_mut()).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
"reached end of xchacha20 keystream",
)
})?;
Ok(bytes.freeze())
})
}
#[async_trait]
impl<S> Storage for Backend<S>
where
S: Storage + Send + Sync + 'static,
S::Error: 'static,
{
type Error = S::Error;
async fn get(
&self,
key: &StorageKey,
) -> Result<Option<LFSObject>, Self::Error> {
let mut nonce: [u8; 24] = [0; 24];
nonce.copy_from_slice(&key.oid().bytes()[0..24]);
let chacha = ChaCha::new_xchacha20(&self.key, &nonce);
Ok(self.storage.get(key).await?.map(move |obj| {
let (len, stream) = obj.into_parts();
LFSObject::new(len, Box::pin(xor_stream(chacha, stream)))
}))
}
async fn put(
&self,
key: StorageKey,
value: LFSObject,
) -> Result<(), Self::Error> {
let mut nonce: [u8; 24] = [0; 24];
nonce.copy_from_slice(&key.oid().bytes()[0..24]);
let chacha = ChaCha::new_xchacha20(&self.key, &nonce);
let (len, stream) = value.into_parts();
let stream = xor_stream(chacha, stream);
self.storage
.put(key, LFSObject::new(len, Box::pin(stream)))
.await
}
async fn size(&self, key: &StorageKey) -> Result<Option<u64>, Self::Error> {
self.storage.size(key).await
}
async fn delete(&self, key: &StorageKey) -> Result<(), Self::Error> {
self.storage.delete(key).await
}
fn list(&self) -> StorageStream<(StorageKey, u64), Self::Error> {
self.storage.list()
}
async fn total_size(&self) -> Option<u64> {
self.storage.total_size().await
}
async fn max_size(&self) -> Option<u64> {
self.storage.max_size().await
}
fn public_url(&self, key: &StorageKey) -> Option<String> {
self.storage.public_url(key)
}
async fn upload_url(
&self,
key: &StorageKey,
expires_in: Duration,
) -> Option<String> {
self.storage.upload_url(key, expires_in).await
}
}