Skip to main content

pingap_core/
ctx.rs

1// Copyright 2024-2025 Tree xie.
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
15use crate::{Ja4Fingerprint, Plugin, get_request_host, now_ms};
16use ahash::AHashMap;
17use bytes::BytesMut;
18use http::StatusCode;
19use http::{HeaderName, HeaderValue};
20#[cfg(feature = "tracing")]
21use opentelemetry::{
22    Context,
23    global::{BoxedSpan, BoxedTracer, ObjectSafeSpan},
24    trace::{SpanKind, TraceContextExt, Tracer},
25};
26use pingora::cache::CacheKey;
27use pingora::http::RequestHeader;
28use pingora::protocols::Digest;
29use pingora::protocols::TimingDigest;
30use pingora::proxy::Session;
31use pingora_limits::inflight::Guard;
32use std::borrow::Cow;
33use std::fmt::Write;
34use std::sync::Arc;
35use std::sync::atomic::{AtomicU32, Ordering};
36use std::time::{Duration, Instant, SystemTime};
37use strum::EnumString;
38
39// Constants for time conversions in milliseconds.
40const SECOND: u64 = 1_000;
41const MINUTE: u64 = 60 * SECOND;
42const HOUR: u64 = 60 * MINUTE;
43
44#[inline]
45/// Format the duration in human readable format, checking smaller units first.
46/// e.g., ms, s, m, h.
47pub fn format_duration(buf: &mut BytesMut, ms: u64) {
48    if ms < SECOND {
49        // Format as milliseconds if less than a second.
50        buf.extend_from_slice(itoa::Buffer::new().format(ms).as_bytes());
51        buf.extend_from_slice(b"ms");
52    } else if ms < MINUTE {
53        // Format as seconds with one decimal place if less than a minute.
54        buf.extend_from_slice(
55            itoa::Buffer::new().format(ms / SECOND).as_bytes(),
56        );
57        let value = (ms % SECOND) / 100;
58        if value != 0 {
59            buf.extend_from_slice(b".");
60            buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
61        }
62        buf.extend_from_slice(b"s");
63    } else if ms < HOUR {
64        // Format as minutes with one decimal place if less than an hour.
65        buf.extend_from_slice(
66            itoa::Buffer::new().format(ms / MINUTE).as_bytes(),
67        );
68        let value = ms % MINUTE * 10 / MINUTE;
69        if value != 0 {
70            buf.extend_from_slice(b".");
71            buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
72        }
73        buf.extend_from_slice(b"m");
74    } else {
75        // Format as hours with one decimal place.
76        buf.extend_from_slice(itoa::Buffer::new().format(ms / HOUR).as_bytes());
77        let value = ms % HOUR * 10 / HOUR;
78        if value != 0 {
79            buf.extend_from_slice(b".");
80            buf.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
81        }
82        buf.extend_from_slice(b"h");
83    }
84}
85
86#[derive(PartialEq)]
87pub enum ModifiedMode {
88    Upstream,
89    Response,
90}
91
92impl From<&str> for ModifiedMode {
93    fn from(value: &str) -> Self {
94        match value {
95            "upstream" => ModifiedMode::Upstream,
96            _ => ModifiedMode::Response,
97        }
98    }
99}
100
101/// Trait for modifying the response body.
102pub trait ModifyResponseBody: Sync + Send {
103    /// Handles the modification of response body data.
104    fn handle(
105        &mut self,
106        session: &Session,
107        body: &mut Option<bytes::Bytes>,
108        end_of_stream: bool,
109    ) -> pingora::Result<()>;
110    /// Returns the name of the modifier.
111    fn name(&self) -> &str {
112        "unknown"
113    }
114}
115
116/// What a plugin does with the body of the request as it is passed on to
117/// the upstream: looks at it (a digest to check) or keeps a copy of it (a
118/// mirror). It does not change it.
119///
120/// Set by the plugin at its request step
121/// ([`Ctx::add_request_body_handler`]) and called by the proxy for each
122/// chunk. A request the upstream is never asked for - answered by a later
123/// plugin, or from the cache - has its body passed to nobody, and the
124/// handler is not called.
125pub trait HandleRequestBody: Sync + Send {
126    /// A chunk of the body, `None` where there is none, and whether it is
127    /// the last. An error ends the request, and the upstream is not sent
128    /// this chunk.
129    ///
130    /// The end is not always said. A request that turns out to have no
131    /// body may get one call with `end_of_stream` or none at all (pingora
132    /// sends the header of such a request to an HTTP/2 upstream as its
133    /// end, and passes nothing through the filter). And a body with a
134    /// `Content-Length` is complete for the upstream once that many bytes
135    /// are out, which may be well before a client over HTTP/2 ends its
136    /// stream. Whoever has to act when the body is complete goes by the
137    /// length the request declares, not by this flag alone.
138    fn handle(
139        &mut self,
140        body: Option<&bytes::Bytes>,
141        end_of_stream: bool,
142    ) -> pingora::Result<()>;
143    /// The upstream is tried again: the body comes from its first byte
144    /// once more, what pingora has kept of it and then the rest.
145    fn restart(&mut self);
146}
147
148/// Information about a single client connection.
149#[derive(Default)]
150pub struct ConnectionInfo {
151    /// A unique identifier for the connection.
152    pub id: usize,
153    /// The IP address of the client.
154    pub client_ip: Option<String>,
155    /// The remote address of the client connection.
156    pub remote_addr: Option<String>,
157    /// The remote port of the client connection.
158    pub remote_port: Option<u16>,
159    /// The server address the client connected to.
160    pub server_addr: Option<String>,
161    /// The server port the client connected to.
162    pub server_port: Option<u16>,
163    /// The TLS version used for the connection, if any.
164    ///
165    /// Stored as `Cow<'static, str>` so values borrowed from pingora's
166    /// `SslDigest` (usually `&'static str`) do not allocate per request.
167    pub tls_version: Option<Cow<'static, str>>,
168    /// The TLS cipher used for the connection, if any.
169    pub tls_cipher: Option<Cow<'static, str>>,
170    /// The JA4 fingerprint of the client's ClientHello, when the server
171    /// collects it. Shared by every request on the connection.
172    pub ja4: Option<Arc<Ja4Fingerprint>>,
173    /// The certificate the client presented in the TLS handshake, on a
174    /// server that asks for one (`tls_client_ca`). It was verified
175    /// against that CA: a certificate that does not verify ends the
176    /// handshake. Shared by every request on the connection.
177    pub tls_client_cert: Option<Arc<TlsClientCert>>,
178    /// Indicates whether the connection was reused (e.g., HTTP keep-alive).
179    pub reused: bool,
180}
181
182/// What is told about the certificate of a client: enough to say who it
183/// is to an upstream, a plugin or the access log.
184#[derive(Debug, Clone, Default, PartialEq, Eq)]
185pub struct TlsClientCert {
186    /// The subject, its parts in the order the certificate has them:
187    /// `O=Example, CN=device-42`.
188    pub subject: String,
189    /// The SHA-256 of the certificate, in lower case hex.
190    pub fingerprint: String,
191    /// The serial number, in lower case hex.
192    pub serial: String,
193}
194
195/// All timing-related metrics for the request lifecycle.
196// #[derive(Default)]
197pub struct Timing {
198    /// Timestamp in milliseconds when the request was created.
199    pub created_at: Instant,
200    /// The total duration of the client connection in milliseconds.
201    /// May be large for reused connections.
202    pub connection_duration: u64,
203    /// The duration of the TLS handshake with the client in milliseconds.
204    pub tls_handshake: Option<i32>,
205    /// The total duration to connect to the upstream server in milliseconds.
206    pub upstream_connect: Option<i32>,
207    /// The duration of the TCP connection to the upstream server in milliseconds.
208    pub upstream_tcp_connect: Option<i32>,
209    /// The duration of the TLS handshake with the upstream server in milliseconds.
210    pub upstream_tls_handshake: Option<i32>,
211    /// How long the upstream connect waited for an offload thread before it
212    /// began, in milliseconds. Only present when
213    /// `basic.upstream_connect_offload_*` is on; it separates scheduling
214    /// delay in the offload pool from network latency.
215    pub upstream_connect_offload_wait: Option<i32>,
216    /// The duration the upstream server took to process the request in milliseconds.
217    pub upstream_processing: Option<i32>,
218    /// The duration from sending the request to receiving the upstream response in milliseconds.
219    pub upstream_response: Option<i32>,
220    /// The total duration of the upstream connection in milliseconds.
221    pub upstream_connection_duration: Option<u64>,
222    /// The duration of the cache lookup in milliseconds.
223    pub cache_lookup: Option<i32>,
224    /// The duration spent waiting for a cache lock in milliseconds.
225    pub cache_lock: Option<i32>,
226}
227
228impl Default for Timing {
229    fn default() -> Self {
230        Self {
231            created_at: Instant::now(),
232            connection_duration: 0,
233            tls_handshake: None,
234            upstream_connect: None,
235            upstream_tcp_connect: None,
236            upstream_tls_handshake: None,
237            upstream_connect_offload_wait: None,
238            upstream_processing: None,
239            upstream_response: None,
240            upstream_connection_duration: None,
241            cache_lookup: None,
242            cache_lock: None,
243        }
244    }
245}
246
247/// Trait for upstream instance, used to handle the upstream instance lifecycle.
248pub trait UpstreamInstance: Send + Sync {
249    fn on_transport_failure(&self, address: &str);
250    fn on_response(&self, address: &str, status: StatusCode);
251    /// Marks the request as finished on this upstream.
252    ///
253    /// Returns the number of in-flight requests still being processed *after*
254    /// releasing this one (so a zero means the upstream is idle).
255    fn completed(&self) -> i32;
256}
257
258/// Trait for location instance
259pub trait LocationInstance: Send + Sync {
260    /// Get location's name
261    fn name(&self) -> &str;
262    /// Get the upstream of location
263    fn upstream(&self) -> &str;
264    /// Whether the location has a rewrite rule at all, so that the uri of
265    /// a request is only kept (`Features::original_uri`) where it may be
266    /// replaced.
267    fn has_rewrite(&self) -> bool {
268        false
269    }
270    /// Rewrites the request url. Returns whether the path changed; named
271    /// captures of the rewrite pattern are added to `variables`. An error
272    /// when the rule gives something that is no path: the request is then
273    /// refused and not sent on with the path it came with.
274    fn rewrite(
275        &self,
276        header: &mut RequestHeader,
277        variables: &mut Option<AHashMap<String, String>>,
278    ) -> pingora::Result<bool>;
279    /// Returns the proxy header to upstream
280    fn headers(&self) -> Option<&Vec<(HeaderName, HeaderValue, bool)>>;
281    /// Returns the client body size limit
282    fn client_body_size_limit(&self) -> usize;
283    /// Called when the request is received from the client
284    /// Returns
285    /// `Result<(u64, i32)>` - A tuple containing:
286    ///   - The new total number of accepted requests (u64)
287    ///   - The new number of currently processing requests (i32)
288    fn on_request(&self) -> pingora::Result<(u64, i32)>;
289    /// Called when the response is received from the upstream
290    fn on_response(&self);
291    /// Puts the timeouts of the location in place of the upstream's, on the
292    /// peer of one of its requests. A location that sets none leaves the
293    /// peer as it is.
294    fn apply_timeouts(
295        &self,
296        _options: &mut pingora::upstreams::peer::PeerOptions,
297    ) {
298    }
299}
300
301/// Information about the upstream (backend) server.
302#[derive(Default)]
303pub struct UpstreamInfo {
304    /// Upstream instance
305    pub upstream_instance: Option<Arc<dyn UpstreamInstance>>,
306    /// Location instance
307    pub location_instance: Option<Arc<dyn LocationInstance>>,
308    /// The location (route) that directed the request to this upstream.
309    pub location: Arc<str>,
310    /// The name of the upstream server or group.
311    pub name: Arc<str>,
312    /// The address of the upstream server.
313    pub address: String,
314    /// Indicates if the connection to the upstream was reused.
315    pub reused: bool,
316    /// The number of requests currently being processed by the upstream.
317    pub processing_count: Option<i32>,
318    /// The current number of active connections to the upstream.
319    pub connected_count: Option<i32>,
320    /// The HTTP status code of upstream response.
321    pub status: Option<StatusCode>,
322    /// The number of retries for failed connections.
323    pub retries: u8,
324    /// Maximum number of retries for failed connections.
325    pub max_retries: Option<u8>,
326    /// The maximum total time window allowed for an operation and all of its subsequent retries.
327    ///
328    /// The timer starts from the beginning of the **initial attempt**. Once this time window
329    /// is exceeded, no more retries will be initiated, even if the maximum number of
330    /// retries (`max_retries`) has not been reached.
331    ///
332    /// If set to `None`, there is no time limit for the retry process.
333    pub max_retry_window: Option<Duration>,
334    /// The backends this request was sent to and that failed it in a way
335    /// that is retried. The next attempt goes to another one, where there
336    /// is one.
337    pub failed_addresses: Vec<String>,
338    /// The request as one in flight on its backend, for an upstream that
339    /// chooses by that (`least_conn`): counted from the attempt to the
340    /// next one, or to the end of the request.
341    pub backend_inflight: Option<InflightGuard>,
342    /// The cookie that keeps the client on the backend it was given,
343    /// for an upstream that does so (`sticky:<cookie>`): `name=value`,
344    /// to be set on the response when the client does not have it yet,
345    /// or has it for another backend.
346    pub sticky_cookie: Option<String>,
347}
348
349/// One request in flight on a backend, for as long as this is kept: the
350/// count goes up when it is made and down when it is dropped, whichever
351/// way the request ends.
352pub struct InflightGuard(Arc<AtomicU32>);
353
354impl InflightGuard {
355    pub fn new(count: Arc<AtomicU32>) -> Self {
356        count.fetch_add(1, Ordering::Relaxed);
357        Self(count)
358    }
359}
360
361impl Drop for InflightGuard {
362    fn drop(&mut self) {
363        self.0.fetch_sub(1, Ordering::Relaxed);
364    }
365}
366
367/// What a limit has left for the client of this request, as the
368/// `X-RateLimit-*` response headers tell it.
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub struct RateLimitQuota {
371    /// What the limit allows: requests in any one interval, or at once.
372    pub limit: u64,
373    /// How many of them are left, this request counted.
374    pub remaining: u64,
375    /// In how many seconds all of them are back if nothing more is sent.
376    /// `None` for a limit on concurrent requests, where that is whenever
377    /// they end.
378    pub reset: Option<u64>,
379}
380
381/// State related to the current request being processed.
382#[derive(Default)]
383pub struct RequestState {
384    /// A unique identifier for the request.
385    pub request_id: Option<String>,
386    /// The HTTP status code of the response.
387    pub status: Option<StatusCode>,
388    /// The size of the request payload in bytes.
389    pub payload_size: usize,
390    /// The inflight limiters this request is counted in, one guard each.
391    /// A limiter's count goes down again when its guard is dropped, at the
392    /// end of the request.
393    pub guards: Vec<Guard>,
394    /// The total number of requests currently being processed by the service.
395    pub processing_count: i32,
396    /// The total number of requests accepted by the service.
397    pub accepted_count: u64,
398    /// The number of requests currently being processed for this location.
399    pub location_processing_count: i32,
400    /// The total number of requests accepted for this location.
401    pub location_accepted_count: u64,
402    /// The request passed every filter and is on its way to an upstream.
403    /// A failure from here on closes the client's connection, whatever
404    /// the proxy says of it: pingora asks whether it may be kept only for
405    /// a request that failed in one of the filters.
406    pub proxying: bool,
407    /// A stale response of the cache is being answered for a `5xx` of
408    /// the upstream (`stale-if-error`). The proxy goes by it to tell
409    /// where the body of that answer ends, which pingora does not say on
410    /// this path: see `Server::response_body_filter`.
411    pub stale_for_status: bool,
412    /// The plugins have been told that the body of the response ends:
413    /// they are told once, whoever says it.
414    pub body_end_said: bool,
415    /// The budget to report to the client, from the limit that has the
416    /// least left of those that were asked to report theirs.
417    pub rate_limit: Option<RateLimitQuota>,
418}
419
420/// Components of the cache key that stand for something this request
421/// said - the coding it accepts, the image formats it takes - and that
422/// another request for the same url has others in the place of.
423///
424/// A `PURGE` is such another request: it names the url and says nothing of
425/// codings or formats. What it has to remove is the entry under every one
426/// of the alternatives, not only under its own.
427#[derive(Clone, Debug)]
428pub struct CacheKeyVariant {
429    /// Where in `keys` its components start.
430    pub at: usize,
431    /// How many of them there are for this request, which may be none.
432    pub len: usize,
433    /// Every list of components that can stand here, the empty one
434    /// included.
435    pub alternatives: Arc<Vec<Vec<String>>>,
436}
437
438/// Which parameters of the query a cache key is made of.
439#[derive(Debug, Clone, PartialEq)]
440pub enum CacheQueryRule {
441    /// All but these.
442    Ignore(Vec<String>),
443    /// These and no other.
444    Allow(Vec<String>),
445}
446
447impl CacheQueryRule {
448    /// The query string a cache key is made of: the parameters the rule
449    /// keeps, in the order of their names.
450    ///
451    /// `?b=2&a=1` and `?a=1&b=2` are the same page and were two entries,
452    /// and a tracking parameter (`utm_source`, `fbclid`) made one entry
453    /// per visitor. Parameters of the same name keep the order they came
454    /// in, which is theirs to mean something. Names are compared as they
455    /// are written, not decoded.
456    pub fn key_query(&self, query: &str) -> String {
457        fn name(pair: &str) -> &str {
458            pair.split('=').next().unwrap_or(pair)
459        }
460        let mut pairs: Vec<&str> = query
461            .split('&')
462            .filter(|pair| !pair.is_empty())
463            .filter(|pair| {
464                let name = name(pair);
465                match self {
466                    Self::Ignore(names) => !names.iter().any(|n| n == name),
467                    Self::Allow(names) => names.iter().any(|n| n == name),
468                }
469            })
470            .collect();
471        pairs.sort_by(|a, b| name(a).cmp(name(b)));
472        pairs.join("&")
473    }
474}
475
476/// All cache-related configuration and statistics for a request.
477#[derive(Default)]
478pub struct CacheInfo {
479    /// The namespace for cache entries.
480    pub namespace: Option<String>,
481    /// The list of keys used to generate the final cache key.
482    pub keys: Option<Vec<String>>,
483    /// Which of `keys` are one alternative among several, see
484    /// [`CacheKeyVariant`].
485    pub key_variants: Option<Vec<CacheKeyVariant>>,
486    /// Whether to respect Cache-Control headers.
487    pub check_cache_control: bool,
488    /// The maximum time-to-live for cache entries.
489    pub max_ttl: Option<Duration>,
490    /// Request headers the origin's `Vary` response header may turn into
491    /// cache variants (lowercased); `None` honours every header it names.
492    pub vary_headers: Option<Arc<Vec<String>>>,
493    /// How long a response stays fresh when the origin names no lifetime,
494    /// for the statuses that are kept by default, in place of one second.
495    pub default_ttl: Option<Duration>,
496    /// The same for single statuses, also ones that are not kept by
497    /// default: `(status, lifetime)`. A lifetime of zero keeps a status
498    /// out.
499    pub status_ttl: Option<Arc<Vec<(u16, Duration)>>>,
500    /// Which parameters of the query the cache key is made of, where
501    /// the plugin has a rule for that.
502    pub query_rule: Option<Arc<CacheQueryRule>>,
503    /// The query the key of this request was made of by that rule, from
504    /// the moment the key is made (`settle_key_query`): with the
505    /// parameters the rule leaves out gone and the others in order.
506    /// Empty for no query at all. The upstream is asked with it too, see
507    /// `ask_with_key_query`.
508    pub key_query: Option<String>,
509    /// The request headers the key is made of as well (`headers` of the
510    /// cache plugin), by name. Their values are read when the key is
511    /// made, see [`CacheInfo::header_key_components`].
512    pub key_headers: Option<Arc<Vec<String>>>,
513    /// The client asked for a copy that is checked with the origin
514    /// (`Cache-Control: no-cache`), and the plugin lets clients do that:
515    /// what is cached is revalidated before it is served.
516    pub revalidate: bool,
517    /// The number of cache read operations performed.
518    pub reading_count: Option<u32>,
519    /// The number of cache write operations performed.
520    pub writing_count: Option<u32>,
521}
522
523/// The value of a header as one component of the cache key. The
524/// components are joined with `:`, so one inside a value is written
525/// `%3A` (and `%` as `%25`): `Origin: https://a.com` with nothing after it
526/// and `Origin: https` followed by `//a.com:` were one key, and whoever
527/// sent the second chose the response the first was given.
528pub fn cache_key_slot(value: &str) -> String {
529    if !value.contains([':', '%']) {
530        return value.to_string();
531    }
532    value.replace('%', "%25").replace(':', "%3A")
533}
534
535/// What `header` has under `name`, as a component of the cache key: every
536/// line of it, as the upstream gets them. Read from the first line alone,
537/// a second one a client or a plugin had added was sent on and not in the
538/// key.
539fn header_key_slot(header: &RequestHeader, name: &str) -> String {
540    let mut values = header.headers.get_all(name).iter();
541    let Some(first) = values.next() else {
542        return String::new();
543    };
544    let mut value = String::from_utf8_lossy(first.as_bytes()).into_owned();
545    for more in values {
546        value.push_str(", ");
547        value.push_str(&String::from_utf8_lossy(more.as_bytes()));
548    }
549    cache_key_slot(&value)
550}
551
552impl CacheInfo {
553    /// The components the key has for the headers it is made of
554    /// (`headers` of the cache plugin): one for each name, kept for a
555    /// header the request does not have, and none at all for a request
556    /// that has none of them.
557    ///
558    /// The values are those of `header`, the request as it is when the
559    /// key is made: after every request plugin has run, as the query of
560    /// the key is, and so what the upstream is asked with. A plugin that
561    /// is listed after the cache has set its header by then: `jwt`
562    /// passing a claim on (`claims_to_headers`), `request_headers`. Read
563    /// when the cache plugin ran, such a header was not in the key at
564    /// all - or it was what the client itself had sent under that name -
565    /// and the page of one user was the next one's.
566    ///
567    /// A key that is made of another request, a purge, has the values of
568    /// that one: it names the variant by sending the headers itself.
569    fn header_key_components(&self, header: &RequestHeader) -> Vec<String> {
570        let Some(names) = &self.key_headers else {
571            return vec![];
572        };
573        let mut slots: Vec<String> = names
574            .iter()
575            .map(|name| header_key_slot(header, name))
576            .collect();
577        if slots.iter().all(|slot| slot.is_empty()) {
578            slots.clear();
579        }
580        slots
581    }
582
583    /// Makes the query the cache key is made of, from the request as it
584    /// is now. Called when the key is made, which is after the request
585    /// plugins have run: one that takes a parameter out of the query
586    /// (`key_auth` with `hide_credentials`) has done so by then, also
587    /// when it is listed after the cache. Taken when the cache plugin
588    /// ran, the key query had the credential in it, and the upstream was
589    /// then asked with what was meant to be hidden from it.
590    pub fn settle_key_query(&mut self, header: &RequestHeader) {
591        if let Some(rule) = &self.query_rule {
592            self.key_query =
593                Some(rule.key_query(header.uri.query().unwrap_or_default()));
594        }
595    }
596
597    /// Gives `header`, the request that goes to the upstream, the query
598    /// the cache key is made of, where that is not the request's own.
599    ///
600    /// The key says which parameters a response depends on; with the
601    /// others sent on all the same it was the upstream that decided. A
602    /// parameter spelled another way than the rule has it (`p%61ge=2`
603    /// for `page`, `x=1;page=2`) was left out of the key and read by the
604    /// upstream, and page 2 was stored as the page without a number, for
605    /// everyone. Asked with the query of the key, the upstream can not
606    /// tell two requests of one key apart.
607    ///
608    /// Only the request to the upstream is changed, not the client's:
609    /// the access log and the plugins see what was asked for. `false`
610    /// when the uri can not be made, and then the request must not be
611    /// sent as it is.
612    pub fn ask_with_key_query(&self, header: &mut RequestHeader) -> bool {
613        let made;
614        let query = match (&self.key_query, &self.query_rule) {
615            (Some(query), _) => query.as_str(),
616            // No key was made for this request, so nothing is stored
617            // either: the rule is applied all the same, for an upstream
618            // that sees one kind of query on this location.
619            (None, Some(rule)) => {
620                made = rule.key_query(header.uri.query().unwrap_or_default());
621                made.as_str()
622            },
623            (None, None) => return true,
624        };
625        // `/a?` is `/a` in the key, and is sent as that.
626        if header.uri.query() == (!query.is_empty()).then_some(query) {
627            return true;
628        }
629        let path_and_query = if query.is_empty() {
630            header.uri.path().to_string()
631        } else {
632            format!("{}?{query}", header.uri.path())
633        };
634        let mut parts = header.uri.clone().into_parts();
635        let Ok(path_and_query) = path_and_query.parse() else {
636            return false;
637        };
638        parts.path_and_query = Some(path_and_query);
639        let Ok(uri) = http::Uri::from_parts(parts) else {
640            return false;
641        };
642        header.set_uri(uri);
643        true
644    }
645}
646
647/// A limit on how fast a response body is sent: so many bytes a second,
648/// once the first `after` of them are out.
649///
650/// It is kept by what has been sent and how long that has taken, not by
651/// a pause per chunk: the time the sending itself takes is part of the
652/// time a body is given, so a client that reads slowly is not slowed
653/// down a second time. Time that was not used is not saved up.
654#[derive(Debug, Clone)]
655pub struct BodyPace {
656    rate: u64,
657    after: u64,
658    sent: u64,
659    /// When the first byte past `after` was about to be sent.
660    limited_since: Option<Instant>,
661}
662
663impl BodyPace {
664    /// `rate` bytes a second, at least one; the first `after` bytes as
665    /// fast as they go.
666    pub fn new(rate: u64, after: u64) -> Self {
667        Self {
668            rate: rate.max(1),
669            after,
670            sent: 0,
671            limited_since: None,
672        }
673    }
674
675    /// How long to wait after a chunk of `len` bytes, for it and what
676    /// went before it to have taken their time at the rate: `None` when
677    /// the body is on time, or not limited yet. For whoever writes the
678    /// chunks itself, and asks between one and the next.
679    pub fn delay(&mut self, len: usize) -> Option<Duration> {
680        self.delay_at(len, Instant::now())
681    }
682
683    /// How long to wait before a chunk of `len` bytes, for what went
684    /// before it to have taken its time: nothing for the first chunk,
685    /// which is how a body that is written in one piece is not held back
686    /// at all. For whoever is asked before a chunk is written, and has
687    /// the wait done for it (the proxy, by pingora).
688    pub fn delay_before(&mut self, len: usize) -> Option<Duration> {
689        self.delay_before_at(len, Instant::now())
690    }
691
692    fn delay_at(&mut self, len: usize, now: Instant) -> Option<Duration> {
693        self.count(len, now);
694        self.owed(now)
695    }
696
697    fn delay_before_at(
698        &mut self,
699        len: usize,
700        now: Instant,
701    ) -> Option<Duration> {
702        let owed = self.owed(now);
703        self.count(len, now);
704        owed
705    }
706
707    fn count(&mut self, len: usize, now: Instant) {
708        self.sent = self.sent.saturating_add(len as u64);
709        if self.sent > self.after {
710            self.limited_since.get_or_insert(now);
711        }
712    }
713
714    /// The time that what has been counted is still to take.
715    fn owed(&mut self, now: Instant) -> Option<Duration> {
716        let limited = self.sent.checked_sub(self.after).filter(|n| *n > 0)?;
717        let since = self.limited_since?;
718        // What the limited part of the body is given, at the rate.
719        let due = Duration::from_secs_f64(limited as f64 / self.rate as f64);
720        let taken = now.saturating_duration_since(since);
721        if taken > due {
722            // Behind, which is the client's doing. The time it did not
723            // use is not kept for it: counted from the start, a body that
724            // stood still for a minute could then go out a minute's worth
725            // at once.
726            self.limited_since = now.checked_sub(due);
727            return None;
728        }
729        Some(due - taken).filter(|delay| !delay.is_zero())
730    }
731}
732
733/// Optional features like tracing, plugins, and response modifications.
734#[derive(Default)]
735pub struct Features {
736    /// A map of custom variables for request processing.
737    pub variables: Option<AHashMap<String, String>>,
738    /// The uri the client asked for, when the location has rewritten it.
739    /// A plugin that sends the client somewhere relative to where it is
740    /// (the `directory` plugin, adding the closing slash) goes by this:
741    /// the rewritten path is the upstream's, not the browser's.
742    pub original_uri: Option<http::Uri>,
743    /// A list of plugin names and their processing times in milliseconds.
744    pub plugin_processing_times: Option<Vec<(Arc<str>, u32)>>,
745    /// Statistics about response compression.
746    pub compression_stat: Option<CompressionStat>,
747    /// A map of plugin names and their response body handlers.
748    pub modify_body_handlers:
749        Option<AHashMap<String, Box<dyn ModifyResponseBody>>>,
750    /// What a plugin settled at one step of a request and goes by again
751    /// at a later one, under a name of its own: see
752    /// [`Ctx::set_plugin_note`].
753    pub plugin_notes: Option<Vec<(String, &'static str)>>,
754    /// What plugins do with the body of the request as it passes, see
755    /// [`HandleRequestBody`].
756    pub request_body_handlers: Option<Vec<Box<dyn HandleRequestBody>>>,
757    /// How fast the body of the response is sent, where a plugin limits
758    /// that (`bandwidth_limit`): gone by wherever a body is written, for
759    /// a response from the upstream or the cache as for a file a plugin
760    /// sends itself.
761    pub body_pace: Option<BodyPace>,
762    /// A plugin reads the body of the answer to this request on its way
763    /// to the client and has to have it as the upstream sends it: see
764    /// [`Ctx::keep_response_body_plain`].
765    pub plain_response_body: Option<PlainBody>,
766    /// OpenTelemetry tracer for distributed tracing (available with the "tracing" feature).
767    #[cfg(feature = "tracing")]
768    pub otel_tracer: Option<OtelTracer>,
769    /// OpenTelemetry span for the upstream request (available with the "tracing" feature).
770    #[cfg(feature = "tracing")]
771    pub upstream_span: Option<BoxedSpan>,
772}
773
774/// The responses whose body a plugin reads, see
775/// [`Ctx::keep_response_body_plain`].
776#[derive(Debug, Clone, PartialEq)]
777pub enum PlainBody {
778    /// Whatever the response is.
779    Any,
780    /// Those of these content types: prefixes, in lower case.
781    Types(Vec<String>),
782}
783
784#[derive(Default)]
785/// Statistics about response compression operations.
786pub struct CompressionStat {
787    /// The algorithm used for compression (e.g., "gzip", "br").
788    pub algorithm: String,
789    /// The size of the data before compression in bytes.
790    pub in_bytes: usize,
791    /// The size of the data after compression in bytes.
792    pub out_bytes: usize,
793    /// The time taken to perform the compression operation.
794    pub duration: Duration,
795}
796
797impl CompressionStat {
798    /// Calculates the compression ratio.
799    pub fn ratio(&self) -> f64 {
800        if self.out_bytes == 0 {
801            return 0.0;
802        }
803        (self.in_bytes as f64) / (self.out_bytes as f64)
804    }
805}
806
807/// A wrapper for OpenTelemetry tracing components.
808#[cfg(feature = "tracing")]
809pub struct OtelTracer {
810    /// The tracer instance.
811    pub tracer: BoxedTracer,
812    /// The main span for the incoming HTTP request.
813    pub http_request_span: BoxedSpan,
814}
815
816#[cfg(feature = "tracing")]
817impl OtelTracer {
818    /// Creates a new child span for an upstream request.
819    #[inline]
820    pub fn new_upstream_span(&self, name: &str) -> BoxedSpan {
821        self.tracer
822            .span_builder(name.to_string())
823            .with_kind(SpanKind::Client)
824            .start_with_context(
825                &self.tracer,
826                // Set the parent span context to link this upstream span with the main request span.
827                &Context::current().with_remote_span_context(
828                    self.http_request_span.span_context().clone(),
829                ),
830            )
831    }
832}
833
834/// A plugin paired with the name it was registered under in the location config.
835pub type NamedPlugin = (Arc<str>, Arc<dyn Plugin>);
836
837/// A `Ctx` value the access log can print. The `{:name}` in a log format
838/// parses into one of these once, when the format is read, so writing a
839/// log line dispatches on an enum instead of comparing the name against
840/// every key on every request.
841#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, strum::Display)]
842#[strum(serialize_all = "snake_case")]
843pub enum CtxLogField {
844    ConnectionId,
845    UpstreamReused,
846    UpstreamStatus,
847    UpstreamAddr,
848    Processing,
849    UpstreamConnected,
850    UpstreamConnectTime,
851    UpstreamConnectTimeHuman,
852    UpstreamProcessingTime,
853    UpstreamProcessingTimeHuman,
854    UpstreamResponseTime,
855    UpstreamResponseTimeHuman,
856    UpstreamTcpConnectTime,
857    UpstreamTcpConnectTimeHuman,
858    UpstreamTlsHandshakeTime,
859    UpstreamTlsHandshakeTimeHuman,
860    UpstreamConnectOffloadWaitTime,
861    UpstreamConnectOffloadWaitTimeHuman,
862    UpstreamConnectionTime,
863    UpstreamConnectionTimeHuman,
864    ConnectionTime,
865    ConnectionTimeHuman,
866    Location,
867    ConnectionReused,
868    TlsVersion,
869    TlsCipher,
870    TlsClientSubject,
871    TlsClientFingerprint,
872    TlsClientSerial,
873    TlsClientVerified,
874    Ja4,
875    #[strum(serialize = "ja4_r")]
876    Ja4R,
877    #[strum(serialize = "ja4_o")]
878    Ja4O,
879    #[strum(serialize = "ja4_ro")]
880    Ja4Ro,
881    TlsHandshakeTime,
882    TlsHandshakeTimeHuman,
883    CompressionTime,
884    CompressionTimeHuman,
885    CompressionRatio,
886    CacheLookupTime,
887    CacheLookupTimeHuman,
888    CacheLockTime,
889    CacheLockTimeHuman,
890    ServiceTime,
891    ServiceTimeHuman,
892}
893
894/// Represents the state of a request/response cycle, tracking various metrics and properties
895/// including connection details, caching information, and upstream server interactions.
896#[derive(Default)]
897pub struct Ctx {
898    /// Information about the client connection.
899    pub conn: ConnectionInfo,
900    /// Information about the upstream server.
901    pub upstream: UpstreamInfo,
902    /// Timing metrics for the request lifecycle.
903    pub timing: Timing,
904    /// State related to the current request.
905    pub state: RequestState,
906    /// Cache-related information. Boxed behind an `Option`: most requests
907    /// never touch the cache, and a `None` costs a pointer instead of the
908    /// whole struct on every `Ctx`.
909    pub cache: Option<Box<CacheInfo>>,
910    /// Optional features, boxed for the same reason.
911    pub features: Option<Box<Features>>,
912    /// Plugins for the current location, shared with the location's cache
913    /// of resolved plugins so a request only bumps a reference count.
914    pub plugins: Option<Arc<[NamedPlugin]>>,
915    /// The same list, left here while the request plugins run when one of
916    /// them asks to see the responses of the others
917    /// (`handles_plugin_response`), for a plugin that writes its response
918    /// itself: see [`crate::decorate_plugin_response`].
919    pub response_plugins: Option<Arc<[NamedPlugin]>>,
920}
921
922/// Helper struct to store connection timing and TLS details
923#[derive(Debug, Default)]
924pub struct DigestDetail {
925    /// A guess at reuse: the connection was established more than 100 ms
926    /// before this request. It is only a guess - a slow client or a long TLS
927    /// handshake trips it on a brand-new connection - so the proxy takes the
928    /// exact keepalive signal pingora gives it for HTTP/1 and applies this
929    /// only to HTTP/2, where streams share one connection with no signal.
930    pub connection_reused: bool,
931    /// Age of the connection in milliseconds, wall clock.
932    pub connection_time: u64,
933    /// Timestamp when TCP connection was established
934    pub tcp_established: u64,
935    /// Timestamp when TLS handshake completed
936    pub tls_established: u64,
937    /// TCP connect time in milliseconds, measured by pingora on a monotonic
938    /// clock. Only a connection this side opened has one; an accepted
939    /// connection reports `None`.
940    pub tcp_connect: Option<u64>,
941    /// TLS handshake time in milliseconds, measured by pingora on a monotonic
942    /// clock around the handshake itself; `None` without TLS or when it was
943    /// not measured.
944    pub tls_handshake: Option<u64>,
945    /// Time the connect spent queued for an offload thread, in milliseconds.
946    /// `None` unless the connect was offloaded.
947    pub connect_offload_wait: Option<u64>,
948    /// TLS protocol version if using HTTPS
949    pub tls_version: Option<Cow<'static, str>>,
950    /// TLS cipher suite in use if using HTTPS
951    pub tls_cipher: Option<Cow<'static, str>>,
952    /// The certificate of the peer, where the listener's handshake kept
953    /// one for the connection.
954    pub tls_client_cert: Option<Arc<TlsClientCert>>,
955}
956
957#[inline]
958pub(crate) fn timing_to_ms(timing: Option<&Option<TimingDigest>>) -> u64 {
959    match timing {
960        Some(Some(item)) => item
961            .established_ts
962            .duration_since(SystemTime::UNIX_EPOCH)
963            .unwrap_or_default()
964            .as_millis() as u64,
965        _ => 0,
966    }
967}
968
969/// Extracts timing and TLS information from connection digest.
970/// Used for metrics and logging connection details.
971#[inline]
972pub fn get_digest_detail(digest: &Digest) -> DigestDetail {
973    let tcp_established = timing_to_ms(digest.timing_digest.first());
974    let mut connection_time = 0;
975    let now = now_ms();
976    if tcp_established > 0 && tcp_established < now {
977        connection_time = now - tcp_established;
978    }
979    let connection_reused = connection_time > 100;
980    // The first entry is the transport layer, the last one the outermost
981    // layer - the TLS session when there is one, otherwise the transport
982    // layer again, which is why the handshake is only read under TLS.
983    let tcp_connect = establishment_ms(digest.timing_digest.first());
984    let connect_offload_wait = offload_wait_ms(digest.timing_digest.first());
985
986    let Some(ssl_digest) = &digest.ssl_digest else {
987        return DigestDetail {
988            connection_reused,
989            tcp_established,
990            connection_time,
991            tcp_connect,
992            connect_offload_wait,
993            ..Default::default()
994        };
995    };
996
997    DigestDetail {
998        connection_reused,
999        tcp_established,
1000        connection_time,
1001        tcp_connect,
1002        connect_offload_wait,
1003        tls_established: timing_to_ms(digest.timing_digest.last()),
1004        tls_handshake: establishment_ms(digest.timing_digest.last()),
1005        // Clone the Cow: Borrowed(&'static str) is allocation-free.
1006        tls_version: Some(ssl_digest.version.clone()),
1007        tls_cipher: Some(ssl_digest.cipher.clone()),
1008        // Made once, when the handshake was done: a request takes a
1009        // reference to it.
1010        tls_client_cert: ssl_digest
1011            .extension
1012            .get::<Arc<TlsClientCert>>()
1013            .cloned(),
1014    }
1015}
1016
1017/// The layer's own establishment time, when pingora measured it.
1018fn establishment_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
1019    timing
1020        .and_then(|item| item.as_ref())
1021        .and_then(|item| item.establishment_duration)
1022        .map(|duration| duration.as_millis() as u64)
1023}
1024
1025/// How long the transport connect waited for an offload thread, when it was
1026/// offloaded at all.
1027fn offload_wait_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
1028    timing
1029        .and_then(|item| item.as_ref())
1030        .and_then(|item| item.offload_wait_duration)
1031        .map(|duration| duration.as_millis() as u64)
1032}
1033
1034impl Ctx {
1035    /// Creates a new Ctx instance with the current timestamp and default values.
1036    ///
1037    /// Returns a new Ctx struct initialized with the current timestamp and all other fields
1038    /// set to their default values.
1039    pub fn new() -> Self {
1040        Self {
1041            ..Default::default()
1042        }
1043    }
1044
1045    /// Adds a variable to the state's variables map with the given key and value.
1046    ///
1047    /// # Arguments
1048    /// * `key` - The variable name.
1049    /// * `value` - The value to store for this variable.
1050    #[inline]
1051    pub fn add_variable(&mut self, key: &str, value: &str) {
1052        // Lazily initialize features and variables map.
1053        let features = self.features.get_or_insert_default();
1054        let variables = features.variables.get_or_insert_with(AHashMap::new);
1055        variables.insert(key.to_string(), value.to_string());
1056    }
1057
1058    /// Extends the variables map with the given key-value pairs.
1059    ///
1060    /// # Arguments
1061    /// * `values` - A HashMap containing the key-value pairs to add.
1062    #[inline]
1063    pub fn extend_variables(&mut self, values: AHashMap<String, String>) {
1064        let features = self.features.get_or_insert_default();
1065        if let Some(variables) = features.variables.as_mut() {
1066            variables.extend(values);
1067        } else {
1068            features.variables = Some(values);
1069        }
1070    }
1071
1072    /// Returns the value of a variable by key.
1073    ///
1074    /// # Arguments
1075    /// * `key` - The key of the variable to retrieve.
1076    ///
1077    /// Returns: Option<&str> representing the value of the variable, or None if the variable does not exist.
1078    #[inline]
1079    pub fn get_variable(&self, key: &str) -> Option<&str> {
1080        self.features
1081            .as_ref()?
1082            .variables
1083            .as_ref()?
1084            .get(key)
1085            .map(|v| v.as_str())
1086    }
1087
1088    /// Keeps `note` for the plugin that calls itself `name`, until the
1089    /// request ends.
1090    ///
1091    /// For a decision that is made once and has to hold: the request a
1092    /// later step sees is not the one the first step saw - a location
1093    /// rewrites the path, another plugin takes a parameter out of the
1094    /// query or changes a header - and a plugin that decided again from
1095    /// what is there then could come to the other answer.
1096    #[inline]
1097    pub fn set_plugin_note(&mut self, name: &str, note: &'static str) {
1098        let notes = self
1099            .features
1100            .get_or_insert_default()
1101            .plugin_notes
1102            .get_or_insert_default();
1103        match notes.iter_mut().find(|(key, _)| key == name) {
1104            Some(found) => found.1 = note,
1105            None => notes.push((name.to_string(), note)),
1106        }
1107    }
1108
1109    /// What [`Ctx::set_plugin_note`] kept under `name`.
1110    #[inline]
1111    pub fn get_plugin_note(&self, name: &str) -> Option<&'static str> {
1112        self.features
1113            .as_ref()?
1114            .plugin_notes
1115            .as_ref()?
1116            .iter()
1117            .find(|(key, _)| key == name)
1118            .map(|(_, note)| *note)
1119    }
1120
1121    /// Says that a plugin reads the body of the answer to this request on
1122    /// its way to the client - signs it as a token, rewrites it by rules -
1123    /// and so has to have it as the upstream sends it.
1124    ///
1125    /// What looks at the response before the plugins of the response step
1126    /// do goes by it: compression in its upstream mode, which codes the
1127    /// body as it arrives from the upstream, leaves such a response as it
1128    /// comes. Coded there, the body was opaque bytes to every plugin
1129    /// after it, whatever their order in the list.
1130    ///
1131    /// `types` are the content types the plugin reads (prefixes, in lower
1132    /// case), where it reads only some: the others are none of its
1133    /// business and are coded as before. `None` is every response.
1134    #[inline]
1135    pub fn keep_response_body_plain(&mut self, types: Option<&[String]>) {
1136        let wanted =
1137            &mut self.features.get_or_insert_default().plain_response_body;
1138        *wanted = match (wanted.take(), types) {
1139            (Some(PlainBody::Any), _) | (_, None) => Some(PlainBody::Any),
1140            (Some(PlainBody::Types(mut all)), Some(types)) => {
1141                all.extend_from_slice(types);
1142                Some(PlainBody::Types(all))
1143            },
1144            (None, Some(types)) => Some(PlainBody::Types(types.to_vec())),
1145        };
1146    }
1147
1148    /// Whether a plugin asked for the body of this response as the
1149    /// upstream sends it ([`Ctx::keep_response_body_plain`]). `is_of` says
1150    /// whether the response is of one of the content types it is given.
1151    #[inline]
1152    pub fn response_body_stays_plain(
1153        &self,
1154        is_of: impl FnOnce(&[String]) -> bool,
1155    ) -> bool {
1156        let wanted = self
1157            .features
1158            .as_ref()
1159            .and_then(|features| features.plain_response_body.as_ref());
1160        match wanted {
1161            None => false,
1162            Some(PlainBody::Any) => true,
1163            Some(PlainBody::Types(types)) => is_of(types),
1164        }
1165    }
1166
1167    /// Adds a modify body handler to the context.
1168    ///
1169    /// # Arguments
1170    /// * `name` - The name of the handler.
1171    /// * `handler` - The handler to add.
1172    #[inline]
1173    pub fn add_modify_body_handler(
1174        &mut self,
1175        name: &str,
1176        handler: Box<dyn ModifyResponseBody>,
1177    ) {
1178        let features = self.features.get_or_insert_default();
1179        let handlers = features
1180            .modify_body_handlers
1181            .get_or_insert_with(AHashMap::new);
1182        handlers.insert(name.to_string(), handler);
1183    }
1184
1185    /// Has `handler` see the body of the request as the proxy passes it
1186    /// to the upstream.
1187    #[inline]
1188    pub fn add_request_body_handler(
1189        &mut self,
1190        handler: Box<dyn HandleRequestBody>,
1191    ) {
1192        self.features
1193            .get_or_insert_default()
1194            .request_body_handlers
1195            .get_or_insert_default()
1196            .push(handler);
1197    }
1198
1199    /// Returns the modify body handler by name.
1200    #[inline]
1201    pub fn get_modify_body_handler(
1202        &mut self,
1203        name: &str,
1204    ) -> Option<&mut Box<dyn ModifyResponseBody>> {
1205        self.features
1206            .as_mut()
1207            .and_then(|f| f.modify_body_handlers.as_mut())
1208            .and_then(|h| h.get_mut(name))
1209    }
1210
1211    // A private helper function to filter out time values that are too large (over an hour),
1212    // which might indicate an error or uninitialized state.
1213    #[inline]
1214    fn get_time_field(&self, field: Option<i32>) -> Option<u32> {
1215        if let Some(value) = field
1216            && value >= 0
1217        {
1218            return Some(value as u32);
1219        }
1220        None
1221    }
1222
1223    /// Returns the upstream response time if it's less than one hour, otherwise None.
1224    /// This helps filter out potentially invalid or stale timing data.
1225    ///
1226    /// Returns: Option<u64> representing milliseconds, or None if time exceeds 1 hour.
1227    #[inline]
1228    pub fn get_upstream_response_time(&self) -> Option<u32> {
1229        self.get_time_field(self.timing.upstream_response)
1230    }
1231
1232    /// Returns the upstream connect time if it's less than one hour, otherwise None.
1233    /// This helps filter out potentially invalid or stale timing data.
1234    ///
1235    /// Returns: Option<u64> representing milliseconds, or None if time exceeds 1 hour.
1236    #[inline]
1237    pub fn get_upstream_connect_time(&self) -> Option<u32> {
1238        self.get_time_field(self.timing.upstream_connect)
1239    }
1240
1241    /// Returns the upstream processing time if it's less than one hour, otherwise None.
1242    /// This helps filter out potentially invalid or stale timing data.
1243    ///
1244    /// Returns: Option<u64> representing milliseconds, or None if time exceeds 1 hour.
1245    #[inline]
1246    pub fn get_upstream_processing_time(&self) -> Option<u32> {
1247        self.get_time_field(self.timing.upstream_processing)
1248    }
1249
1250    /// Adds a plugin processing time to the context.
1251    ///
1252    /// # Arguments
1253    /// * `name` - The name of the plugin.
1254    /// * `time` - The time taken by the plugin in milliseconds.
1255    #[inline]
1256    pub fn add_plugin_processing_time(&mut self, name: &Arc<str>, time: u32) {
1257        // Lazily initialize features and the processing times vector.
1258        let features = self.features.get_or_insert_default();
1259        let times = features
1260            .plugin_processing_times
1261            .get_or_insert_with(|| Vec::with_capacity(5));
1262        if let Some(item) = times.iter_mut().find(|item| &item.0 == name) {
1263            item.1 += time;
1264        } else {
1265            times.push((Arc::clone(name), time));
1266        }
1267    }
1268
1269    /// Appends a formatted value to the provided log buffer based on the given key.
1270    /// Handles various metrics including connection info, timing data, and TLS details.
1271    ///
1272    /// Resolves `key` on every call; a caller that knows the key ahead of
1273    /// time (the access log format) should parse it into a [`CtxLogField`]
1274    /// once and use [`Ctx::append_log_field`].
1275    ///
1276    /// # Arguments
1277    /// * `buf` - The BytesMut buffer to append the value to.
1278    /// * `key` - The key identifying which state value to format and append.
1279    #[inline]
1280    pub fn append_log_value(&self, buf: &mut BytesMut, key: &str) {
1281        // Unknown keys append nothing.
1282        if let Ok(field) = key.parse::<CtxLogField>() {
1283            self.append_log_field(buf, field);
1284        }
1285    }
1286
1287    /// Appends the value of `field` to the log buffer.
1288    pub fn append_log_field(&self, buf: &mut BytesMut, field: CtxLogField) {
1289        // A macro to simplify formatting and appending optional time values.
1290        macro_rules! append_time {
1291            // Append raw milliseconds.
1292            ($val:expr) => {
1293                if let Some(ms) = $val {
1294                    buf.extend(itoa::Buffer::new().format(ms).as_bytes());
1295                }
1296            };
1297            // Append human-readable formatted time.
1298            ($val:expr, human) => {
1299                if let Some(ms) = $val {
1300                    format_duration(buf, ms as u64);
1301                }
1302            };
1303        }
1304
1305        match field {
1306            CtxLogField::ConnectionId => {
1307                buf.extend(itoa::Buffer::new().format(self.conn.id).as_bytes());
1308            },
1309            CtxLogField::UpstreamReused => {
1310                if self.upstream.reused {
1311                    buf.extend(b"true");
1312                } else {
1313                    buf.extend(b"false");
1314                }
1315            },
1316            CtxLogField::UpstreamStatus => {
1317                if let Some(status) = &self.upstream.status {
1318                    buf.extend_from_slice(status.as_str().as_bytes());
1319                } else {
1320                    buf.extend_from_slice(b"-");
1321                }
1322            },
1323            CtxLogField::UpstreamAddr => {
1324                buf.extend(self.upstream.address.as_bytes())
1325            },
1326            CtxLogField::Processing => buf.extend(
1327                itoa::Buffer::new()
1328                    .format(self.state.processing_count)
1329                    .as_bytes(),
1330            ),
1331            CtxLogField::UpstreamConnected => {
1332                if let Some(value) = self.upstream.connected_count {
1333                    buf.extend(itoa::Buffer::new().format(value).as_bytes());
1334                }
1335            },
1336
1337            // Timing fields
1338            CtxLogField::UpstreamConnectTime => {
1339                append_time!(self.get_upstream_connect_time())
1340            },
1341            CtxLogField::UpstreamConnectTimeHuman => {
1342                append_time!(self.get_upstream_connect_time(), human)
1343            },
1344
1345            CtxLogField::UpstreamProcessingTime => {
1346                append_time!(self.get_upstream_processing_time())
1347            },
1348            CtxLogField::UpstreamProcessingTimeHuman => {
1349                append_time!(self.get_upstream_processing_time(), human)
1350            },
1351            CtxLogField::UpstreamResponseTime => {
1352                append_time!(self.get_upstream_response_time())
1353            },
1354            CtxLogField::UpstreamResponseTimeHuman => {
1355                append_time!(self.get_upstream_response_time(), human)
1356            },
1357            CtxLogField::UpstreamTcpConnectTime => {
1358                append_time!(self.timing.upstream_tcp_connect)
1359            },
1360            CtxLogField::UpstreamTcpConnectTimeHuman => {
1361                append_time!(self.timing.upstream_tcp_connect, human)
1362            },
1363            CtxLogField::UpstreamTlsHandshakeTime => {
1364                append_time!(self.timing.upstream_tls_handshake)
1365            },
1366            CtxLogField::UpstreamTlsHandshakeTimeHuman => {
1367                append_time!(self.timing.upstream_tls_handshake, human)
1368            },
1369            CtxLogField::UpstreamConnectOffloadWaitTime => {
1370                append_time!(self.timing.upstream_connect_offload_wait)
1371            },
1372            CtxLogField::UpstreamConnectOffloadWaitTimeHuman => {
1373                append_time!(self.timing.upstream_connect_offload_wait, human)
1374            },
1375            CtxLogField::UpstreamConnectionTime => {
1376                append_time!(self.timing.upstream_connection_duration)
1377            },
1378            CtxLogField::UpstreamConnectionTimeHuman => {
1379                append_time!(self.timing.upstream_connection_duration, human)
1380            },
1381            CtxLogField::ConnectionTime => {
1382                append_time!(Some(self.timing.connection_duration))
1383            },
1384            CtxLogField::ConnectionTimeHuman => {
1385                append_time!(Some(self.timing.connection_duration), human)
1386            },
1387
1388            // Other fields
1389            CtxLogField::Location => {
1390                if !self.upstream.location.is_empty() {
1391                    buf.extend(self.upstream.location.as_bytes())
1392                }
1393            },
1394            CtxLogField::ConnectionReused => {
1395                if self.conn.reused {
1396                    buf.extend(b"true");
1397                } else {
1398                    buf.extend(b"false");
1399                }
1400            },
1401            CtxLogField::TlsVersion => {
1402                if let Some(value) = &self.conn.tls_version {
1403                    buf.extend(value.as_bytes());
1404                }
1405            },
1406            CtxLogField::TlsCipher => {
1407                if let Some(value) = &self.conn.tls_cipher {
1408                    buf.extend(value.as_bytes());
1409                }
1410            },
1411            CtxLogField::TlsClientSubject => {
1412                if let Some(cert) = &self.conn.tls_client_cert {
1413                    buf.extend(cert.subject.as_bytes());
1414                }
1415            },
1416            CtxLogField::TlsClientFingerprint => {
1417                if let Some(cert) = &self.conn.tls_client_cert {
1418                    buf.extend(cert.fingerprint.as_bytes());
1419                }
1420            },
1421            CtxLogField::TlsClientSerial => {
1422                if let Some(cert) = &self.conn.tls_client_cert {
1423                    buf.extend(cert.serial.as_bytes());
1424                }
1425            },
1426            CtxLogField::TlsClientVerified => {
1427                if self.conn.tls_client_cert.is_some() {
1428                    buf.extend(b"true");
1429                } else {
1430                    buf.extend(b"false");
1431                }
1432            },
1433            CtxLogField::Ja4 => {
1434                if let Some(fingerprint) = &self.conn.ja4 {
1435                    buf.extend(fingerprint.ja4().as_bytes());
1436                }
1437            },
1438            CtxLogField::Ja4R => {
1439                if let Some(fingerprint) = &self.conn.ja4 {
1440                    buf.extend(fingerprint.ja4_r().as_bytes());
1441                }
1442            },
1443            CtxLogField::Ja4O => {
1444                if let Some(fingerprint) = &self.conn.ja4 {
1445                    buf.extend(fingerprint.ja4_o().as_bytes());
1446                }
1447            },
1448            CtxLogField::Ja4Ro => {
1449                if let Some(fingerprint) = &self.conn.ja4 {
1450                    buf.extend(fingerprint.ja4_ro().as_bytes());
1451                }
1452            },
1453            CtxLogField::TlsHandshakeTime => {
1454                append_time!(self.timing.tls_handshake)
1455            },
1456            CtxLogField::TlsHandshakeTimeHuman => {
1457                append_time!(self.timing.tls_handshake, human)
1458            },
1459            CtxLogField::CompressionTime => {
1460                if let Some(feature) = &self.features
1461                    && let Some(value) = &feature.compression_stat
1462                {
1463                    append_time!(Some(value.duration.as_millis() as u64))
1464                }
1465            },
1466            CtxLogField::CompressionTimeHuman => {
1467                if let Some(feature) = &self.features
1468                    && let Some(value) = &feature.compression_stat
1469                {
1470                    append_time!(Some(value.duration.as_millis() as u64), human)
1471                }
1472            },
1473            CtxLogField::CompressionRatio => {
1474                if let Some(feature) = &self.features
1475                    && let Some(value) = &feature.compression_stat
1476                {
1477                    // One decimal place without allocating via `format!`.
1478                    let tenths = (value.ratio() * 10.0).round() as u64;
1479                    buf.extend(
1480                        itoa::Buffer::new().format(tenths / 10).as_bytes(),
1481                    );
1482                    buf.extend_from_slice(b".");
1483                    buf.extend(
1484                        itoa::Buffer::new().format(tenths % 10).as_bytes(),
1485                    );
1486                }
1487            },
1488            CtxLogField::CacheLookupTime => {
1489                append_time!(self.timing.cache_lookup)
1490            },
1491            CtxLogField::CacheLookupTimeHuman => {
1492                append_time!(self.timing.cache_lookup, human)
1493            },
1494            CtxLogField::CacheLockTime => {
1495                append_time!(self.timing.cache_lock)
1496            },
1497            CtxLogField::CacheLockTimeHuman => {
1498                append_time!(self.timing.cache_lock, human)
1499            },
1500            CtxLogField::ServiceTime => {
1501                append_time!(Some(self.timing.created_at.elapsed().as_millis()))
1502            },
1503            CtxLogField::ServiceTimeHuman => {
1504                append_time!(
1505                    Some(self.timing.created_at.elapsed().as_millis()),
1506                    human
1507                )
1508            },
1509        }
1510    }
1511
1512    /// Generates a Server-Timing header value based on the context's timing metrics.
1513    ///
1514    /// The Server-Timing header allows servers to communicate performance metrics
1515    /// about the request-response cycle to the client. This implementation includes
1516    /// various timing metrics like connection time, processing time, and cache operations.
1517    ///
1518    /// Returns a String containing the formatted Server-Timing header value.
1519    pub fn generate_server_timing(&self) -> String {
1520        let mut timing_str = String::with_capacity(200);
1521        // Flag to track if this is the first timing entry, to handle commas correctly.
1522        let mut first = true;
1523
1524        // Macro to add a timing entry to the string.
1525        macro_rules! add_timing {
1526            ($name:expr, $dur:expr) => {
1527                if !first {
1528                    timing_str.push_str(", ");
1529                }
1530                // Ignore the write! result as it's unlikely to fail with a String.
1531                let _ = write!(&mut timing_str, "{};dur={}", $name, $dur);
1532                first = false;
1533            };
1534        }
1535
1536        // Aggregate and add upstream timings.
1537        let mut upstream_time = 0;
1538        if let Some(time) = self.get_upstream_connect_time() {
1539            upstream_time += time;
1540            add_timing!("upstream.connect", time);
1541        }
1542        if let Some(time) = self.get_upstream_processing_time() {
1543            upstream_time += time;
1544            add_timing!("upstream.processing", time);
1545        }
1546        if upstream_time > 0 {
1547            add_timing!("upstream", upstream_time);
1548        }
1549
1550        // Aggregate and add cache timings.
1551        let mut cache_time = 0;
1552        if let Some(time) = self.timing.cache_lookup {
1553            cache_time += time;
1554            add_timing!("cache.lookup", time);
1555        }
1556        if let Some(time) = self.timing.cache_lock {
1557            cache_time += time;
1558            add_timing!("cache.lock", time);
1559        }
1560        if cache_time > 0 {
1561            add_timing!("cache", cache_time);
1562        }
1563
1564        // Aggregate and add plugin timings.
1565        if let Some(features) = &self.features
1566            && let Some(times) = &features.plugin_processing_times
1567        {
1568            let mut plugin_time: u32 = 0;
1569            for (name, time) in times {
1570                if *time == 0 {
1571                    continue;
1572                }
1573                plugin_time += time;
1574                // Write directly into the shared buffer — avoid a per-plugin
1575                // temporary `"plugin." + name` String.
1576                if !first {
1577                    timing_str.push_str(", ");
1578                }
1579                let _ = write!(&mut timing_str, "plugin.{name};dur={time}");
1580                first = false;
1581            }
1582            if plugin_time > 0 {
1583                add_timing!("plugin", plugin_time);
1584            }
1585        }
1586
1587        // Add the total service time, which is always present.
1588        let service_time = self.timing.created_at.elapsed().as_millis();
1589        // Add a separator if other timings were already added.
1590        if !first {
1591            timing_str.push_str(", ");
1592        }
1593        // Write the final timing directly.
1594        let _ = write!(&mut timing_str, "total;dur={}", service_time);
1595
1596        timing_str
1597    }
1598
1599    /// Pushes a single cache key component to the context.
1600    #[inline]
1601    pub fn push_cache_key(&mut self, key: String) {
1602        let cache_info = self.cache.get_or_insert_default();
1603        cache_info
1604            .keys
1605            .get_or_insert_with(|| Vec::with_capacity(2))
1606            .push(key);
1607    }
1608
1609    /// Extends the cache key components with a vector of keys.
1610    #[inline]
1611    pub fn extend_cache_keys(&mut self, keys: Vec<String>) {
1612        let cache_info = self.cache.get_or_insert_default();
1613        cache_info
1614            .keys
1615            .get_or_insert_with(|| Vec::with_capacity(keys.len() + 2))
1616            .extend(keys);
1617    }
1618    /// Adds `current` to the cache key as the one of `alternatives` this
1619    /// request stands for. It may be empty: the place is noted all the
1620    /// same, for the request that has to know what else could be there.
1621    #[inline]
1622    pub fn push_cache_key_variant(
1623        &mut self,
1624        current: Vec<String>,
1625        alternatives: Arc<Vec<Vec<String>>>,
1626    ) {
1627        let cache_info = self.cache.get_or_insert_default();
1628        let keys = cache_info
1629            .keys
1630            .get_or_insert_with(|| Vec::with_capacity(current.len() + 2));
1631        cache_info
1632            .key_variants
1633            .get_or_insert_default()
1634            .push(CacheKeyVariant {
1635                at: keys.len(),
1636                len: current.len(),
1637                alternatives,
1638            });
1639        keys.extend(current);
1640    }
1641
1642    /// Every list of key components a request for this url can have: the
1643    /// one of this request, with each [`CacheKeyVariant`] in it replaced by
1644    /// each of its alternatives. The list of this request itself when it
1645    /// has no variants, or when there would be too many lists to go
1646    /// through.
1647    pub fn cache_key_alternatives(&self) -> Vec<Vec<String>> {
1648        /// More than any sensible set of plugins gives: the codings times
1649        /// the selections of four image formats is 64.
1650        const MAX_ALTERNATIVES: usize = 1024;
1651        let Some(cache_info) = &self.cache else {
1652            return vec![vec![]];
1653        };
1654        let keys = cache_info.keys.as_deref().unwrap_or_default();
1655        let variants = cache_info.key_variants.as_deref().unwrap_or_default();
1656        let count = variants.iter().try_fold(1usize, |count, variant| {
1657            count.checked_mul(variant.alternatives.len().max(1))
1658        });
1659        if variants.is_empty() {
1660            return vec![keys.to_vec()];
1661        }
1662        if count.is_none_or(|n| n > MAX_ALTERNATIVES) {
1663            // Said, since whoever asked goes on with less than all of them.
1664            tracing::warn!(
1665                target: "pingap::core",
1666                variants = variants.len(),
1667                "too many cache key alternatives, only the key of this request is used"
1668            );
1669            return vec![keys.to_vec()];
1670        }
1671        let mut lists = vec![Vec::with_capacity(keys.len())];
1672        let mut cursor = 0;
1673        for variant in variants {
1674            // What other plugins put in between is the same in all of them.
1675            let fixed = keys.get(cursor..variant.at).unwrap_or_default();
1676            lists = lists
1677                .into_iter()
1678                .flat_map(|list: Vec<String>| {
1679                    variant.alternatives.iter().map(move |alternative| {
1680                        let mut list = list.clone();
1681                        list.extend_from_slice(fixed);
1682                        list.extend_from_slice(alternative);
1683                        list
1684                    })
1685                })
1686                .collect();
1687            cursor = variant.at + variant.len;
1688        }
1689        let rest = keys.get(cursor..).unwrap_or_default();
1690        for list in lists.iter_mut() {
1691            list.extend_from_slice(rest);
1692        }
1693        lists
1694    }
1695
1696    /// Updates the upstream timing from the digest.
1697    #[inline]
1698    pub fn update_upstream_timing_from_digest(
1699        &mut self,
1700        digest: &Digest,
1701        reused: bool,
1702    ) {
1703        let detail = get_digest_detail(digest);
1704        self.timing.upstream_connection_duration = Some(detail.connection_time);
1705        if reused {
1706            return;
1707        }
1708
1709        // pingora times each layer on a monotonic clock: the TCP connect on
1710        // the transport entry, the handshake on the TLS entry. Prefer those.
1711        if let Some(tcp_connect) = detail.tcp_connect {
1712            self.timing.upstream_tcp_connect = Some(tcp_connect as i32);
1713            self.timing.upstream_tls_handshake =
1714                detail.tls_handshake.map(|value| value as i32);
1715            self.timing.upstream_connect_offload_wait =
1716                detail.connect_offload_wait.map(|value| value as i32);
1717            return;
1718        }
1719
1720        // Fallback for a stream that carries no measurement: split pingap's
1721        // own end-to-end connect timer by the wall-clock gap between the
1722        // layers' timestamps. Coarser, and the TCP share also absorbs
1723        // whatever the connector did around the connect.
1724        let upstream_connect_time =
1725            self.timing.upstream_connect.unwrap_or_default();
1726        let mut upstream_tcp_connect = upstream_connect_time;
1727        if detail.tls_established > detail.tcp_established {
1728            let latency =
1729                (detail.tls_established - detail.tcp_established) as i32;
1730            upstream_tcp_connect -= latency;
1731            self.timing.upstream_tls_handshake = Some(latency);
1732        }
1733        if upstream_tcp_connect > 0 {
1734            self.timing.upstream_tcp_connect = Some(upstream_tcp_connect);
1735        }
1736    }
1737}
1738
1739/// Writes the host part of a cache key.
1740///
1741/// In lower case: a host name is case-insensitive, and one spelling keeps
1742/// `Example.com` from caching beside `example.com`. Anything that is not a
1743/// host name character is written as `%XX`. A `Host` header is whatever the
1744/// client sent, and written as it came `a.com/static` followed by the path
1745/// `/app.js` made the key of `a.com` and `/static/app.js`: one request
1746/// could store its response under the key of another url.
1747fn push_key_host(key: &mut String, host: &str) {
1748    for byte in host.bytes() {
1749        match byte {
1750            b'a'..=b'z'
1751            | b'0'..=b'9'
1752            | b'.'
1753            | b'-'
1754            | b'_'
1755            | b':'
1756            | b'['
1757            | b']' => key.push(byte as char),
1758            b'A'..=b'Z' => key.push(byte.to_ascii_lowercase() as char),
1759            _ => {
1760                let _ = write!(key, "%{byte:02X}");
1761            },
1762        }
1763    }
1764}
1765
1766/// Generates the cache key of a request.
1767///
1768/// The primary is, in this order: the namespace, the custom keys (each
1769/// followed by `:`), the method, `:`, the host in lower case without its
1770/// port, then the path and the query. `user_tag` is the namespace.
1771///
1772/// The host is part of the key whatever the protocol. It used to come from
1773/// the request uri alone, which carries it in HTTP/2 but not in HTTP/1.1:
1774/// two domains behind one cache plugin answered with each other's pages
1775/// over HTTP/1.1, and a `PURGE` sent over one protocol missed what the other
1776/// had stored. Scheme and port are left out on purpose, so a `PURGE` sent
1777/// to an internal plain-http listener with the right `Host` clears what the
1778/// public https listener cached.
1779///
1780/// # Arguments
1781/// * `ctx` - The Ctx context containing cache configuration.
1782/// * `method` - The HTTP method as a string.
1783/// * `header` - The request the key is for: its host, path and query.
1784pub fn get_cache_key(
1785    ctx: &Ctx,
1786    method: &str,
1787    header: &RequestHeader,
1788) -> CacheKey {
1789    let Some(cache_info) = &ctx.cache else {
1790        // Return an empty key if cache is not configured for this context.
1791        return CacheKey::new("", "");
1792    };
1793    let namespace = cache_info.namespace.as_ref().map_or("", |v| v);
1794    // As it was sent, see `get_request_host`.
1795    let host = get_request_host(header).unwrap_or_default();
1796    let path = header.uri.path();
1797    // By the rule of the plugin where it has one: the query that was
1798    // settled when the key of the request was made, or, for a key that is
1799    // made of another request (a purge), the rule applied to that one.
1800    let made;
1801    let query = match (&cache_info.key_query, &cache_info.query_rule) {
1802        (Some(query), _) => (!query.is_empty()).then_some(query.as_str()),
1803        (None, Some(rule)) => {
1804            made = rule.key_query(header.uri.query().unwrap_or_default());
1805            (!made.is_empty()).then_some(made.as_str())
1806        },
1807        (None, None) => header.uri.query(),
1808    };
1809    // pingora's CacheKey used to take the namespace as its own argument and
1810    // hashed `namespace ++ primary` as one unframed byte string. That argument
1811    // is gone, so the namespace is written straight in front of the primary
1812    // here. The storage layer still partitions by namespace, so it also
1813    // travels in `user_tag`, which is carried alongside the key but never
1814    // hashed.
1815    let header_keys = cache_info.header_key_components(header);
1816    let keys_len = cache_info
1817        .keys
1818        .iter()
1819        .flatten()
1820        .chain(&header_keys)
1821        .map(|s| s.len() + 1)
1822        .sum::<usize>();
1823    let mut key_buf = String::with_capacity(
1824        namespace.len()
1825            + keys_len
1826            + method.len()
1827            + 1
1828            + host.len()
1829            + path.len()
1830            + query.map_or(0, |query| query.len() + 1),
1831    );
1832    key_buf.push_str(namespace);
1833    // Custom key components first, each followed by ':'.
1834    for k in cache_info.keys.iter().flatten().chain(&header_keys) {
1835        key_buf.push_str(k);
1836        key_buf.push(':');
1837    }
1838    // Then "METHOD:host/path?query".
1839    key_buf.push_str(method);
1840    key_buf.push(':');
1841    push_key_host(&mut key_buf, host);
1842    key_buf.push_str(path);
1843    if let Some(query) = query {
1844        key_buf.push('?');
1845        key_buf.push_str(query);
1846    }
1847
1848    CacheKey::new(key_buf, namespace)
1849}
1850
1851#[cfg(test)]
1852mod tests {
1853    use super::*;
1854    use bytes::Bytes;
1855    use bytes::BytesMut;
1856    use http::Uri;
1857    use pingora::cache::key::CacheHashKey;
1858    use pingora::protocols::tls::SslDigest;
1859    use pingora::protocols::tls::SslDigestExtension;
1860    use pretty_assertions::assert_eq;
1861    use std::{sync::Arc, time::Duration};
1862
1863    /// A body is held to its rate by what has gone out and how long that
1864    /// took, once the part that is free of it is past.
1865    #[test]
1866    fn test_body_pace() {
1867        let start = Instant::now();
1868        let at = |ms: u64| start + Duration::from_millis(ms);
1869        let ms = |delay: Option<Duration>| delay.map(|d| d.as_millis());
1870
1871        // 1000 bytes a second, from the first byte.
1872        let mut pace = BodyPace::new(1000, 0);
1873        assert_eq!(Some(500), ms(pace.delay_at(500, at(0))));
1874        // On time after the wait: only the new chunk is owed.
1875        assert_eq!(Some(500), ms(pace.delay_at(500, at(500))));
1876        // A client that took its time has used it up already.
1877        assert_eq!(None, ms(pace.delay_at(1000, at(5000))));
1878        // And is not let run ahead by that afterwards: three thousand
1879        // more bytes are three more seconds.
1880        assert_eq!(Some(3000), ms(pace.delay_at(3000, at(5000))));
1881
1882        // The first 2000 bytes as fast as they go.
1883        let mut pace = BodyPace::new(1000, 2000);
1884        assert_eq!(None, ms(pace.delay_at(1500, at(0))));
1885        // 500 of these are free, 500 are not.
1886        assert_eq!(Some(500), ms(pace.delay_at(1000, at(10))));
1887        assert_eq!(Some(1000), ms(pace.delay_at(1000, at(510))));
1888
1889        // No rate at all is the slowest there is, not a division by zero.
1890        let mut pace = BodyPace::new(0, 0);
1891        assert_eq!(Some(2000), ms(pace.delay_at(2, at(0))));
1892        // An empty chunk asks for nothing.
1893        let mut pace = BodyPace::new(1000, 0);
1894        assert_eq!(None, ms(pace.delay_at(0, at(0))));
1895
1896        // Asked before a chunk: the wait is for what went before it.
1897        let mut pace = BodyPace::new(1000, 0);
1898        // nothing went before the first, however large it is
1899        assert_eq!(None, ms(pace.delay_before_at(2000, at(0))));
1900        assert_eq!(Some(2000), ms(pace.delay_before_at(500, at(0))));
1901        // half a second later the 500 are paid for
1902        assert_eq!(Some(500), ms(pace.delay_before_at(500, at(2000))));
1903        assert_eq!(None, ms(pace.delay_before_at(500, at(3000))));
1904        // a client that took its time owes nothing, and is not let run
1905        // ahead afterwards
1906        assert_eq!(None, ms(pace.delay_before_at(1000, at(9000))));
1907        assert_eq!(Some(1000), ms(pace.delay_before_at(1, at(9000))));
1908        // with the first bytes free
1909        let mut pace = BodyPace::new(1000, 2000);
1910        assert_eq!(None, ms(pace.delay_before_at(1500, at(0))));
1911        assert_eq!(None, ms(pace.delay_before_at(1000, at(0))));
1912        assert_eq!(Some(500), ms(pace.delay_before_at(1000, at(0))));
1913    }
1914
1915    /// A `PURGE` names a url and no coding or image format: it has to
1916    /// find the key of every request that named one.
1917    #[test]
1918    fn test_ask_with_key_query() {
1919        let ask = |uri: &str, key_query: Option<&str>| {
1920            let mut header = RequestHeader::build("GET", b"/", None).unwrap();
1921            header.set_uri(uri.parse().unwrap());
1922            let info = CacheInfo {
1923                key_query: key_query.map(str::to_string),
1924                ..Default::default()
1925            };
1926            assert_eq!(true, info.ask_with_key_query(&mut header));
1927            header.uri.to_string()
1928        };
1929        // no rule for the query: the request is sent as it came
1930        assert_eq!("/a?b=2&a=1", ask("/a?b=2&a=1", None));
1931        // the query of the key, whatever else the client sent
1932        assert_eq!(
1933            "/a?a=1&b=2",
1934            ask("/a?b=2&utm_source=x&a=1", Some("a=1&b=2"))
1935        );
1936        // spellings the rule does not know do not reach the upstream
1937        assert_eq!("/list", ask("/list?p%61ge=2", Some("")));
1938        assert_eq!("/list", ask("/list?x=1;page=2", Some("")));
1939        assert_eq!(
1940            "/list?size=10",
1941            ask("/list?Page=2&size=10", Some("size=10"))
1942        );
1943        // nothing to change
1944        assert_eq!("/a?a=1", ask("/a?a=1", Some("a=1")));
1945        assert_eq!("/a", ask("/a", Some("")));
1946        // an empty query is no query, as in the key
1947        assert_eq!("/a", ask("/a?", Some("")));
1948
1949        // The query is settled when the key is made, from the request as
1950        // it is by then; until then the rule is what is applied.
1951        let rule =
1952            Arc::new(CacheQueryRule::Ignore(vec!["utm_source".to_string()]));
1953        let mut info = CacheInfo {
1954            query_rule: Some(rule),
1955            ..Default::default()
1956        };
1957        let mut header = RequestHeader::build("GET", b"/", None).unwrap();
1958        header.set_uri("/a?b=2&utm_source=x&a=1".parse().unwrap());
1959        let mut upstream = header.clone();
1960        assert_eq!(true, info.ask_with_key_query(&mut upstream));
1961        assert_eq!("/a?a=1&b=2", upstream.uri.to_string());
1962        // a plugin after the cache took a parameter out
1963        header.set_uri("/a?utm_source=x&a=1".parse().unwrap());
1964        info.settle_key_query(&header);
1965        assert_eq!(Some("a=1".to_string()), info.key_query);
1966        let mut ctx = Ctx {
1967            cache: Some(Box::new(info)),
1968            ..Default::default()
1969        };
1970        assert_eq!(
1971            Some("GET:/a?a=1"),
1972            get_cache_key(&ctx, "GET", &header).primary_key_str()
1973        );
1974        // and a key that is made of another request goes by the rule
1975        if let Some(info) = ctx.cache.as_mut() {
1976            info.key_query = None;
1977        }
1978        header.set_uri("/a?z=9&utm_source=x&b=1".parse().unwrap());
1979        assert_eq!(
1980            Some("GET:/a?b=1&z=9"),
1981            get_cache_key(&ctx, "GET", &header).primary_key_str()
1982        );
1983        // the rest of the uri is kept (HTTP/2 carries the authority)
1984        assert_eq!(
1985            "https://example.com/a?a=1",
1986            ask("https://example.com/a?z=9&a=1", Some("a=1"))
1987        );
1988    }
1989
1990    #[test]
1991    fn test_cache_key_alternatives() {
1992        let list = |items: &[&str]| {
1993            items
1994                .iter()
1995                .map(|item| item.to_string())
1996                .collect::<Vec<_>>()
1997        };
1998        let mut ctx = Ctx::default();
1999        assert_eq!(vec![list(&[])], ctx.cache_key_alternatives());
2000
2001        ctx.push_cache_key("fixed".to_string());
2002        assert_eq!(vec![list(&["fixed"])], ctx.cache_key_alternatives());
2003
2004        ctx.push_cache_key_variant(
2005            list(&["gzip"]),
2006            Arc::new(vec![list(&[]), list(&["gzip"]), list(&["br"])]),
2007        );
2008        ctx.push_cache_key("lang".to_string());
2009        // Nothing of this one in the key of this request.
2010        ctx.push_cache_key_variant(
2011            list(&[]),
2012            Arc::new(vec![list(&[]), list(&["avif", "webp"])]),
2013        );
2014        assert_eq!(
2015            Some(list(&["fixed", "gzip", "lang"])),
2016            ctx.cache.as_ref().unwrap().keys
2017        );
2018        assert_eq!(
2019            vec![
2020                list(&["fixed", "lang"]),
2021                list(&["fixed", "lang", "avif", "webp"]),
2022                list(&["fixed", "gzip", "lang"]),
2023                list(&["fixed", "gzip", "lang", "avif", "webp"]),
2024                list(&["fixed", "br", "lang"]),
2025                list(&["fixed", "br", "lang", "avif", "webp"]),
2026            ],
2027            ctx.cache_key_alternatives()
2028        );
2029
2030        // Too many to go through: the key of the request itself.
2031        let many: Vec<_> =
2032            (0..40).map(|index| vec![index.to_string()]).collect();
2033        let mut ctx = Ctx::default();
2034        ctx.push_cache_key_variant(list(&["1"]), Arc::new(many.clone()));
2035        ctx.push_cache_key_variant(list(&["2"]), Arc::new(many));
2036        assert_eq!(vec![list(&["1", "2"])], ctx.cache_key_alternatives());
2037    }
2038
2039    #[test]
2040    fn test_ctx_new() {
2041        let ctx = Ctx::new();
2042        // Check that created_at is a recent timestamp.
2043        // It should be within the last 100ms.
2044        let elapsed_ms = ctx.timing.created_at.elapsed().as_millis();
2045        assert!(elapsed_ms < 100, "created_at should be a recent timestamp");
2046        // Check that other fields are correctly defaulted.
2047        assert!(ctx.cache.is_none());
2048        assert!(ctx.features.is_none());
2049        assert_eq!(ctx.conn.id, 0);
2050    }
2051
2052    /// Tests both adding and getting variables.
2053    /// The certificate of a client in the access log, and that there is
2054    /// nothing to print without one.
2055    #[test]
2056    fn test_tls_client_cert_log_fields() {
2057        let mut ctx = Ctx::new();
2058        let print = |ctx: &Ctx, key: &str| {
2059            let mut buf = BytesMut::new();
2060            ctx.append_log_value(&mut buf, key);
2061            String::from_utf8_lossy(&buf).to_string()
2062        };
2063        assert_eq!("", print(&ctx, "tls_client_subject"));
2064        assert_eq!("false", print(&ctx, "tls_client_verified"));
2065        ctx.conn.tls_client_cert = Some(Arc::new(TlsClientCert {
2066            subject: "O=Example, CN=device-42".to_string(),
2067            fingerprint: "b545db7a".to_string(),
2068            serial: "3429".to_string(),
2069        }));
2070        assert_eq!(
2071            "O=Example, CN=device-42",
2072            print(&ctx, "tls_client_subject")
2073        );
2074        assert_eq!("b545db7a", print(&ctx, "tls_client_fingerprint"));
2075        assert_eq!("3429", print(&ctx, "tls_client_serial"));
2076        assert_eq!("true", print(&ctx, "tls_client_verified"));
2077    }
2078
2079    #[test]
2080    fn test_plugin_notes() {
2081        let mut ctx = Ctx::new();
2082        assert_eq!(None, ctx.get_plugin_note("a"));
2083        ctx.set_plugin_note("a", "gzip");
2084        ctx.set_plugin_note("b", "");
2085        assert_eq!(Some("gzip"), ctx.get_plugin_note("a"));
2086        assert_eq!(Some(""), ctx.get_plugin_note("b"));
2087        assert_eq!(None, ctx.get_plugin_note("c"));
2088        // The last one said is the one kept.
2089        ctx.set_plugin_note("a", "br");
2090        assert_eq!(Some("br"), ctx.get_plugin_note("a"));
2091        assert_eq!(
2092            2,
2093            ctx.features
2094                .as_ref()
2095                .unwrap()
2096                .plugin_notes
2097                .as_ref()
2098                .unwrap()
2099                .len()
2100        );
2101    }
2102
2103    #[test]
2104    fn test_add_and_get_variable() {
2105        let mut ctx = Ctx::new();
2106        assert!(
2107            ctx.get_variable("key1").is_none(),
2108            "Should be None before adding"
2109        );
2110
2111        ctx.add_variable("key1", "value1");
2112        ctx.add_variable("key2", "value2");
2113
2114        assert_eq!(ctx.get_variable("key1"), Some("value1"));
2115        assert_eq!(ctx.get_variable("key2"), Some("value2"));
2116        assert_eq!(ctx.get_variable("nonexistent"), None);
2117    }
2118
2119    /// Tests the helper functions for getting filtered time values.
2120    #[test]
2121    fn test_get_time_field() {
2122        let mut ctx = Ctx::new();
2123
2124        // Test with a valid time
2125        ctx.timing.upstream_response = Some(100);
2126        assert_eq!(ctx.get_upstream_response_time(), Some(100));
2127
2128        // Test with a time is negative
2129        ctx.timing.upstream_response = Some(-1);
2130        assert_eq!(
2131            ctx.get_upstream_response_time(),
2132            None,
2133            "Time exceeding one hour should be None"
2134        );
2135
2136        // Test with None
2137        ctx.timing.upstream_response = None;
2138        assert_eq!(ctx.get_upstream_response_time(), None);
2139    }
2140
2141    /// Every log field name round-trips through the enum, and the two
2142    /// entry points agree.
2143    #[test]
2144    fn test_ctx_log_field_names() {
2145        for (name, field) in [
2146            ("connection_id", CtxLogField::ConnectionId),
2147            (
2148                "upstream_tcp_connect_time_human",
2149                CtxLogField::UpstreamTcpConnectTimeHuman,
2150            ),
2151            (
2152                "upstream_connect_offload_wait_time",
2153                CtxLogField::UpstreamConnectOffloadWaitTime,
2154            ),
2155            ("tls_version", CtxLogField::TlsVersion),
2156            ("tls_client_subject", CtxLogField::TlsClientSubject),
2157            ("tls_client_fingerprint", CtxLogField::TlsClientFingerprint),
2158            ("tls_client_serial", CtxLogField::TlsClientSerial),
2159            ("tls_client_verified", CtxLogField::TlsClientVerified),
2160            ("ja4", CtxLogField::Ja4),
2161            ("ja4_r", CtxLogField::Ja4R),
2162            ("ja4_o", CtxLogField::Ja4O),
2163            ("ja4_ro", CtxLogField::Ja4Ro),
2164            ("compression_ratio", CtxLogField::CompressionRatio),
2165            ("service_time_human", CtxLogField::ServiceTimeHuman),
2166        ] {
2167            assert_eq!(Ok(field), name.parse::<CtxLogField>(), "{name}");
2168            assert_eq!(name, field.to_string());
2169        }
2170        assert_eq!(true, "unknown_key".parse::<CtxLogField>().is_err());
2171
2172        let mut ctx = Ctx::new();
2173        ctx.conn.id = 7;
2174        let mut by_name = BytesMut::new();
2175        ctx.append_log_value(&mut by_name, "connection_id");
2176        let mut by_field = BytesMut::new();
2177        ctx.append_log_field(&mut by_field, CtxLogField::ConnectionId);
2178        assert_eq!(b"7", by_name.as_ref());
2179        assert_eq!(by_name, by_field);
2180    }
2181
2182    /// The JA4 fields print the connection's fingerprint in each form,
2183    /// and nothing when there is none.
2184    #[test]
2185    fn test_ja4_log_fields() {
2186        let mut ctx = Ctx::new();
2187        let value = |ctx: &Ctx, key: &str| {
2188            let mut buf = BytesMut::new();
2189            ctx.append_log_value(&mut buf, key);
2190            String::from_utf8(buf.to_vec()).unwrap()
2191        };
2192        assert_eq!("", value(&ctx, "ja4"));
2193        let fingerprint = Ja4Fingerprint::from_client_hello(
2194            &crate::ja4::testing::spec_example_body(),
2195        )
2196        .unwrap();
2197        ctx.conn.ja4 = Some(Arc::new(fingerprint.clone()));
2198        assert_eq!("t13d1516h2_8daaf6152771_e5627efa2ab1", value(&ctx, "ja4"));
2199        assert_eq!(fingerprint.ja4_r(), value(&ctx, "ja4_r"));
2200        assert_eq!(fingerprint.ja4_o(), value(&ctx, "ja4_o"));
2201        assert_eq!(fingerprint.ja4_ro(), value(&ctx, "ja4_ro"));
2202    }
2203
2204    /// Tests the `append_log_value` function with a wider range of keys and edge cases.
2205    #[test]
2206    fn test_append_log_value_coverage() {
2207        let mut ctx = Ctx::new();
2208        // Test an unknown key, should do nothing.
2209        let mut buf = BytesMut::new();
2210        ctx.append_log_value(&mut buf, "unknown_key");
2211        assert!(buf.is_empty(), "Unknown key should not append anything");
2212
2213        // Test boolean values
2214        buf = BytesMut::new();
2215        ctx.conn.reused = true;
2216        ctx.append_log_value(&mut buf, "connection_reused");
2217        assert_eq!(&buf[..], b"true");
2218
2219        // Test optional string values
2220        ctx.conn.tls_version = Some("TLSv1.3".into());
2221        buf = BytesMut::new();
2222        ctx.append_log_value(&mut buf, "tls_version");
2223        assert_eq!(&buf[..], b"TLSv1.3");
2224
2225        // Test service_time calculation
2226        std::thread::sleep(Duration::from_millis(11));
2227        buf = BytesMut::new();
2228        ctx.append_log_value(&mut buf, "service_time");
2229        let service_time: u64 =
2230            std::str::from_utf8(&buf[..]).unwrap().parse().unwrap();
2231        assert!(service_time >= 10, "Service time should be at least 10ms");
2232    }
2233
2234    /// Tests the `get_cache_key` function's logic more thoroughly.
2235    #[test]
2236    fn test_get_cache_key() {
2237        let method = "GET";
2238        // HTTP/2: the host is in the uri.
2239        let mut h2 = RequestHeader::build("GET", b"/", None).unwrap();
2240        h2.set_uri(Uri::from_static("https://example.com/path?a=1"));
2241        // HTTP/1.1: the uri is the path, the host is a header.
2242        let h1 = |host: &str, path: &str| {
2243            let mut header =
2244                RequestHeader::build("GET", path.as_bytes(), None).unwrap();
2245            header.insert_header("Host", host).unwrap();
2246            header
2247        };
2248
2249        // Case 1: No cache info in context.
2250        let ctx_no_cache = Ctx::new();
2251        let key1 = get_cache_key(&ctx_no_cache, method, &h2);
2252        assert_eq!(key1.user_tag, "");
2253        assert_eq!(key1.primary_key_str(), Some(""));
2254
2255        // Case 2: Cache info with namespace but no keys.
2256        let mut ctx_with_ns = Ctx::new();
2257        ctx_with_ns.cache = Some(Box::new(CacheInfo {
2258            namespace: Some("my-ns".to_string()),
2259            ..Default::default()
2260        }));
2261        let key2 = get_cache_key(&ctx_with_ns, method, &h2);
2262        assert_eq!(key2.user_tag, "my-ns");
2263        assert_eq!(
2264            key2.primary_key_str(),
2265            Some("my-nsGET:example.com/path?a=1")
2266        );
2267        // Changing what goes into the key makes every entry an older
2268        // pingap wrote to disk unreachable after the upgrade, so the hash
2269        // of a known key is pinned here.
2270        assert_eq!(key2.primary(), "8fe845e5498004cd81203be9074a8e68");
2271
2272        // The same request over HTTP/1.1 is the same entry, whatever the
2273        // spelling of the host and whichever port it came in on.
2274        for host in ["example.com", "Example.COM", "example.com:8443"] {
2275            let key =
2276                get_cache_key(&ctx_with_ns, method, &h1(host, "/path?a=1"));
2277            assert_eq!(key2.primary(), key.primary(), "{host}");
2278        }
2279        // Regression: another host is another entry. Over HTTP/1.1 the host
2280        // used to be missing from the key.
2281        let other =
2282            get_cache_key(&ctx_with_ns, method, &h1("other.com", "/path?a=1"));
2283        assert_eq!(
2284            other.primary_key_str(),
2285            Some("my-nsGET:other.com/path?a=1")
2286        );
2287        assert_ne!(key2.primary(), other.primary());
2288        // Regression: the host is the client's to write, and must not be
2289        // able to stand in for a piece of the path. These two used to have
2290        // one key.
2291        let odd = get_cache_key(
2292            &ctx_with_ns,
2293            method,
2294            &h1("example.com/static", "/app.js"),
2295        );
2296        let plain = get_cache_key(
2297            &ctx_with_ns,
2298            method,
2299            &h1("example.com", "/static/app.js"),
2300        );
2301        assert_eq!(
2302            odd.primary_key_str(),
2303            Some("my-nsGET:example.com%2Fstatic/app.js")
2304        );
2305        assert_ne!(odd.primary(), plain.primary());
2306        // An escape of its own does not get it there either.
2307        let escaped = get_cache_key(
2308            &ctx_with_ns,
2309            method,
2310            &h1("example.com%2Fstatic", "/app.js"),
2311        );
2312        assert_ne!(odd.primary(), escaped.primary());
2313
2314        // No host at all (HTTP/1.0).
2315        let bare = RequestHeader::build("GET", b"/path", None).unwrap();
2316        assert_eq!(
2317            get_cache_key(&ctx_with_ns, method, &bare).primary_key_str(),
2318            Some("my-nsGET:/path")
2319        );
2320
2321        // Case 3: Cache info with namespace and multiple keys.
2322        let mut ctx_with_keys = Ctx::new();
2323        ctx_with_keys.cache = Some(Box::new(CacheInfo {
2324            namespace: Some("my-ns".to_string()),
2325            keys: Some(vec!["user-123".to_string(), "desktop".to_string()]),
2326            ..Default::default()
2327        }));
2328        let key3 = get_cache_key(&ctx_with_keys, method, &h2);
2329        assert_eq!(key3.user_tag, "my-ns");
2330        assert_eq!(
2331            key3.primary_key_str(),
2332            Some("my-nsuser-123:desktop:GET:example.com/path?a=1")
2333        );
2334    }
2335
2336    /// The original `test_generate_server_timing` is good, but this version
2337    /// is slightly more robust to minor timing variations.
2338    #[test]
2339    fn test_generate_server_timing() {
2340        let mut ctx = Ctx::new();
2341        ctx.timing.upstream_connect = Some(1);
2342        ctx.timing.upstream_processing = Some(2);
2343        ctx.timing.cache_lookup = Some(6);
2344        ctx.timing.cache_lock = Some(7);
2345        ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
2346
2347        let timing_header = ctx.generate_server_timing();
2348
2349        // Check for the presence of each expected component.
2350        assert!(timing_header.contains("upstream.connect;dur=1"));
2351        assert!(timing_header.contains("upstream.processing;dur=2"));
2352        assert!(timing_header.contains("upstream;dur=3"));
2353        assert!(timing_header.contains("cache.lookup;dur=6"));
2354        assert!(timing_header.contains("cache.lock;dur=7"));
2355        assert!(timing_header.contains("cache;dur=13"));
2356        assert!(timing_header.contains("plugin.plugin1;dur=100"));
2357        assert!(timing_header.contains("plugin;dur=100"));
2358        assert!(timing_header.contains("total;dur="));
2359    }
2360
2361    #[test]
2362    fn test_format_duration() {
2363        let mut buf = BytesMut::new();
2364        format_duration(&mut buf, (3600 + 3500) * 1000);
2365        assert_eq!(b"1.9h", buf.as_ref());
2366
2367        buf = BytesMut::new();
2368        format_duration(&mut buf, (3600 + 1800) * 1000);
2369        assert_eq!(b"1.5h", buf.as_ref());
2370
2371        buf = BytesMut::new();
2372        format_duration(&mut buf, (3600 + 100) * 1000);
2373        assert_eq!(b"1h", buf.as_ref());
2374
2375        buf = BytesMut::new();
2376        format_duration(&mut buf, (60 + 50) * 1000);
2377        assert_eq!(b"1.8m", buf.as_ref());
2378
2379        buf = BytesMut::new();
2380        format_duration(&mut buf, (60 + 2) * 1000);
2381        assert_eq!(b"1m", buf.as_ref());
2382
2383        buf = BytesMut::new();
2384        format_duration(&mut buf, 1000);
2385        assert_eq!(b"1s", buf.as_ref());
2386
2387        buf = BytesMut::new();
2388        format_duration(&mut buf, 512);
2389        assert_eq!(b"512ms", buf.as_ref());
2390
2391        buf = BytesMut::new();
2392        format_duration(&mut buf, 1112);
2393        assert_eq!(b"1.1s", buf.as_ref());
2394    }
2395
2396    #[test]
2397    fn test_add_variable() {
2398        let mut ctx = Ctx::new();
2399        ctx.add_variable("key1", "value1");
2400        ctx.add_variable("key2", "value2");
2401        ctx.extend_variables(AHashMap::from([
2402            ("key3".to_string(), "value3".to_string()),
2403            ("key4".to_string(), "value4".to_string()),
2404        ]));
2405        let variables =
2406            ctx.features.as_ref().unwrap().variables.as_ref().unwrap();
2407        // NOTE: The current implementation in the main code doesn't add the '$' prefix automatically.
2408        // The test should reflect the actual implementation.
2409        assert_eq!(variables.get("key1"), Some(&"value1".to_string()));
2410        assert_eq!(variables.get("key2"), Some(&"value2".to_string()));
2411        assert_eq!(variables.get("key3"), Some(&"value3".to_string()));
2412        assert_eq!(variables.get("key4"), Some(&"value4".to_string()));
2413    }
2414
2415    #[test]
2416    fn test_cache_key() {
2417        let mut ctx = Ctx::new();
2418        ctx.push_cache_key("key1".to_string());
2419        ctx.extend_cache_keys(vec!["key2".to_string(), "key3".to_string()]);
2420        assert_eq!(
2421            vec!["key1".to_string(), "key2".to_string(), "key3".to_string()],
2422            ctx.cache.unwrap().keys.unwrap()
2423        );
2424
2425        let mut ctx = Ctx::new();
2426        ctx.cache.get_or_insert_default();
2427        let mut header = RequestHeader::build("GET", b"/", None).unwrap();
2428        header.set_uri(Uri::from_static("https://example.com/path"));
2429        let key = get_cache_key(&ctx, "GET", &header);
2430        assert_eq!(key.user_tag, "");
2431        assert_eq!(key.primary_key_str(), Some("GET:example.com/path"));
2432    }
2433
2434    #[test]
2435    fn test_state() {
2436        let mut ctx = Ctx::new();
2437
2438        let mut buf = BytesMut::new();
2439        ctx.conn.id = 10;
2440        ctx.append_log_value(&mut buf, "connection_id");
2441        assert_eq!(b"10", buf.as_ref());
2442
2443        buf = BytesMut::new();
2444        ctx.append_log_value(&mut buf, "upstream_reused");
2445        assert_eq!(b"false", buf.as_ref());
2446
2447        buf = BytesMut::new();
2448        ctx.upstream.reused = true;
2449        ctx.append_log_value(&mut buf, "upstream_reused");
2450        assert_eq!(b"true", buf.as_ref());
2451
2452        buf = BytesMut::new();
2453        ctx.upstream.address = "192.168.1.1:80".to_string();
2454        ctx.append_log_value(&mut buf, "upstream_addr");
2455        assert_eq!(b"192.168.1.1:80", buf.as_ref());
2456
2457        buf = BytesMut::new();
2458        ctx.upstream.status = Some(StatusCode::CREATED);
2459        ctx.append_log_value(&mut buf, "upstream_status");
2460        assert_eq!(b"201", buf.as_ref());
2461
2462        buf = BytesMut::new();
2463        ctx.state.processing_count = 10;
2464        ctx.append_log_value(&mut buf, "processing");
2465        assert_eq!(b"10", buf.as_ref());
2466
2467        buf = BytesMut::new();
2468        ctx.timing.upstream_connect = Some(1);
2469        ctx.append_log_value(&mut buf, "upstream_connect_time");
2470        assert_eq!(b"1", buf.as_ref());
2471
2472        buf = BytesMut::new();
2473        ctx.append_log_value(&mut buf, "upstream_connect_time_human");
2474        assert_eq!(b"1ms", buf.as_ref());
2475
2476        buf = BytesMut::new();
2477        ctx.upstream.connected_count = Some(30);
2478        ctx.append_log_value(&mut buf, "upstream_connected");
2479        assert_eq!(b"30", buf.as_ref());
2480
2481        buf = BytesMut::new();
2482        ctx.timing.upstream_processing = Some(2);
2483        ctx.append_log_value(&mut buf, "upstream_processing_time");
2484        assert_eq!(b"2", buf.as_ref());
2485
2486        buf = BytesMut::new();
2487        ctx.append_log_value(&mut buf, "upstream_processing_time_human");
2488        assert_eq!(b"2ms", buf.as_ref());
2489
2490        buf = BytesMut::new();
2491        ctx.timing.upstream_response = Some(3);
2492        ctx.append_log_value(&mut buf, "upstream_response_time");
2493        assert_eq!(b"3", buf.as_ref());
2494
2495        buf = BytesMut::new();
2496        ctx.append_log_value(&mut buf, "upstream_response_time_human");
2497        assert_eq!(b"3ms", buf.as_ref());
2498
2499        buf = BytesMut::new();
2500        ctx.timing.upstream_tcp_connect = Some(100);
2501        ctx.append_log_value(&mut buf, "upstream_tcp_connect_time");
2502        assert_eq!(b"100", buf.as_ref());
2503
2504        buf = BytesMut::new();
2505        ctx.append_log_value(&mut buf, "upstream_tcp_connect_time_human");
2506        assert_eq!(b"100ms", buf.as_ref());
2507
2508        buf = BytesMut::new();
2509        ctx.timing.upstream_tls_handshake = Some(110);
2510        ctx.append_log_value(&mut buf, "upstream_tls_handshake_time");
2511        assert_eq!(b"110", buf.as_ref());
2512
2513        buf = BytesMut::new();
2514        ctx.timing.upstream_connect_offload_wait = Some(3);
2515        ctx.append_log_value(&mut buf, "upstream_connect_offload_wait_time");
2516        assert_eq!(b"3", buf.as_ref());
2517        buf = BytesMut::new();
2518        ctx.append_log_value(
2519            &mut buf,
2520            "upstream_connect_offload_wait_time_human",
2521        );
2522        assert_eq!(b"3ms", buf.as_ref());
2523
2524        buf = BytesMut::new();
2525        ctx.append_log_value(&mut buf, "upstream_tls_handshake_time_human");
2526        assert_eq!(b"110ms", buf.as_ref());
2527
2528        buf = BytesMut::new();
2529        ctx.timing.upstream_connection_duration = Some(120);
2530        ctx.append_log_value(&mut buf, "upstream_connection_time");
2531        assert_eq!(b"120", buf.as_ref());
2532
2533        buf = BytesMut::new();
2534        ctx.append_log_value(&mut buf, "upstream_connection_time_human");
2535        assert_eq!(b"120ms", buf.as_ref());
2536
2537        buf = BytesMut::new();
2538        ctx.upstream.location = "pingap".to_string().into();
2539        ctx.append_log_value(&mut buf, "location");
2540        assert_eq!(b"pingap", buf.as_ref());
2541
2542        buf = BytesMut::new();
2543        ctx.timing.connection_duration = 4;
2544        ctx.append_log_value(&mut buf, "connection_time");
2545        assert_eq!(b"4", buf.as_ref());
2546
2547        buf = BytesMut::new();
2548        ctx.append_log_value(&mut buf, "connection_time_human");
2549        assert_eq!(b"4ms", buf.as_ref());
2550
2551        buf = BytesMut::new();
2552        ctx.conn.reused = false;
2553        ctx.append_log_value(&mut buf, "connection_reused");
2554        assert_eq!(b"false", buf.as_ref());
2555
2556        buf = BytesMut::new();
2557        ctx.conn.reused = true;
2558        ctx.append_log_value(&mut buf, "connection_reused");
2559        assert_eq!(b"true", buf.as_ref());
2560
2561        buf = BytesMut::new();
2562        ctx.conn.tls_version = Some("TLSv1.3".into());
2563        ctx.append_log_value(&mut buf, "tls_version");
2564        assert_eq!(b"TLSv1.3", buf.as_ref());
2565
2566        buf = BytesMut::new();
2567        ctx.conn.tls_cipher =
2568            Some("ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into());
2569        ctx.append_log_value(&mut buf, "tls_cipher");
2570        assert_eq!(b"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", buf.as_ref());
2571
2572        buf = BytesMut::new();
2573        ctx.timing.tls_handshake = Some(101);
2574        ctx.append_log_value(&mut buf, "tls_handshake_time");
2575        assert_eq!(b"101", buf.as_ref());
2576
2577        buf = BytesMut::new();
2578        ctx.append_log_value(&mut buf, "tls_handshake_time_human");
2579        assert_eq!(b"101ms", buf.as_ref());
2580
2581        {
2582            let features = ctx.features.get_or_insert_default();
2583            features.compression_stat = Some(CompressionStat {
2584                in_bytes: 1024,
2585                out_bytes: 500,
2586                duration: Duration::from_millis(5),
2587                ..Default::default()
2588            })
2589        }
2590
2591        buf = BytesMut::new();
2592        ctx.append_log_value(&mut buf, "compression_time");
2593        assert_eq!(b"5", buf.as_ref());
2594
2595        buf = BytesMut::new();
2596        ctx.append_log_value(&mut buf, "compression_time_human");
2597        assert_eq!(b"5ms", buf.as_ref());
2598
2599        buf = BytesMut::new();
2600        ctx.append_log_value(&mut buf, "compression_ratio");
2601        assert_eq!(b"2.0", buf.as_ref());
2602
2603        buf = BytesMut::new();
2604        ctx.timing.cache_lookup = Some(6);
2605        ctx.append_log_value(&mut buf, "cache_lookup_time");
2606        assert_eq!(b"6", buf.as_ref());
2607
2608        buf = BytesMut::new();
2609        ctx.append_log_value(&mut buf, "cache_lookup_time_human");
2610        assert_eq!(b"6ms", buf.as_ref());
2611
2612        buf = BytesMut::new();
2613        ctx.timing.cache_lock = Some(7);
2614        ctx.append_log_value(&mut buf, "cache_lock_time");
2615        assert_eq!(b"7", buf.as_ref());
2616
2617        buf = BytesMut::new();
2618        ctx.append_log_value(&mut buf, "cache_lock_time_human");
2619        assert_eq!(b"7ms", buf.as_ref());
2620    }
2621
2622    #[test]
2623    fn test_add_plugin_processing_time() {
2624        let mut ctx = Ctx::new();
2625        ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
2626        ctx.add_plugin_processing_time(&Arc::from("plugin2"), 200);
2627        assert_eq!(
2628            ctx.features.unwrap().plugin_processing_times,
2629            Some(vec![
2630                (Arc::from("plugin1"), 100),
2631                (Arc::from("plugin2"), 200)
2632            ])
2633        );
2634    }
2635
2636    #[test]
2637    fn test_get_digest_detail() {
2638        let mut digest = Digest::default();
2639        let detail = get_digest_detail(&digest);
2640        assert_eq!(detail.connection_reused, false);
2641        assert_eq!(detail.connection_time, 0);
2642        assert_eq!(detail.tcp_established, 0);
2643        assert_eq!(detail.tls_established, 0);
2644        assert_eq!(detail.tls_version, None);
2645        assert_eq!(detail.tls_cipher, None);
2646
2647        digest.timing_digest.push(Some(TimingDigest {
2648            established_ts: SystemTime::UNIX_EPOCH
2649                .checked_add(Duration::from_secs(5))
2650                .unwrap(),
2651            ..Default::default()
2652        }));
2653        digest.timing_digest.push(Some(TimingDigest {
2654            established_ts: SystemTime::UNIX_EPOCH
2655                .checked_add(Duration::from_secs(3))
2656                .unwrap(),
2657            ..Default::default()
2658        }));
2659        digest.ssl_digest = Some(Arc::new(SslDigest {
2660            version: "1.3".into(),
2661            cipher: "123".into(),
2662            organization: Some("cloudflare".to_string()),
2663            serial_number: Some(
2664                "0x00000000000000000000000000000abc".to_string(),
2665            ),
2666            cert_digest: vec![],
2667            extension: SslDigestExtension::default(),
2668        }));
2669        let detail = get_digest_detail(&digest);
2670        assert_eq!(detail.connection_reused, true);
2671        assert_eq!(detail.tcp_established, 5000);
2672        assert_eq!(detail.tls_established, 3000);
2673        assert_eq!(detail.tls_version.as_deref(), Some("1.3"));
2674        assert_eq!(detail.tls_cipher.as_deref(), Some("123"));
2675        // Nothing measured on these entries.
2676        assert_eq!(detail.tcp_connect, None);
2677        assert_eq!(detail.tls_handshake, None);
2678
2679        // pingora's per-layer measurements come through as such.
2680        digest.timing_digest = vec![
2681            Some(TimingDigest {
2682                establishment_duration: Some(Duration::from_millis(12)),
2683                ..Default::default()
2684            }),
2685            Some(TimingDigest {
2686                establishment_duration: Some(Duration::from_millis(34)),
2687                ..Default::default()
2688            }),
2689        ];
2690        let detail = get_digest_detail(&digest);
2691        assert_eq!(detail.tcp_connect, Some(12));
2692        assert_eq!(detail.tls_handshake, Some(34));
2693        // Not offloaded: no queueing time to report.
2694        assert_eq!(detail.connect_offload_wait, None);
2695
2696        // An offloaded connect also carries the time it waited for a thread.
2697        digest.timing_digest[0] = Some(TimingDigest {
2698            establishment_duration: Some(Duration::from_millis(12)),
2699            offload_wait_duration: Some(Duration::from_millis(2)),
2700            ..Default::default()
2701        });
2702        let detail = get_digest_detail(&digest);
2703        assert_eq!(detail.connect_offload_wait, Some(2));
2704
2705        // Without TLS the last entry is the transport again: no handshake.
2706        digest.ssl_digest = None;
2707        digest.timing_digest.truncate(1);
2708        let detail = get_digest_detail(&digest);
2709        assert_eq!(detail.tcp_connect, Some(12));
2710        assert_eq!(detail.tls_handshake, None);
2711    }
2712
2713    #[test]
2714    fn test_update_upstream_timing_from_digest() {
2715        let measured = |tcp: u64, tls: u64| Digest {
2716            timing_digest: vec![
2717                Some(TimingDigest {
2718                    establishment_duration: Some(Duration::from_millis(tcp)),
2719                    ..Default::default()
2720                }),
2721                Some(TimingDigest {
2722                    establishment_duration: Some(Duration::from_millis(tls)),
2723                    ..Default::default()
2724                }),
2725            ],
2726            ssl_digest: Some(Arc::new(SslDigest {
2727                version: "1.3".into(),
2728                cipher: "123".into(),
2729                organization: None,
2730                serial_number: None,
2731                cert_digest: vec![],
2732                extension: SslDigestExtension::default(),
2733            })),
2734            ..Default::default()
2735        };
2736
2737        // Measured layers are taken as they are, independent of pingap's
2738        // own end-to-end timer.
2739        let mut ctx = Ctx::new();
2740        ctx.timing.upstream_connect = Some(100);
2741        ctx.update_upstream_timing_from_digest(&measured(12, 34), false);
2742        assert_eq!(Some(12), ctx.timing.upstream_tcp_connect);
2743        assert_eq!(Some(34), ctx.timing.upstream_tls_handshake);
2744        assert_eq!(None, ctx.timing.upstream_connect_offload_wait);
2745
2746        // The offload queueing time rides along when the connect was
2747        // offloaded.
2748        let mut ctx = Ctx::new();
2749        let mut offloaded = measured(12, 34);
2750        offloaded.timing_digest[0] = Some(TimingDigest {
2751            establishment_duration: Some(Duration::from_millis(12)),
2752            offload_wait_duration: Some(Duration::from_millis(2)),
2753            ..Default::default()
2754        });
2755        ctx.update_upstream_timing_from_digest(&offloaded, false);
2756        assert_eq!(Some(2), ctx.timing.upstream_connect_offload_wait);
2757
2758        // A reused connection carries no connect cost for this request.
2759        let mut ctx = Ctx::new();
2760        ctx.timing.upstream_connect = Some(100);
2761        ctx.update_upstream_timing_from_digest(&measured(12, 34), true);
2762        assert_eq!(None, ctx.timing.upstream_tcp_connect);
2763        assert_eq!(None, ctx.timing.upstream_tls_handshake);
2764
2765        // No measurement: fall back to splitting the end-to-end timer by
2766        // the layers' wall-clock timestamps.
2767        let mut ctx = Ctx::new();
2768        ctx.timing.upstream_connect = Some(100);
2769        let mut unmeasured = measured(0, 0);
2770        unmeasured.timing_digest = vec![
2771            Some(TimingDigest {
2772                established_ts: SystemTime::UNIX_EPOCH
2773                    .checked_add(Duration::from_millis(1_000))
2774                    .unwrap(),
2775                ..Default::default()
2776            }),
2777            Some(TimingDigest {
2778                established_ts: SystemTime::UNIX_EPOCH
2779                    .checked_add(Duration::from_millis(1_030))
2780                    .unwrap(),
2781                ..Default::default()
2782            }),
2783        ];
2784        ctx.update_upstream_timing_from_digest(&unmeasured, false);
2785        assert_eq!(Some(70), ctx.timing.upstream_tcp_connect);
2786        assert_eq!(Some(30), ctx.timing.upstream_tls_handshake);
2787    }
2788
2789    #[test]
2790    fn test_modify_body_handler() {
2791        let mut ctx = Ctx::default();
2792
2793        struct TestHandler {}
2794        impl ModifyResponseBody for TestHandler {
2795            fn handle(
2796                &mut self,
2797                _session: &Session,
2798                body: &mut Option<bytes::Bytes>,
2799                _end_of_stream: bool,
2800            ) -> pingora::Result<()> {
2801                *body = Some(Bytes::from("test"));
2802                Ok(())
2803            }
2804        }
2805
2806        ctx.add_modify_body_handler("test", Box::new(TestHandler {}));
2807        assert_eq!(true, ctx.get_modify_body_handler("test").is_some());
2808    }
2809}