[MIRROR ONLY] A correct and efficient ATProto blob proxy for secure content delivery.
codeberg.org/Blooym/porxie
1use crate::types::blob_cid::BlobCid;
2use anyhow::{Context, Result};
3use axum::http::HeaderMap;
4use bytes::Bytes;
5use jacquard_common::types::did::Did;
6use moka::{future::Cache as MokaCache, policy::EvictionPolicy};
7use reqwest::Url;
8use std::{cmp, num::NonZeroU64, time::Duration};
9
10// Blob Content Cache
11
12type BlobContentCache = MokaCache<BlobCid, CachedBlobData>;
13
14#[derive(Clone)]
15pub struct CachedBlobData {
16 pub bytes: Bytes,
17 pub headers: HeaderMap,
18}
19
20#[must_use]
21fn build_blob_content_cache(mem_capacity: u64, ttl: Duration) -> BlobContentCache {
22 tracing::debug!(
23 "building blob content cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s",
24 ttl.as_secs()
25 );
26
27 BlobContentCache::builder()
28 .name("blob-content")
29 .weigher(|_key, value| {
30 (value.bytes.len()
31 + value
32 .headers
33 .iter()
34 .map(|(k, v)| k.as_str().len() + v.len() + 32)
35 .sum::<usize>())
36 .try_into()
37 .unwrap_or(u32::MAX)
38 })
39 .eviction_policy(EvictionPolicy::tiny_lfu())
40 .max_capacity(mem_capacity)
41 .time_to_idle(ttl)
42 .build()
43}
44
45// Blob Ownership Cache
46
47type BlobOwnershipCache = MokaCache<(BlobCid, Did<'static>), ()>;
48
49#[must_use]
50fn build_blob_ownership_cache(mem_capacity: u64, ttl: Duration) -> BlobOwnershipCache {
51 tracing::debug!(
52 "building blob ownership cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s",
53 ttl.as_secs()
54 );
55
56 BlobOwnershipCache::builder()
57 .name("blob-ownership")
58 .weigher(|key, _value| {
59 (key.0.encoded_len() + key.1.len())
60 .try_into()
61 .unwrap_or(u32::MAX)
62 })
63 .eviction_policy(EvictionPolicy::tiny_lfu())
64 .max_capacity(mem_capacity)
65 .time_to_live(ttl)
66 .support_invalidation_closures()
67 .build()
68}
69
70// Policy Cache
71
72type BlobPolicyCache = MokaCache<(Did<'static>, BlobCid), CachedBlobPolicy>;
73
74#[derive(Debug, Copy, Clone)]
75pub struct CachedBlobPolicy {
76 pub can_serve: bool,
77}
78
79#[must_use]
80pub fn build_blob_policy_cache(mem_capacity: u64, ttl: Duration) -> BlobPolicyCache {
81 tracing::debug!(
82 "building blob policy cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s",
83 ttl.as_secs()
84 );
85
86 BlobPolicyCache::builder()
87 .name("blob-policy")
88 .weigher(|key, _value| {
89 (key.0.len() + key.1.encoded_len())
90 .try_into()
91 .unwrap_or(u32::MAX)
92 })
93 .eviction_policy(EvictionPolicy::tiny_lfu())
94 .max_capacity(mem_capacity)
95 .time_to_live(ttl)
96 .support_invalidation_closures()
97 .build()
98}
99
100// Identity DID <-> PDS Cache
101
102type IdentityCache = MokaCache<Did<'static>, Url>;
103
104#[must_use]
105pub fn build_identity_cache(mem_capacity: u64, ttl: Duration) -> IdentityCache {
106 tracing::debug!(
107 "building identity cache with a mem_capacity of {mem_capacity} bytes and a ttl of {}s",
108 ttl.as_secs()
109 );
110
111 IdentityCache::builder()
112 .name("identity")
113 .weigher(|key, value| {
114 (key.len() + value.as_str().len())
115 .try_into()
116 .unwrap_or(u32::MAX)
117 })
118 .eviction_policy(EvictionPolicy::tiny_lfu())
119 .max_capacity(mem_capacity)
120 .time_to_live(ttl)
121 .support_invalidation_closures()
122 .build()
123}
124
125// Builder
126
127pub struct Caches {
128 pub blob_content: BlobContentCache,
129 pub blob_ownership: BlobOwnershipCache,
130 pub blob_policy: BlobPolicyCache,
131 pub identity: IdentityCache,
132}
133
134pub struct CacheBuildOptions {
135 pub memory_capacity: NonZeroU64,
136 pub blob_content_ttl: Duration,
137 pub blob_ownership_ttl: Duration,
138 pub blob_policy_ttl: Duration,
139 pub identity_cache_ttl: Duration,
140}
141
142pub fn build_caches(options: &CacheBuildOptions) -> Result<Caches> {
143 let sizes = {
144 struct CacheSizes {
145 pub blob: u64,
146 pub ownership: u64,
147 pub policy: u64,
148 pub identity: u64,
149 }
150 let policy = cmp::min(
151 (options.memory_capacity.get() as f64 * 0.06) as u64,
152 48_000_000,
153 ); // 6% up to 48mb max.
154 let ownership = cmp::min(
155 (options.memory_capacity.get() as f64 * 0.06) as u64,
156 48_000_000,
157 ); // 6% up to 48mb max.
158 let identity = cmp::min(
159 (options.memory_capacity.get() as f64 * 0.06) as u64,
160 48_000_000,
161 ); // 6% up to 48mb max.
162 CacheSizes {
163 policy,
164 ownership,
165 identity,
166 blob: options
167 .memory_capacity
168 .get()
169 .checked_sub(policy)
170 .and_then(|r| r.checked_sub(ownership))
171 .and_then(|r| r.checked_sub(identity))
172 .context("cache size allocation underflow")?,
173 }
174 };
175
176 Ok(Caches {
177 blob_content: build_blob_content_cache(sizes.blob, options.blob_content_ttl),
178 blob_ownership: build_blob_ownership_cache(sizes.ownership, options.blob_ownership_ttl),
179 blob_policy: build_blob_policy_cache(sizes.policy, options.blob_policy_ttl),
180 identity: build_identity_cache(sizes.identity, options.identity_cache_ttl),
181 })
182}