aboutsummaryrefslogtreecommitdiff
path: root/base/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-01-24 18:11:23 +0100
committermetamuffin <metamuffin@disroot.org>2024-01-24 18:47:29 +0100
commit7323709537c6ff14136cd79fb07606cd79391758 (patch)
tree3d817d449d4c0a821b9b5073c8acf826c6ccfda1 /base/src
parentcbb2e163abfefd8ed61c41a096d5d6c27b4721b4 (diff)
downloadjellything-7323709537c6ff14136cd79fb07606cd79391758.tar
jellything-7323709537c6ff14136cd79fb07606cd79391758.tar.bz2
jellything-7323709537c6ff14136cd79fb07606cd79391758.tar.zst
refactor asset system pt. 1
Diffstat (limited to 'base/src')
-rw-r--r--base/src/assetfed.rs71
-rw-r--r--base/src/cache.rs51
-rw-r--r--base/src/lib.rs24
-rw-r--r--base/src/temp.rs30
4 files changed, 100 insertions, 76 deletions
diff --git a/base/src/assetfed.rs b/base/src/assetfed.rs
new file mode 100644
index 0000000..800e458
--- /dev/null
+++ b/base/src/assetfed.rs
@@ -0,0 +1,71 @@
+use aes_gcm_siv::{
+ aead::{generic_array::GenericArray, Aead},
+ Aes256GcmSiv, KeyInit,
+};
+use anyhow::{anyhow, bail, Context};
+use base64::Engine;
+use bincode::{Decode, Encode};
+use jellycommon::Asset;
+use log::warn;
+use std::{path::PathBuf, sync::LazyLock};
+
+use crate::{cache::CachePath, SECRETS};
+
+const VERSION: u32 = 2;
+
+static ASSET_KEY: LazyLock<Aes256GcmSiv> = LazyLock::new(|| {
+ if let Some(sk) = &SECRETS.session_key {
+ let r = base64::engine::general_purpose::STANDARD
+ .decode(sk)
+ .expect("key invalid; should be valid base64");
+ aes_gcm_siv::Aes256GcmSiv::new_from_slice(&r)
+ .expect("key has the wrong length; should be 32 bytes")
+ } else {
+ warn!("session_key not configured; generating a random one.");
+ aes_gcm_siv::Aes256GcmSiv::new_from_slice(&[(); 32].map(|_| rand::random())).unwrap()
+ }
+});
+
+#[derive(Debug, Encode, Decode)]
+pub enum AssetInner {
+ Federated { host: String, asset: Vec<u8> },
+ Cache(CachePath),
+ Assets(PathBuf),
+}
+
+impl AssetInner {
+ pub fn ser(&self) -> Asset {
+ let mut plaintext = Vec::new();
+ plaintext.extend(u32::to_le_bytes(VERSION));
+ plaintext.extend(bincode::encode_to_vec(&self, bincode::config::standard()).unwrap());
+
+ while plaintext.len() % 16 == 0 {
+ plaintext.push(0);
+ }
+
+ let nonce = [(); 12].map(|_| rand::random());
+ let mut ciphertext = ASSET_KEY
+ .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
+ .unwrap();
+ ciphertext.extend(nonce);
+
+ Asset(base64::engine::general_purpose::URL_SAFE.encode(&ciphertext))
+ }
+ pub fn deser(s: &str) -> anyhow::Result<Self> {
+ let ciphertext = base64::engine::general_purpose::URL_SAFE.decode(&s)?;
+ let (ciphertext, nonce) = ciphertext.split_at(ciphertext.len() - 12);
+ let plaintext = ASSET_KEY
+ .decrypt(nonce.into(), ciphertext)
+ .map_err(|_| anyhow!("asset token decrypt failed"))?;
+
+ let version = u32::from_le_bytes(plaintext[0..4].try_into().unwrap());
+ if version != VERSION {
+ bail!("asset token version mismatch");
+ }
+
+ let (data, _): (AssetInner, _) =
+ bincode::decode_from_slice(&plaintext[4..], bincode::config::standard())
+ .context("asset token has invalid format")?;
+ Ok(data)
+ }
+}
diff --git a/base/src/cache.rs b/base/src/cache.rs
index d1c3e4d..3c4ae81 100644
--- a/base/src/cache.rs
+++ b/base/src/cache.rs
@@ -3,11 +3,10 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
-use crate::{AssetLocationExt, CONF};
+use crate::CONF;
use anyhow::{anyhow, Context};
use base64::Engine;
use bincode::{Decode, Encode};
-use jellycommon::AssetLocation;
use log::{info, warn};
use rand::random;
use std::{
@@ -17,7 +16,6 @@ use std::{
future::Future,
io::Seek,
path::PathBuf,
- str::FromStr,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, LazyLock, RwLock,
@@ -29,7 +27,15 @@ use tokio::{
sync::Mutex,
};
-pub fn cache_location(seed: &[&str]) -> (usize, AssetLocation) {
+#[derive(Debug, Encode, Decode)]
+pub struct CachePath(pub PathBuf);
+impl CachePath {
+ pub fn abs(&self) -> PathBuf {
+ CONF.cache_path.join(&self.0)
+ }
+}
+
+pub fn cache_location(seed: &[&str]) -> (usize, CachePath) {
use sha2::Digest;
let mut d = sha2::Sha512::new();
for s in seed {
@@ -41,7 +47,7 @@ pub fn cache_location(seed: &[&str]) -> (usize, AssetLocation) {
let fname = base64::engine::general_purpose::URL_SAFE.encode(d);
let fname = &fname[..22];
let fname = format!("{}-{}", seed[0], fname); // about 128 bits
- (n, AssetLocation::Cache(fname.into()))
+ (n, CachePath(fname.into()))
}
const CACHE_GENERATION_BUCKET_COUNT: usize = 1024;
@@ -51,7 +57,7 @@ pub static CACHE_GENERATION_LOCKS: LazyLock<[Mutex<()>; CACHE_GENERATION_BUCKET_
pub async fn async_cache_file<Fun, Fut>(
seed: &[&str],
generate: Fun,
-) -> Result<AssetLocation, anyhow::Error>
+) -> Result<CachePath, anyhow::Error>
where
Fun: FnOnce(tokio::fs::File) -> Fut,
Fut: Future<Output = Result<(), anyhow::Error>>,
@@ -61,13 +67,11 @@ where
let _guard = CACHE_GENERATION_LOCKS[bucket % CACHE_GENERATION_BUCKET_COUNT]
.lock()
.await;
- let exists = tokio::fs::try_exists(location.path())
+ let exists = tokio::fs::try_exists(&location.abs())
.await
.context("unable to test for cache file existance")?;
if !exists {
- let temp_path =
- AssetLocation::Cache(PathBuf::from_str(&format!("temp-{:x}", random::<u128>()))?)
- .path();
+ let temp_path = CONF.cache_path.join(format!("temp-{:x}", random::<u128>()));
let f = tokio::fs::File::create(&temp_path)
.await
.context("creating new cache file")?;
@@ -79,7 +83,7 @@ where
return Err(e);
}
}
- tokio::fs::rename(temp_path, location.path())
+ tokio::fs::rename(temp_path, &location.abs())
.await
.context("rename cache")?;
}
@@ -87,18 +91,15 @@ where
Ok(location)
}
-pub fn cache_file<Fun>(seed: &[&str], mut generate: Fun) -> Result<AssetLocation, anyhow::Error>
+pub fn cache_file<Fun>(seed: &[&str], mut generate: Fun) -> Result<CachePath, anyhow::Error>
where
Fun: FnMut(std::fs::File) -> Result<(), anyhow::Error>,
{
let (bucket, location) = cache_location(seed);
// we need a lock even if it exists since somebody might be still in the process of writing.
let _guard = CACHE_GENERATION_LOCKS[bucket % CACHE_GENERATION_BUCKET_COUNT].blocking_lock();
- let exists = location.path().exists();
- if !exists {
- let temp_path =
- AssetLocation::Cache(PathBuf::from_str(&format!("temp-{:x}", random::<u128>()))?)
- .path();
+ if !location.abs().exists() {
+ let temp_path = CONF.cache_path.join(format!("temp-{:x}", random::<u128>()));
let f = std::fs::File::create(&temp_path).context("creating new cache file")?;
match generate(f) {
Ok(()) => (),
@@ -108,7 +109,7 @@ where
return Err(e);
}
}
- rename(temp_path, location.path()).context("rename cache")?;
+ rename(temp_path, &location.abs()).context("rename cache")?;
}
drop(_guard);
Ok(location)
@@ -119,7 +120,7 @@ pub struct InMemoryCacheEntry {
last_access: Instant,
object: Arc<dyn Any + Send + Sync + 'static>,
}
-pub static CACHE_IN_MEMORY_OBJECTS: LazyLock<RwLock<HashMap<AssetLocation, InMemoryCacheEntry>>> =
+pub static CACHE_IN_MEMORY_OBJECTS: LazyLock<RwLock<HashMap<PathBuf, InMemoryCacheEntry>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub static CACHE_IN_MEMORY_SIZE: AtomicUsize = AtomicUsize::new(0);
@@ -131,7 +132,7 @@ where
let (_, location) = cache_location(seed);
{
let mut g = CACHE_IN_MEMORY_OBJECTS.write().unwrap();
- if let Some(entry) = g.get_mut(&location) {
+ if let Some(entry) = g.get_mut(&location.abs()) {
entry.last_access = Instant::now();
let object = entry
.object
@@ -148,7 +149,7 @@ where
.context("encoding cache object")?;
Ok(())
})?;
- let mut file = std::fs::File::open(location.path())?;
+ let mut file = std::fs::File::open(&location.abs())?;
let object = bincode::decode_from_std_read::<T, _, _>(&mut file, bincode::config::standard())
.context("decoding cache object")?;
let object = Arc::new(object);
@@ -157,7 +158,7 @@ where
{
let mut g = CACHE_IN_MEMORY_OBJECTS.write().unwrap();
g.insert(
- location,
+ location.abs(),
InMemoryCacheEntry {
size,
last_access: Instant::now(),
@@ -184,7 +185,7 @@ where
let (_, location) = cache_location(seed);
{
let mut g = CACHE_IN_MEMORY_OBJECTS.write().unwrap();
- if let Some(entry) = g.get_mut(&location) {
+ if let Some(entry) = g.get_mut(&location.abs()) {
entry.last_access = Instant::now();
let object = entry
.object
@@ -205,7 +206,7 @@ where
Ok(())
})
.await?;
- let mut file = tokio::fs::File::open(location.path()).await?;
+ let mut file = tokio::fs::File::open(&location.abs()).await?;
let mut data = Vec::new();
file.read_to_end(&mut data)
.await
@@ -218,7 +219,7 @@ where
{
let mut g = CACHE_IN_MEMORY_OBJECTS.write().unwrap();
g.insert(
- location,
+ location.abs(),
InMemoryCacheEntry {
size,
last_access: Instant::now(),
diff --git a/base/src/lib.rs b/base/src/lib.rs
index 015b62b..90ac27b 100644
--- a/base/src/lib.rs
+++ b/base/src/lib.rs
@@ -8,13 +8,10 @@ pub mod cache;
pub mod database;
pub mod federation;
pub mod permission;
-pub mod temp;
+pub mod assetfed;
-use jellycommon::{
- config::{GlobalConfig, SecretsConfig},
- AssetLocation,
-};
-use std::{fs::File, path::PathBuf, sync::LazyLock};
+use jellycommon::config::{GlobalConfig, SecretsConfig};
+use std::{fs::File, sync::LazyLock};
pub static CONF: LazyLock<GlobalConfig> = LazyLock::new(|| {
serde_yaml::from_reader(
@@ -39,18 +36,3 @@ pub static SECRETS: LazyLock<SecretsConfig> = LazyLock::new(|| {
serde_yaml::from_reader(File::open(&CONF.secrets_path).expect("secrets file missing"))
.expect("secrets config invalid")
});
-
-pub trait AssetLocationExt {
- fn path(&self) -> PathBuf;
-}
-impl AssetLocationExt for AssetLocation {
- fn path(&self) -> PathBuf {
- match self {
- AssetLocation::Assets(p) => CONF.asset_path.join(p),
- AssetLocation::Cache(p) => CONF.cache_path.join(p),
- AssetLocation::Library(p) => CONF.library_path.join(p),
- AssetLocation::Temp(p) => CONF.temp_path.join(p),
- AssetLocation::Media(p) => CONF.media_path.join(p),
- }
- }
-}
diff --git a/base/src/temp.rs b/base/src/temp.rs
deleted file mode 100644
index ee44004..0000000
--- a/base/src/temp.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2023 metamuffin <metamuffin.org>
-*/
-use crate::AssetLocationExt;
-use anyhow::Context;
-use jellycommon::AssetLocation;
-use std::{fs::File, sync::atomic::AtomicUsize};
-
-static TEMP_COUNTER: AtomicUsize = AtomicUsize::new(0);
-
-pub struct TempFile(pub AssetLocation);
-
-impl TempFile {
- pub fn new(generate: impl FnOnce(File) -> anyhow::Result<()>) -> anyhow::Result<Self> {
- let i = TEMP_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
- let loc = AssetLocation::Temp(format!("jellything-temp-{i}").into());
-
- let file = File::create(loc.path()).context("creating temp file")?;
- generate(file).context("tempfile generation")?;
-
- Ok(Self(loc))
- }
-}
-impl Drop for TempFile {
- fn drop(&mut self) {
- std::fs::remove_file(self.0.path()).expect("cant unlink tempfile")
- }
-}