/* 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) 2025 metamuffin */ use crate::{CACHE_GENERATION_BUCKET_COUNT, CONF}; use anyhow::bail; use base64::{Engine, prelude::BASE64_URL_SAFE}; use sha2::Sha256; use std::{ fmt::Display, hash::{Hash, Hasher}, str::FromStr, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct CacheKey(pub [u8; 32]); impl CacheKey { pub fn new(ty: CacheContentType, seed: impl Hash) -> Self { use sha2::Digest; struct ShaHasher(Sha256); impl Hasher for ShaHasher { fn finish(&self) -> u64 { unreachable!() } fn write(&mut self, bytes: &[u8]) { self.0.update(bytes); } } let mut d = ShaHasher(sha2::Sha256::new()); d.0.update(CONF.secret.as_bytes()); seed.hash(&mut d); let d = d.0.finalize(); let mut key: [u8; 32] = d.as_slice().try_into().unwrap(); key[0] = ty as u8; Self(key) } pub fn new_json(seed: impl Hash) -> Self { Self::new(CacheContentType::Json, seed) } pub fn new_image(seed: impl Hash) -> Self { Self::new(CacheContentType::Image, seed) } pub fn content_type(&self) -> CacheContentType { match self.0[0] { 1 => CacheContentType::Image, 2 => CacheContentType::Json, _ => CacheContentType::Unknown, } } pub(super) fn bucket(&self) -> usize { (self.0[1] as usize | ((self.0[2] as usize) << 8) | ((self.0[3] as usize) << 16) | ((self.0[4] as usize) << 24)) % CACHE_GENERATION_BUCKET_COUNT } } impl Display for CacheKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&BASE64_URL_SAFE.encode(self.0)) } } impl FromStr for CacheKey { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let mut out = [0; 32]; let size = BASE64_URL_SAFE.decode_slice(s, &mut out)?; if size != out.len() { bail!("cache key parse invalid size") } Ok(Self(out)) } } #[repr(u8)] pub enum CacheContentType { Unknown = 0, Image = 1, Json = 2, }