Skip to main content

pingora_cache/
meta.rs

1// Copyright 2026 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Metadata for caching
16
17pub use http::Extensions;
18use log::warn;
19use once_cell::sync::{Lazy, OnceCell};
20use pingora_error::{Error, ErrorType::*, OrErr, Result};
21use pingora_header_serde::HeaderSerde;
22use pingora_http::{HMap, ResponseHeader};
23use serde::{Deserialize, Serialize};
24use std::borrow::Cow;
25use std::time::{Duration, SystemTime};
26
27use crate::key::HashBinary;
28
29pub(crate) type InternalMeta = internal_meta::InternalMetaLatest;
30mod internal_meta {
31    use super::*;
32
33    pub(crate) type InternalMetaLatest = InternalMetaV2;
34
35    #[derive(Debug, Deserialize, Serialize, Clone)]
36    pub(crate) struct InternalMetaV0 {
37        pub(crate) fresh_until: SystemTime,
38        pub(crate) created: SystemTime,
39        pub(crate) stale_while_revalidate_sec: u32,
40        pub(crate) stale_if_error_sec: u32,
41        // Do not add more field
42    }
43
44    impl InternalMetaV0 {
45        #[allow(dead_code)]
46        fn serialize(&self) -> Result<Vec<u8>> {
47            rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
48        }
49
50        fn deserialize(buf: &[u8]) -> Result<Self> {
51            rmp_serde::decode::from_slice(buf)
52                .or_err(InternalError, "failed to decode cache meta v0")
53        }
54    }
55
56    #[derive(Debug, Deserialize, Serialize, Clone)]
57    pub(crate) struct InternalMetaV1 {
58        pub(crate) version: u8,
59        pub(crate) fresh_until: SystemTime,
60        pub(crate) created: SystemTime,
61        pub(crate) stale_while_revalidate_sec: u32,
62        pub(crate) stale_if_error_sec: u32,
63        // Do not add more field
64    }
65
66    impl InternalMetaV1 {
67        #[allow(dead_code)]
68        pub const VERSION: u8 = 1;
69
70        #[allow(dead_code)]
71        pub fn serialize(&self) -> Result<Vec<u8>> {
72            assert_eq!(self.version, 1);
73            rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
74        }
75
76        fn deserialize(buf: &[u8]) -> Result<Self> {
77            rmp_serde::decode::from_slice(buf)
78                .or_err(InternalError, "failed to decode cache meta v1")
79        }
80    }
81
82    #[derive(Debug, Deserialize, Serialize, Clone)]
83    pub(crate) struct InternalMetaV2 {
84        pub(crate) version: u8,
85        pub(crate) fresh_until: SystemTime,
86        pub(crate) created: SystemTime,
87        pub(crate) updated: SystemTime,
88        pub(crate) stale_while_revalidate_sec: u32,
89        pub(crate) stale_if_error_sec: u32,
90        // Only the extended field to be added below. One field at a time.
91        // 1. serde default in order to accept an older version schema without the field existing
92        // 2. serde skip_serializing_if in order for software with only an older version of this
93        //    schema to decode it
94        // After full releases, remove `skip_serializing_if` so that we can add the next extended field.
95        #[serde(default)]
96        pub(crate) variance: Option<HashBinary>,
97        #[serde(default)]
98        pub(crate) epoch_override: Option<SystemTime>,
99        // Cache-object provenance timestamp for hit filtering decisions that need a
100        // stable reference point across metadata rewrites or refreshes.
101        #[serde(default)]
102        #[serde(skip_serializing_if = "Option::is_none")]
103        pub(crate) provenance: Option<SystemTime>,
104    }
105
106    impl Default for InternalMetaV2 {
107        fn default() -> Self {
108            let epoch = SystemTime::UNIX_EPOCH;
109            InternalMetaV2 {
110                version: InternalMetaV2::VERSION,
111                fresh_until: epoch,
112                created: epoch,
113                updated: epoch,
114                stale_while_revalidate_sec: 0,
115                stale_if_error_sec: 0,
116                variance: None,
117                epoch_override: None,
118                provenance: None,
119            }
120        }
121    }
122
123    impl InternalMetaV2 {
124        pub const VERSION: u8 = 2;
125
126        pub fn serialize(&self) -> Result<Vec<u8>> {
127            assert_eq!(self.version, Self::VERSION);
128            rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
129        }
130
131        fn deserialize(buf: &[u8]) -> Result<Self> {
132            rmp_serde::decode::from_slice(buf)
133                .or_err(InternalError, "failed to decode cache meta v2")
134        }
135    }
136
137    impl From<InternalMetaV0> for InternalMetaV2 {
138        fn from(v0: InternalMetaV0) -> Self {
139            InternalMetaV2 {
140                version: InternalMetaV2::VERSION,
141                fresh_until: v0.fresh_until,
142                created: v0.created,
143                updated: v0.created,
144                stale_while_revalidate_sec: v0.stale_while_revalidate_sec,
145                stale_if_error_sec: v0.stale_if_error_sec,
146                ..Default::default()
147            }
148        }
149    }
150
151    impl From<InternalMetaV1> for InternalMetaV2 {
152        fn from(v1: InternalMetaV1) -> Self {
153            InternalMetaV2 {
154                version: InternalMetaV2::VERSION,
155                fresh_until: v1.fresh_until,
156                created: v1.created,
157                updated: v1.created,
158                stale_while_revalidate_sec: v1.stale_while_revalidate_sec,
159                stale_if_error_sec: v1.stale_if_error_sec,
160                ..Default::default()
161            }
162        }
163    }
164
165    // cross version decode
166    pub(crate) fn deserialize(buf: &[u8]) -> Result<InternalMetaLatest> {
167        const MIN_SIZE: usize = 10; // a small number to read the first few bytes
168        if buf.len() < MIN_SIZE {
169            return Error::e_explain(
170                InternalError,
171                format!("Buf too short ({}) to be InternalMeta", buf.len()),
172            );
173        }
174        let preread_buf = &mut &buf[..MIN_SIZE];
175        // the struct is always packed as a fixed size array
176        match rmp::decode::read_array_len(preread_buf)
177            .or_err(InternalError, "failed to decode cache meta array size")?
178        {
179            // v0 has 4 items and no version number
180            4 => Ok(InternalMetaV0::deserialize(buf)?.into()),
181            // other V should have version number encoded
182            _ => {
183                // rmp will encode `version` < 128 into a fixint (one byte),
184                // so we use read_pfix
185                let version = rmp::decode::read_pfix(preread_buf)
186                    .or_err(InternalError, "failed to decode meta version")?;
187                match version {
188                    1 => Ok(InternalMetaV1::deserialize(buf)?.into()),
189                    2 => InternalMetaV2::deserialize(buf),
190                    _ => Error::e_explain(
191                        InternalError,
192                        format!("Unknown InternalMeta version {version}"),
193                    ),
194                }
195            }
196        }
197    }
198
199    #[cfg(test)]
200    mod tests {
201        use super::*;
202
203        #[test]
204        fn test_internal_meta_serde_v0() {
205            let meta = InternalMetaV0 {
206                fresh_until: SystemTime::now(),
207                created: SystemTime::now(),
208                stale_while_revalidate_sec: 0,
209                stale_if_error_sec: 0,
210            };
211            let binary = meta.serialize().unwrap();
212            let meta2 = InternalMetaV0::deserialize(&binary).unwrap();
213            assert_eq!(meta.fresh_until, meta2.fresh_until);
214        }
215
216        #[test]
217        fn test_internal_meta_serde_v1() {
218            let meta = InternalMetaV1 {
219                version: InternalMetaV1::VERSION,
220                fresh_until: SystemTime::now(),
221                created: SystemTime::now(),
222                stale_while_revalidate_sec: 0,
223                stale_if_error_sec: 0,
224            };
225            let binary = meta.serialize().unwrap();
226            let meta2 = InternalMetaV1::deserialize(&binary).unwrap();
227            assert_eq!(meta.fresh_until, meta2.fresh_until);
228        }
229
230        #[test]
231        fn test_internal_meta_serde_v2() {
232            let meta = InternalMetaV2::default();
233            let binary = meta.serialize().unwrap();
234            let meta2 = InternalMetaV2::deserialize(&binary).unwrap();
235            assert_eq!(meta2.version, 2);
236            assert_eq!(meta.fresh_until, meta2.fresh_until);
237            assert_eq!(meta.created, meta2.created);
238            assert_eq!(meta.updated, meta2.updated);
239        }
240
241        #[test]
242        fn test_internal_meta_serde_across_versions() {
243            let meta = InternalMetaV0 {
244                fresh_until: SystemTime::now(),
245                created: SystemTime::now(),
246                stale_while_revalidate_sec: 0,
247                stale_if_error_sec: 0,
248            };
249            let binary = meta.serialize().unwrap();
250            let meta2 = deserialize(&binary).unwrap();
251            assert_eq!(meta2.version, 2);
252            assert_eq!(meta.fresh_until, meta2.fresh_until);
253
254            let meta = InternalMetaV1 {
255                version: 1,
256                fresh_until: SystemTime::now(),
257                created: SystemTime::now(),
258                stale_while_revalidate_sec: 0,
259                stale_if_error_sec: 0,
260            };
261            let binary = meta.serialize().unwrap();
262            let meta2 = deserialize(&binary).unwrap();
263            assert_eq!(meta2.version, 2);
264            assert_eq!(meta.fresh_until, meta2.fresh_until);
265            // `updated` == `created` when upgrading to v2
266            assert_eq!(meta2.created, meta2.updated);
267        }
268
269        // make sure that v2 format is backward compatible
270        // this is the base version of v2 without any extended fields
271        #[derive(Deserialize, Serialize)]
272        struct InternalMetaV2Base {
273            version: u8,
274            fresh_until: SystemTime,
275            created: SystemTime,
276            updated: SystemTime,
277            stale_while_revalidate_sec: u32,
278            stale_if_error_sec: u32,
279        }
280
281        impl InternalMetaV2Base {
282            pub const VERSION: u8 = 2;
283            pub fn serialize(&self) -> Result<Vec<u8>> {
284                assert!(self.version >= Self::VERSION);
285                rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
286            }
287            fn deserialize(buf: &[u8]) -> Result<Self> {
288                rmp_serde::decode::from_slice(buf)
289                    .or_err(InternalError, "failed to decode cache meta v2")
290            }
291        }
292
293        // this is the base version of v2 with variance but without epoch_override
294        #[derive(Deserialize, Serialize)]
295        struct InternalMetaV2BaseWithVariance {
296            version: u8,
297            fresh_until: SystemTime,
298            created: SystemTime,
299            updated: SystemTime,
300            stale_while_revalidate_sec: u32,
301            stale_if_error_sec: u32,
302            #[serde(default)]
303            #[serde(skip_serializing_if = "Option::is_none")]
304            variance: Option<HashBinary>,
305        }
306
307        impl Default for InternalMetaV2BaseWithVariance {
308            fn default() -> Self {
309                let epoch = SystemTime::UNIX_EPOCH;
310                InternalMetaV2BaseWithVariance {
311                    version: InternalMetaV2::VERSION,
312                    fresh_until: epoch,
313                    created: epoch,
314                    updated: epoch,
315                    stale_while_revalidate_sec: 0,
316                    stale_if_error_sec: 0,
317                    variance: None,
318                }
319            }
320        }
321
322        impl InternalMetaV2BaseWithVariance {
323            pub const VERSION: u8 = 2;
324            pub fn serialize(&self) -> Result<Vec<u8>> {
325                assert!(self.version >= Self::VERSION);
326                rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
327            }
328            fn deserialize(buf: &[u8]) -> Result<Self> {
329                rmp_serde::decode::from_slice(buf)
330                    .or_err(InternalError, "failed to decode cache meta v2")
331            }
332        }
333
334        // V2 with variance + epoch_override fixed in the wire layout, but without
335        // provenance. Models the layout produced by reader-prep binaries before
336        // provenance writes are enabled.
337        #[derive(Deserialize, Serialize)]
338        struct InternalMetaV2BeforeProvenance {
339            version: u8,
340            fresh_until: SystemTime,
341            created: SystemTime,
342            updated: SystemTime,
343            stale_while_revalidate_sec: u32,
344            stale_if_error_sec: u32,
345            #[serde(default)]
346            variance: Option<HashBinary>,
347            #[serde(default)]
348            epoch_override: Option<SystemTime>,
349        }
350
351        impl Default for InternalMetaV2BeforeProvenance {
352            fn default() -> Self {
353                let epoch = SystemTime::UNIX_EPOCH;
354                InternalMetaV2BeforeProvenance {
355                    version: InternalMetaV2::VERSION,
356                    fresh_until: epoch,
357                    created: epoch,
358                    updated: epoch,
359                    stale_while_revalidate_sec: 0,
360                    stale_if_error_sec: 0,
361                    variance: None,
362                    epoch_override: None,
363                }
364            }
365        }
366
367        impl InternalMetaV2BeforeProvenance {
368            pub fn serialize(&self) -> Result<Vec<u8>> {
369                rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta")
370            }
371            fn deserialize(buf: &[u8]) -> Result<Self> {
372                rmp_serde::decode::from_slice(buf)
373                    .or_err(InternalError, "failed to decode cache meta v2")
374            }
375        }
376
377        #[test]
378        fn test_internal_meta_serde_v2_extend_fields_variance() {
379            // ext V2 to base v2
380            let meta = InternalMetaV2BaseWithVariance::default();
381            let binary = meta.serialize().unwrap();
382            let meta2 = InternalMetaV2Base::deserialize(&binary).unwrap();
383            assert_eq!(meta2.version, 2);
384            assert_eq!(meta.fresh_until, meta2.fresh_until);
385            assert_eq!(meta.created, meta2.created);
386            assert_eq!(meta.updated, meta2.updated);
387
388            // base V2 to ext v2
389            let now = SystemTime::now();
390            let meta = InternalMetaV2Base {
391                version: InternalMetaV2::VERSION,
392                fresh_until: now,
393                created: now,
394                updated: now,
395                stale_while_revalidate_sec: 0,
396                stale_if_error_sec: 0,
397            };
398            let binary = meta.serialize().unwrap();
399            let meta2 = InternalMetaV2BaseWithVariance::deserialize(&binary).unwrap();
400            assert_eq!(meta2.version, 2);
401            assert_eq!(meta.fresh_until, meta2.fresh_until);
402            assert_eq!(meta.created, meta2.created);
403            assert_eq!(meta.updated, meta2.updated);
404        }
405
406        #[test]
407        fn test_internal_meta_serde_v2_extend_fields_epoch_override() {
408            let now = SystemTime::now();
409
410            // Backward compat: pre-epoch_override encodings (V2BaseWithVariance) must
411            // still decode into the current InternalMetaV2 with epoch_override = None.
412            // This direction is permanent — older on-disk entries written before
413            // epoch_override existed must remain readable.
414            let mut meta = InternalMetaV2BaseWithVariance {
415                version: InternalMetaV2::VERSION,
416                fresh_until: now,
417                created: now,
418                updated: now,
419                stale_while_revalidate_sec: 0,
420                stale_if_error_sec: 0,
421                variance: None,
422            };
423            let binary = meta.serialize().unwrap();
424            let meta2 = InternalMetaV2::deserialize(&binary).unwrap();
425            assert_eq!(meta2.version, 2);
426            assert_eq!(meta.fresh_until, meta2.fresh_until);
427            assert_eq!(meta.created, meta2.created);
428            assert_eq!(meta.updated, meta2.updated);
429            assert!(meta2.variance.is_none());
430            assert!(meta2.epoch_override.is_none());
431
432            // Same direction with variance set.
433            meta.variance = Some(*b"variance_testing");
434            let binary = meta.serialize().unwrap();
435            let meta2 = InternalMetaV2::deserialize(&binary).unwrap();
436            assert_eq!(meta2.version, 2);
437            assert_eq!(meta.fresh_until, meta2.fresh_until);
438            assert_eq!(meta.created, meta2.created);
439            assert_eq!(meta.updated, meta2.updated);
440            assert_eq!(meta.variance, meta2.variance);
441            assert!(meta2.epoch_override.is_none());
442        }
443
444        // Pins the wire-format change made when removing skip_serializing_if from
445        // epoch_override: a Some value and a None value must both round-trip cleanly
446        // and produce arrays of the same length. This is the precondition for appending
447        // a new optional field after epoch_override in a future release.
448        #[test]
449        fn test_internal_meta_serde_v2_epoch_override_always_serialized() {
450            let now = SystemTime::now();
451
452            let meta_none = InternalMetaV2 {
453                fresh_until: now,
454                created: now,
455                updated: now,
456                epoch_override: None,
457                ..Default::default()
458            };
459            let meta_some = InternalMetaV2 {
460                fresh_until: now,
461                created: now,
462                updated: now,
463                epoch_override: Some(now),
464                ..Default::default()
465            };
466
467            let bin_none = meta_none.serialize().unwrap();
468            let bin_some = meta_some.serialize().unwrap();
469
470            // Both encodings must produce the same array length so the next appended
471            // extended field always lands at the same fixed position regardless of
472            // whether epoch_override is set.
473            let len_none =
474                rmp::decode::read_array_len(&mut &bin_none[..]).expect("decode array len");
475            let len_some =
476                rmp::decode::read_array_len(&mut &bin_some[..]).expect("decode array len");
477            assert_eq!(len_none, len_some);
478
479            // Round-trip both values to confirm decoding still works.
480            let decoded_none = InternalMetaV2::deserialize(&bin_none).unwrap();
481            let decoded_some = InternalMetaV2::deserialize(&bin_some).unwrap();
482            assert!(decoded_none.epoch_override.is_none());
483            assert_eq!(decoded_some.epoch_override, Some(now));
484
485            // The same invariant should hold regardless of the preceding variance slot.
486            let meta_none_with_variance = InternalMetaV2 {
487                fresh_until: now,
488                created: now,
489                updated: now,
490                variance: Some(*b"variance_testing"),
491                epoch_override: None,
492                ..Default::default()
493            };
494            let meta_some_with_variance = InternalMetaV2 {
495                fresh_until: now,
496                created: now,
497                updated: now,
498                variance: Some(*b"variance_testing"),
499                epoch_override: Some(now),
500                ..Default::default()
501            };
502            let bin_none = meta_none_with_variance.serialize().unwrap();
503            let bin_some = meta_some_with_variance.serialize().unwrap();
504            let len_none =
505                rmp::decode::read_array_len(&mut &bin_none[..]).expect("decode array len");
506            let len_some =
507                rmp::decode::read_array_len(&mut &bin_some[..]).expect("decode array len");
508            assert_eq!(len_none, len_some);
509        }
510
511        // An on-disk entry written by a pre-provenance binary must decode cleanly
512        // into the current schema with provenance = None. The lookup path falls
513        // back to `created` for those entries.
514        #[test]
515        fn test_internal_meta_serde_v2_extend_fields_provenance_backward_compat() {
516            let now = SystemTime::now();
517            let old = InternalMetaV2BeforeProvenance {
518                fresh_until: now,
519                created: now,
520                updated: now,
521                variance: Some(*b"variance_testing"),
522                epoch_override: Some(now),
523                ..Default::default()
524            };
525            let binary = old.serialize().unwrap();
526
527            let decoded = InternalMetaV2::deserialize(&binary).unwrap();
528            assert_eq!(decoded.version, 2);
529            assert_eq!(decoded.fresh_until, now);
530            assert_eq!(decoded.created, now);
531            assert_eq!(decoded.variance, Some(*b"variance_testing"));
532            assert_eq!(decoded.epoch_override, Some(now));
533            // The new field is absent from the encoded blob, so serde gives us None.
534            assert!(decoded.provenance.is_none());
535        }
536
537        // Forward compat: a current encoding with provenance = None must still be
538        // decodable by a pre-provenance reader (the field is skipped on the wire when
539        // None thanks to skip_serializing_if, keeping the array length equal to the
540        // older schema's length).
541        #[test]
542        fn test_internal_meta_serde_v2_extend_fields_provenance_forward_compat_none() {
543            let now = SystemTime::now();
544            let current = InternalMetaV2 {
545                fresh_until: now,
546                created: now,
547                updated: now,
548                variance: Some(*b"variance_testing"),
549                epoch_override: Some(now),
550                provenance: None,
551                ..Default::default()
552            };
553            let binary = current.serialize().unwrap();
554
555            // Old reader (no provenance field) accepts this encoding because the
556            // array length matches (provenance was skipped during serialization).
557            let decoded = InternalMetaV2BeforeProvenance::deserialize(&binary).unwrap();
558            assert_eq!(decoded.fresh_until, now);
559            assert_eq!(decoded.created, now);
560            assert_eq!(decoded.variance, Some(*b"variance_testing"));
561            assert_eq!(decoded.epoch_override, Some(now));
562        }
563
564        // Entries written with provenance = Some(...) require a reader that supports the
565        // provenance field. The previous test pins the compatible None encoding.
566        #[test]
567        fn test_internal_meta_serde_v2_extend_fields_provenance_some_needs_field_support() {
568            let now = SystemTime::now();
569            let current = InternalMetaV2 {
570                fresh_until: now,
571                created: now,
572                updated: now,
573                variance: Some(*b"variance_testing"),
574                epoch_override: Some(now),
575                provenance: Some(now),
576                ..Default::default()
577            };
578            let binary = current.serialize().unwrap();
579
580            assert!(InternalMetaV2BeforeProvenance::deserialize(&binary).is_err());
581            let decoded = InternalMetaV2::deserialize(&binary).unwrap();
582            assert_eq!(decoded.provenance, Some(now));
583        }
584
585        // Round-trip a Some(provenance): preservation across encode/decode cycles is
586        // what the cache_vary_lookup tombstone relies on for SWR-refreshed entries.
587        #[test]
588        fn test_internal_meta_serde_v2_provenance_round_trip() {
589            let admission = SystemTime::now();
590            let updated = admission + Duration::from_secs(300);
591            let meta = InternalMetaV2 {
592                fresh_until: updated,
593                created: updated, // simulates an SWR-refreshed entry: created = now
594                updated,
595                provenance: Some(admission), // ... but provenance is the ORIGINAL admission
596                ..Default::default()
597            };
598            let binary = meta.serialize().unwrap();
599            let decoded = InternalMetaV2::deserialize(&binary).unwrap();
600            assert_eq!(decoded.created, updated);
601            assert_eq!(decoded.provenance, Some(admission));
602        }
603    }
604}
605
606#[derive(Debug)]
607pub(crate) struct CacheMetaInner {
608    // http header and Internal meta have different ways of serialization, so keep them separated
609    pub(crate) internal: InternalMeta,
610    pub(crate) header: ResponseHeader,
611    /// An opaque type map to hold extra information for communication between cache backends
612    /// and users. This field is **not** guaranteed be persistently stored in the cache backend.
613    pub extensions: Extensions,
614}
615
616/// The cacheable response header and cache metadata
617#[derive(Debug)]
618pub struct CacheMeta(pub(crate) Box<CacheMetaInner>);
619
620impl CacheMeta {
621    /// Create a [CacheMeta] from the given metadata and the response header
622    pub fn new(
623        fresh_until: SystemTime,
624        created: SystemTime,
625        stale_while_revalidate_sec: u32,
626        stale_if_error_sec: u32,
627        header: ResponseHeader,
628    ) -> CacheMeta {
629        CacheMeta(Box::new(CacheMetaInner {
630            internal: InternalMeta {
631                version: InternalMeta::VERSION,
632                fresh_until,
633                created,
634                updated: created, // created == updated for new meta
635                stale_while_revalidate_sec,
636                stale_if_error_sec,
637                provenance: Some(created),
638                ..Default::default()
639            },
640            header,
641            extensions: Extensions::new(),
642        }))
643    }
644
645    /// When the asset was created/admitted to cache
646    pub fn created(&self) -> SystemTime {
647        self.0.internal.created
648    }
649
650    /// The last time the asset was revalidated
651    ///
652    /// This value will be the same as [Self::created()] if no revalidation ever happens
653    pub fn updated(&self) -> SystemTime {
654        self.0.internal.updated
655    }
656
657    /// Cache-object provenance timestamp.
658    ///
659    /// When populated, this is a stable reference point for the cache object's
660    /// lineage that hit filtering code can use instead of relying on the metadata
661    /// record's creation time. The accessor falls back to [`Self::created`] while
662    /// the field is absent.
663    pub fn provenance(&self) -> SystemTime {
664        self.0
665            .internal
666            .provenance
667            .unwrap_or(self.0.internal.created)
668    }
669
670    /// Set the cache-object provenance timestamp.
671    pub(crate) fn set_provenance(&mut self, provenance: SystemTime) {
672        self.0.internal.provenance = Some(provenance);
673    }
674
675    /// Reset provenance to this metadata record's creation time.
676    pub(crate) fn reset_provenance_to_created(&mut self) {
677        self.0.internal.provenance = Some(self.0.internal.created);
678    }
679
680    /// The raw provenance value, exposing whether the field was explicitly set
681    /// (`Some`) vs derived via the [`Self::created`] fallback (`None`).
682    ///
683    /// Test-only inspection helper for compatibility coverage.
684    #[cfg(test)]
685    pub(crate) fn provenance_raw(&self) -> Option<SystemTime> {
686        self.0.internal.provenance
687    }
688
689    /// The reference point for cache age. This represents the "starting point" for `fresh_until`.
690    ///
691    /// This defaults to the `updated` timestamp but is overridden by the `epoch_override` field
692    /// if set.
693    pub fn epoch(&self) -> SystemTime {
694        self.0.internal.epoch_override.unwrap_or(self.updated())
695    }
696
697    /// Get the epoch override for this asset
698    pub fn epoch_override(&self) -> Option<SystemTime> {
699        self.0.internal.epoch_override
700    }
701
702    /// Set the epoch override for this asset
703    ///
704    /// When set, this will be used as the reference point for calculating age and freshness
705    /// instead of the updated time.
706    pub fn set_epoch_override(&mut self, epoch: SystemTime) {
707        self.0.internal.epoch_override = Some(epoch);
708    }
709
710    /// Remove the epoch override for this asset
711    pub fn remove_epoch_override(&mut self) {
712        self.0.internal.epoch_override = None;
713    }
714
715    /// Is the asset still valid
716    pub fn is_fresh(&self, time: SystemTime) -> bool {
717        // NOTE: HTTP cache time resolution is second
718        self.0.internal.fresh_until >= time
719    }
720
721    /// How long (in seconds) the asset should be fresh since its admission/revalidation
722    ///
723    /// This is essentially the max-age value (or its equivalence).
724    /// If an epoch override is set, it will be used as the reference point instead of the updated time.
725    pub fn fresh_sec(&self) -> u64 {
726        // swallow `duration_since` error, assets that are always stale have earlier `fresh_until` than `created`
727        // practically speaking we can always treat these as 0 ttl
728        // XXX: return Error if `fresh_until` is much earlier than expected?
729        let reference = self.epoch();
730        self.0
731            .internal
732            .fresh_until
733            .duration_since(reference)
734            .map_or(0, |duration| duration.as_secs())
735    }
736
737    /// Until when the asset is considered fresh
738    pub fn fresh_until(&self) -> SystemTime {
739        self.0.internal.fresh_until
740    }
741
742    /// How old the asset is since its admission/revalidation
743    ///
744    /// If an epoch override is set, it will be used as the reference point instead of the updated time.
745    pub fn age(&self) -> Duration {
746        let reference = self.epoch();
747        SystemTime::now()
748            .duration_since(reference)
749            .unwrap_or_default()
750    }
751
752    /// The stale-while-revalidate limit in seconds
753    pub fn stale_while_revalidate_sec(&self) -> u32 {
754        self.0.internal.stale_while_revalidate_sec
755    }
756
757    /// The stale-if-error limit in seconds
758    pub fn stale_if_error_sec(&self) -> u32 {
759        self.0.internal.stale_if_error_sec
760    }
761
762    /// Can the asset be used to serve stale during revalidation at the given time.
763    ///
764    /// NOTE: the serve stale functions do not check !is_fresh(time),
765    /// i.e. the object is already assumed to be stale.
766    pub fn serve_stale_while_revalidate(&self, time: SystemTime) -> bool {
767        self.can_serve_stale(self.0.internal.stale_while_revalidate_sec, time)
768    }
769
770    /// Can the asset be used to serve stale after error at the given time.
771    ///
772    /// NOTE: the serve stale functions do not check !is_fresh(time),
773    /// i.e. the object is already assumed to be stale.
774    pub fn serve_stale_if_error(&self, time: SystemTime) -> bool {
775        self.can_serve_stale(self.0.internal.stale_if_error_sec, time)
776    }
777
778    /// Disable serve stale for this asset
779    pub fn disable_serve_stale(&mut self) {
780        self.0.internal.stale_if_error_sec = 0;
781        self.0.internal.stale_while_revalidate_sec = 0;
782    }
783
784    /// Mark this asset stale as of `instant`, so the next read revalidates it.
785    ///
786    /// The serve stale windows are measured off `fresh_until`, so anchoring it on the point the
787    /// asset went out of service is what keeps them the length the response asked for. A purge
788    /// at `P` on an asset with a 10 minute stale-while-revalidate leaves the stale body servable
789    /// until `P + 10m`, not until the asset's own deadline plus 10 minutes. A caller that wants
790    /// the next read to block on revalidation instead should call
791    /// [`CacheMeta::disable_serve_stale`] as well.
792    ///
793    /// `instant` only ever moves `fresh_until` earlier, so applying the same expiry on every read
794    /// is safe. Passing `SystemTime::now()` on each read rather than the point the asset went out
795    /// of service would restart the windows every time and hold them open forever. It also means
796    /// an asset that has already outlived its windows keeps them closed.
797    ///
798    /// Unlike [`CacheMeta::update_freshness`] this leaves `updated` alone, so age and
799    /// provenance still describe when the asset was actually stored.
800    pub fn expire_at(&mut self, instant: SystemTime) {
801        let fresh_until = &mut self.0.internal.fresh_until;
802        *fresh_until = (*fresh_until).min(instant);
803    }
804
805    /// Update the freshness metadata of this asset.
806    ///
807    /// Refreshes `fresh_until`, `stale_while_revalidate_sec`, and
808    /// `stale_if_error_sec`, and stamps `updated` to now. The response
809    /// header, variance, epoch override, and other fields are preserved.
810    pub fn update_freshness(
811        &mut self,
812        fresh_until: SystemTime,
813        stale_while_revalidate_sec: u32,
814        stale_if_error_sec: u32,
815    ) {
816        self.0.internal.fresh_until = fresh_until;
817        self.0.internal.stale_while_revalidate_sec = stale_while_revalidate_sec;
818        self.0.internal.stale_if_error_sec = stale_if_error_sec;
819        self.0.internal.updated = SystemTime::now();
820    }
821
822    /// Get the variance hash of this asset
823    pub fn variance(&self) -> Option<HashBinary> {
824        self.0.internal.variance
825    }
826
827    /// Set the variance key of this asset
828    pub fn set_variance_key(&mut self, variance_key: HashBinary) {
829        self.0.internal.variance = Some(variance_key);
830    }
831
832    /// Set the variance (hash) of this asset
833    pub fn set_variance(&mut self, variance: HashBinary) {
834        self.0.internal.variance = Some(variance)
835    }
836
837    /// Removes the variance (hash) of this asset
838    pub fn remove_variance(&mut self) {
839        self.0.internal.variance = None
840    }
841
842    /// Get the response header in this asset
843    pub fn response_header(&self) -> &ResponseHeader {
844        &self.0.header
845    }
846
847    /// Modify the header in this asset
848    pub fn response_header_mut(&mut self) -> &mut ResponseHeader {
849        &mut self.0.header
850    }
851
852    /// Expose the extensions to read
853    pub fn extensions(&self) -> &Extensions {
854        &self.0.extensions
855    }
856
857    /// Expose the extensions to modify
858    pub fn extensions_mut(&mut self) -> &mut Extensions {
859        &mut self.0.extensions
860    }
861
862    /// Get a copy of the response header
863    pub fn response_header_copy(&self) -> ResponseHeader {
864        self.0.header.clone()
865    }
866
867    /// get all the headers of this asset
868    pub fn headers(&self) -> &HMap {
869        &self.0.header.headers
870    }
871
872    fn can_serve_stale(&self, serve_stale_sec: u32, time: SystemTime) -> bool {
873        if serve_stale_sec == 0 {
874            return false;
875        }
876        if let Some(stale_until) = self
877            .0
878            .internal
879            .fresh_until
880            .checked_add(Duration::from_secs(serve_stale_sec.into()))
881        {
882            stale_until >= time
883        } else {
884            // overflowed: treat as infinite ttl
885            true
886        }
887    }
888
889    /// Serialize this object
890    pub fn serialize(&self) -> Result<(Vec<u8>, Vec<u8>)> {
891        let internal = self.0.internal.serialize()?;
892        let header = header_serialize(&self.0.header)?;
893        log::debug!("header to serialize: {:?}", self.0.header);
894        Ok((internal, header))
895    }
896
897    /// Deserialize from the binary format
898    pub fn deserialize(internal: &[u8], header: &[u8]) -> Result<Self> {
899        let internal = internal_meta::deserialize(internal)?;
900        let header = header_deserialize(header)?;
901        Ok(CacheMeta(Box::new(CacheMetaInner {
902            internal,
903            header,
904            extensions: Extensions::new(),
905        })))
906    }
907}
908
909use http::StatusCode;
910
911/// The function to generate TTL from the given [StatusCode].
912pub type FreshDurationByStatusFn = fn(StatusCode) -> Option<Duration>;
913
914/// The default settings to generate [CacheMeta]
915pub struct CacheMetaDefaults {
916    // if a status code is not included in fresh_sec, it's not considered cacheable by default.
917    fresh_sec_fn: FreshDurationByStatusFn,
918    stale_while_revalidate_sec: u32,
919    // TODO: allow "error" condition to be configurable?
920    stale_if_error_sec: u32,
921}
922
923impl CacheMetaDefaults {
924    /// Create a new [CacheMetaDefaults]
925    pub const fn new(
926        fresh_sec_fn: FreshDurationByStatusFn,
927        stale_while_revalidate_sec: u32,
928        stale_if_error_sec: u32,
929    ) -> Self {
930        CacheMetaDefaults {
931            fresh_sec_fn,
932            stale_while_revalidate_sec,
933            stale_if_error_sec,
934        }
935    }
936
937    /// Return the default TTL for the given [StatusCode]
938    ///
939    /// `None`: do no cache this code.
940    pub fn fresh_sec(&self, resp_status: StatusCode) -> Option<Duration> {
941        // safe guard to make sure 304 response to share the same default ttl of 200
942        if resp_status == StatusCode::NOT_MODIFIED {
943            (self.fresh_sec_fn)(StatusCode::OK)
944        } else {
945            (self.fresh_sec_fn)(resp_status)
946        }
947    }
948
949    /// The default SWR seconds
950    pub fn serve_stale_while_revalidate_sec(&self) -> u32 {
951        self.stale_while_revalidate_sec
952    }
953
954    /// The default SIE seconds
955    pub fn serve_stale_if_error_sec(&self) -> u32 {
956        self.stale_if_error_sec
957    }
958}
959
960/// The dictionary content for header compression.
961///
962/// Used during initialization of [`HEADER_SERDE`].
963static COMPRESSION_DICT_CONTENT: OnceCell<Cow<'static, [u8]>> = OnceCell::new();
964
965static HEADER_SERDE: Lazy<HeaderSerde> = Lazy::new(|| {
966    let dict_opt = if let Some(dict_content) = COMPRESSION_DICT_CONTENT.get() {
967        Some(dict_content.to_vec())
968    } else {
969        warn!("no header compression dictionary loaded - use set_compression_dict_content() or set_compression_dict_path() to set one");
970        None
971    };
972
973    HeaderSerde::new(dict_opt)
974});
975
976pub(crate) fn header_serialize(header: &ResponseHeader) -> Result<Vec<u8>> {
977    HEADER_SERDE.serialize(header)
978}
979
980pub(crate) fn header_deserialize<T: AsRef<[u8]>>(buf: T) -> Result<ResponseHeader> {
981    HEADER_SERDE.deserialize(buf.as_ref())
982}
983
984/// Load the header compression dictionary from a file, which helps serialize http header.
985///
986/// Returns false if it is already set or if the file could not be read.
987///
988/// Use [`set_compression_dict_content`] to set the dictionary from memory instead.
989pub fn set_compression_dict_path(path: &str) -> bool {
990    match std::fs::read(path) {
991        Ok(dict) => COMPRESSION_DICT_CONTENT.set(dict.into()).is_ok(),
992        Err(e) => {
993            warn!(
994                "failed to read header compress dictionary file at {}, {:?}",
995                path, e
996            );
997            false
998        }
999    }
1000}
1001
1002/// Set the header compression dictionary content, which helps serialize http header.
1003///
1004/// Returns false if it is already set.
1005///
1006/// This is an alernative to [`set_compression_dict_path`], allowing use of
1007/// a dictionary without an external file.
1008pub fn set_compression_dict_content(content: Cow<'static, [u8]>) -> bool {
1009    COMPRESSION_DICT_CONTENT.set(content).is_ok()
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015    use std::time::Duration;
1016
1017    #[test]
1018    fn test_cache_meta_age_without_override() {
1019        let now = SystemTime::now();
1020        let header = ResponseHeader::build_no_case(200, None).unwrap();
1021        let meta = CacheMeta::new(now + Duration::from_secs(300), now, 0, 0, header);
1022
1023        // Without epoch_override, age() should use updated() as reference
1024        std::thread::sleep(Duration::from_millis(100));
1025        let age = meta.age();
1026        assert!(age.as_secs() < 1, "age should be close to 0");
1027
1028        // epoch() should return updated() when no override is set
1029        assert_eq!(meta.epoch(), meta.updated());
1030    }
1031
1032    #[test]
1033    fn test_cache_meta_age_with_epoch_override_past() {
1034        let now = SystemTime::now();
1035        let header = ResponseHeader::build(200, None).unwrap();
1036        let mut meta = CacheMeta::new(now + Duration::from_secs(300), now, 0, 0, header);
1037
1038        // Set epoch_override to 10 seconds in the past
1039        let epoch_override = now - Duration::from_secs(10);
1040        meta.set_epoch_override(epoch_override);
1041
1042        // age() should now use epoch_override as the reference
1043        let age = meta.age();
1044        assert!(age.as_secs() >= 10);
1045        assert!(age.as_secs() < 12);
1046
1047        // epoch() should return the override
1048        assert_eq!(meta.epoch(), epoch_override);
1049        assert_eq!(meta.epoch_override(), Some(epoch_override));
1050    }
1051
1052    #[test]
1053    fn test_cache_meta_age_with_epoch_override_future() {
1054        let now = SystemTime::now();
1055        let header = ResponseHeader::build(200, None).unwrap();
1056        let mut meta = CacheMeta::new(now + Duration::from_secs(100), now, 0, 0, header);
1057
1058        // Set epoch_override to a future time
1059        let future_epoch = now + Duration::from_secs(10);
1060        meta.set_epoch_override(future_epoch);
1061
1062        let age_with_epoch = meta.age();
1063        // age should be 0 since epoch_override is in the future
1064        assert_eq!(age_with_epoch, Duration::ZERO);
1065    }
1066
1067    #[test]
1068    fn test_cache_meta_fresh_sec() {
1069        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1070        let mut meta = CacheMeta::new(
1071            SystemTime::now() + Duration::from_secs(100),
1072            SystemTime::now() - Duration::from_secs(100),
1073            0,
1074            0,
1075            header,
1076        );
1077
1078        meta.0.internal.updated = SystemTime::UNIX_EPOCH + Duration::from_secs(1000);
1079        meta.0.internal.fresh_until = SystemTime::UNIX_EPOCH + Duration::from_secs(1100);
1080
1081        // Without epoch_override, fresh_sec should use updated as reference
1082        let fresh_sec_without_override = meta.fresh_sec();
1083        assert_eq!(fresh_sec_without_override, 100); // 1100 - 1000 = 100 seconds
1084
1085        // With epoch_override set to a later time (1050), fresh_sec should be calculated from that reference
1086        let epoch_override = SystemTime::UNIX_EPOCH + Duration::from_secs(1050);
1087        meta.set_epoch_override(epoch_override);
1088        assert_eq!(meta.epoch_override(), Some(epoch_override));
1089        assert_eq!(meta.epoch(), epoch_override);
1090
1091        let fresh_sec_with_override = meta.fresh_sec();
1092        // fresh_until - epoch_override = 1100 - 1050 = 50 seconds
1093        assert_eq!(fresh_sec_with_override, 50);
1094
1095        meta.remove_epoch_override();
1096        assert_eq!(meta.epoch_override(), None);
1097        assert_eq!(meta.epoch(), meta.updated());
1098        assert_eq!(meta.fresh_sec(), 100); // back to normal calculation
1099    }
1100
1101    #[test]
1102    fn test_cache_meta_new_stamps_provenance() {
1103        let now = SystemTime::now();
1104        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1105        let meta = CacheMeta::new(now + Duration::from_secs(60), now, 0, 0, header);
1106
1107        assert_eq!(meta.created(), now);
1108        assert_eq!(meta.provenance(), now);
1109        assert_eq!(meta.provenance_raw(), Some(now));
1110    }
1111
1112    #[test]
1113    fn test_cache_meta_provenance_fallback_for_absent_field() {
1114        let admission = SystemTime::now();
1115        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1116        let mut meta = CacheMeta::new(admission + Duration::from_secs(60), admission, 0, 0, header);
1117        meta.0.internal.provenance = None;
1118
1119        assert_eq!(meta.created(), admission);
1120        assert!(meta.provenance_raw().is_none());
1121        // Fallback path: provenance() returns created().
1122        assert_eq!(meta.provenance(), admission);
1123    }
1124
1125    #[test]
1126    fn test_cache_meta_set_provenance() {
1127        let admission = SystemTime::now();
1128        let provenance = admission - Duration::from_secs(30);
1129        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1130        let mut meta = CacheMeta::new(admission + Duration::from_secs(60), admission, 0, 0, header);
1131
1132        meta.set_provenance(provenance);
1133
1134        assert_eq!(meta.created(), admission);
1135        assert_eq!(meta.provenance(), provenance);
1136        assert_eq!(meta.provenance_raw(), Some(provenance));
1137    }
1138
1139    #[test]
1140    fn expiring_at_the_current_instant_leaves_the_serve_stale_windows_open() {
1141        let admission = SystemTime::now();
1142        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1143        let mut meta = CacheMeta::new(
1144            admission + Duration::from_secs(300),
1145            admission,
1146            30,
1147            30,
1148            header,
1149        );
1150        assert!(meta.is_fresh(admission));
1151
1152        let expired_at = SystemTime::now();
1153        meta.expire_at(expired_at);
1154
1155        assert_eq!(meta.fresh_until(), expired_at);
1156        assert!(!meta.is_fresh(expired_at + Duration::from_secs(1)));
1157
1158        // Both windows hang off fresh_until, so landing it at the expiry rather than earlier
1159        // is what leaves them open.
1160        assert!(meta.serve_stale_while_revalidate(expired_at));
1161        assert!(meta.serve_stale_if_error(expired_at));
1162    }
1163
1164    /// The point of taking the instant: a purge 5 minutes into an hour of freshness has to
1165    /// leave the window its own length, not the hour it interrupted plus its own length.
1166    #[test]
1167    fn expiring_at_an_instant_measures_the_serve_stale_windows_from_that_instant() {
1168        let admission = SystemTime::now();
1169        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1170        let mut meta = CacheMeta::new(
1171            admission + Duration::from_secs(3600),
1172            admission,
1173            600,
1174            600,
1175            header,
1176        );
1177
1178        let expired_at = admission + Duration::from_secs(300);
1179        meta.expire_at(expired_at);
1180
1181        assert_eq!(meta.fresh_until(), expired_at);
1182        assert!(!meta.is_fresh(expired_at + Duration::from_secs(1)));
1183
1184        // Open for its 600s from the expiry, and shut after them, rather than running to
1185        // the original deadline of admission + 3600s.
1186        assert!(meta.serve_stale_while_revalidate(expired_at + Duration::from_secs(599)));
1187        assert!(meta.serve_stale_if_error(expired_at + Duration::from_secs(599)));
1188        assert!(!meta.serve_stale_while_revalidate(expired_at + Duration::from_secs(601)));
1189        assert!(!meta.serve_stale_if_error(expired_at + Duration::from_secs(601)));
1190    }
1191
1192    /// Expiring is applied on every read, so it has to be idempotent. Re-anchoring on each
1193    /// read's own clock is what would hold the windows open forever.
1194    #[test]
1195    fn expiring_at_an_instant_is_idempotent() {
1196        let admission = SystemTime::now();
1197        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1198        let mut meta = CacheMeta::new(
1199            admission + Duration::from_secs(3600),
1200            admission,
1201            600,
1202            600,
1203            header,
1204        );
1205
1206        let expired_at = admission + Duration::from_secs(300);
1207        meta.expire_at(expired_at);
1208        meta.expire_at(expired_at);
1209        meta.expire_at(expired_at);
1210
1211        assert_eq!(meta.fresh_until(), expired_at);
1212        assert!(!meta.serve_stale_while_revalidate(expired_at + Duration::from_secs(601)));
1213    }
1214
1215    /// Expiring must not put a staler body back into service than the asset already had.
1216    #[test]
1217    fn expiring_at_a_later_instant_leaves_a_closed_window_closed() {
1218        let admission = SystemTime::now() - Duration::from_secs(3600);
1219        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1220        let fresh_until = admission + Duration::from_secs(60);
1221        let mut meta = CacheMeta::new(fresh_until, admission, 60, 60, header);
1222
1223        let now = SystemTime::now();
1224        assert!(!meta.serve_stale_while_revalidate(now));
1225
1226        meta.expire_at(now);
1227
1228        assert_eq!(
1229            meta.fresh_until(),
1230            fresh_until,
1231            "expiring later than the asset's own deadline must not move it"
1232        );
1233        assert!(!meta.serve_stale_while_revalidate(now));
1234        assert!(!meta.serve_stale_if_error(now));
1235    }
1236
1237    /// The blocking behaviour is still reachable, it just has to be asked for.
1238    #[test]
1239    fn expiring_and_disabling_serve_stale_leaves_nothing_to_serve() {
1240        let admission = SystemTime::now();
1241        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1242        let mut meta = CacheMeta::new(
1243            admission + Duration::from_secs(300),
1244            admission,
1245            30,
1246            30,
1247            header,
1248        );
1249
1250        let expired_at = SystemTime::now();
1251        meta.expire_at(expired_at);
1252        meta.disable_serve_stale();
1253
1254        assert!(!meta.is_fresh(expired_at + Duration::from_secs(1)));
1255        assert!(!meta.serve_stale_while_revalidate(expired_at));
1256        assert!(!meta.serve_stale_if_error(expired_at));
1257    }
1258
1259    #[test]
1260    fn expiring_a_meta_leaves_its_admission_history_alone() {
1261        let admission = SystemTime::now() - Duration::from_secs(120);
1262        let provenance = admission - Duration::from_secs(30);
1263        let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
1264        let mut meta = CacheMeta::new(
1265            admission + Duration::from_secs(300),
1266            admission,
1267            0,
1268            0,
1269            header,
1270        );
1271        meta.set_provenance(provenance);
1272
1273        meta.expire_at(SystemTime::now());
1274
1275        assert_eq!(meta.created(), admission);
1276        assert_eq!(meta.updated(), admission);
1277        assert_eq!(meta.provenance(), provenance);
1278    }
1279}