diff options
author | metamuffin <metamuffin@disroot.org> | 2024-01-24 18:11:23 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-01-24 18:47:29 +0100 |
commit | 7323709537c6ff14136cd79fb07606cd79391758 (patch) | |
tree | 3d817d449d4c0a821b9b5073c8acf826c6ccfda1 /base | |
parent | cbb2e163abfefd8ed61c41a096d5d6c27b4721b4 (diff) | |
download | jellything-7323709537c6ff14136cd79fb07606cd79391758.tar jellything-7323709537c6ff14136cd79fb07606cd79391758.tar.bz2 jellything-7323709537c6ff14136cd79fb07606cd79391758.tar.zst |
refactor asset system pt. 1
Diffstat (limited to 'base')
-rw-r--r-- | base/Cargo.toml | 1 | ||||
-rw-r--r-- | base/src/assetfed.rs | 71 | ||||
-rw-r--r-- | base/src/cache.rs | 51 | ||||
-rw-r--r-- | base/src/lib.rs | 24 | ||||
-rw-r--r-- | base/src/temp.rs | 30 |
5 files changed, 101 insertions, 76 deletions
diff --git a/base/Cargo.toml b/base/Cargo.toml index 36b93bc..bd1a748 100644 --- a/base/Cargo.toml +++ b/base/Cargo.toml @@ -17,6 +17,7 @@ bincode = "2.0.0-rc.3" rand = "0.8.5" redb = "1.5.0" serde_json = "1.0.111" +aes-gcm-siv = "0.11.1" [features] db_json = [] 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") - } -} |