1use crate::{Ja4Fingerprint, Plugin, now_ms};
16use ahash::AHashMap;
17use bytes::BytesMut;
18use http::StatusCode;
19use http::Uri;
20use http::{HeaderName, HeaderValue};
21#[cfg(feature = "tracing")]
22use opentelemetry::{
23 Context,
24 global::{BoxedSpan, BoxedTracer, ObjectSafeSpan},
25 trace::{SpanKind, TraceContextExt, Tracer},
26};
27use pingora::cache::CacheKey;
28use pingora::http::RequestHeader;
29use pingora::protocols::Digest;
30use pingora::protocols::TimingDigest;
31use pingora::proxy::Session;
32use pingora_limits::inflight::Guard;
33use std::borrow::Cow;
34use std::fmt::Write;
35use std::sync::Arc;
36use std::time::{Duration, Instant, SystemTime};
37use strum::EnumString;
38
39const SECOND: u64 = 1_000;
41const MINUTE: u64 = 60 * SECOND;
42const HOUR: u64 = 60 * MINUTE;
43
44#[inline]
45pub fn format_duration(buf: &mut BytesMut, ms: u64) {
48 if ms < SECOND {
49 buf.extend_from_slice(itoa::Buffer::new().format(ms).as_bytes());
51 buf.extend_from_slice(b"ms");
52 } else if ms < MINUTE {
53 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 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 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
101pub trait ModifyResponseBody: Sync + Send {
103 fn handle(
105 &mut self,
106 session: &Session,
107 body: &mut Option<bytes::Bytes>,
108 end_of_stream: bool,
109 ) -> pingora::Result<()>;
110 fn name(&self) -> &str {
112 "unknown"
113 }
114}
115
116#[derive(Default)]
118pub struct ConnectionInfo {
119 pub id: usize,
121 pub client_ip: Option<String>,
123 pub remote_addr: Option<String>,
125 pub remote_port: Option<u16>,
127 pub server_addr: Option<String>,
129 pub server_port: Option<u16>,
131 pub tls_version: Option<Cow<'static, str>>,
136 pub tls_cipher: Option<Cow<'static, str>>,
138 pub ja4: Option<Arc<Ja4Fingerprint>>,
141 pub reused: bool,
143}
144
145pub struct Timing {
148 pub created_at: Instant,
150 pub connection_duration: u64,
153 pub tls_handshake: Option<i32>,
155 pub upstream_connect: Option<i32>,
157 pub upstream_tcp_connect: Option<i32>,
159 pub upstream_tls_handshake: Option<i32>,
161 pub upstream_connect_offload_wait: Option<i32>,
166 pub upstream_processing: Option<i32>,
168 pub upstream_response: Option<i32>,
170 pub upstream_connection_duration: Option<u64>,
172 pub cache_lookup: Option<i32>,
174 pub cache_lock: Option<i32>,
176}
177
178impl Default for Timing {
179 fn default() -> Self {
180 Self {
181 created_at: Instant::now(),
182 connection_duration: 0,
183 tls_handshake: None,
184 upstream_connect: None,
185 upstream_tcp_connect: None,
186 upstream_tls_handshake: None,
187 upstream_connect_offload_wait: None,
188 upstream_processing: None,
189 upstream_response: None,
190 upstream_connection_duration: None,
191 cache_lookup: None,
192 cache_lock: None,
193 }
194 }
195}
196
197pub trait UpstreamInstance: Send + Sync {
199 fn on_transport_failure(&self, address: &str);
200 fn on_response(&self, address: &str, status: StatusCode);
201 fn completed(&self) -> i32;
206}
207
208pub trait LocationInstance: Send + Sync {
210 fn name(&self) -> &str;
212 fn upstream(&self) -> &str;
214 fn rewrite(
217 &self,
218 header: &mut RequestHeader,
219 variables: &mut Option<AHashMap<String, String>>,
220 ) -> bool;
221 fn headers(&self) -> Option<&Vec<(HeaderName, HeaderValue, bool)>>;
223 fn client_body_size_limit(&self) -> usize;
225 fn on_request(&self) -> pingora::Result<(u64, i32)>;
231 fn on_response(&self);
233}
234
235#[derive(Default)]
237pub struct UpstreamInfo {
238 pub upstream_instance: Option<Arc<dyn UpstreamInstance>>,
240 pub location_instance: Option<Arc<dyn LocationInstance>>,
242 pub location: Arc<str>,
244 pub name: Arc<str>,
246 pub address: String,
248 pub reused: bool,
250 pub processing_count: Option<i32>,
252 pub connected_count: Option<i32>,
254 pub status: Option<StatusCode>,
256 pub retries: u8,
258 pub max_retries: Option<u8>,
260 pub max_retry_window: Option<Duration>,
268}
269
270#[derive(Default)]
272pub struct RequestState {
273 pub request_id: Option<String>,
275 pub status: Option<StatusCode>,
277 pub payload_size: usize,
279 pub guard: Option<Guard>,
281 pub processing_count: i32,
283 pub accepted_count: u64,
285 pub location_processing_count: i32,
287 pub location_accepted_count: u64,
289}
290
291#[derive(Default)]
293pub struct CacheInfo {
294 pub namespace: Option<String>,
296 pub keys: Option<Vec<String>>,
298 pub check_cache_control: bool,
300 pub max_ttl: Option<Duration>,
302 pub vary_headers: Option<Arc<Vec<String>>>,
305 pub reading_count: Option<u32>,
307 pub writing_count: Option<u32>,
309}
310
311#[derive(Default)]
313pub struct Features {
314 pub variables: Option<AHashMap<String, String>>,
316 pub plugin_processing_times: Option<Vec<(Arc<str>, u32)>>,
318 pub compression_stat: Option<CompressionStat>,
320 pub modify_body_handlers:
322 Option<AHashMap<String, Box<dyn ModifyResponseBody>>>,
323 #[cfg(feature = "tracing")]
325 pub otel_tracer: Option<OtelTracer>,
326 #[cfg(feature = "tracing")]
328 pub upstream_span: Option<BoxedSpan>,
329}
330
331#[derive(Default)]
332pub struct CompressionStat {
334 pub algorithm: String,
336 pub in_bytes: usize,
338 pub out_bytes: usize,
340 pub duration: Duration,
342}
343
344impl CompressionStat {
345 pub fn ratio(&self) -> f64 {
347 if self.out_bytes == 0 {
348 return 0.0;
349 }
350 (self.in_bytes as f64) / (self.out_bytes as f64)
351 }
352}
353
354#[cfg(feature = "tracing")]
356pub struct OtelTracer {
357 pub tracer: BoxedTracer,
359 pub http_request_span: BoxedSpan,
361}
362
363#[cfg(feature = "tracing")]
364impl OtelTracer {
365 #[inline]
367 pub fn new_upstream_span(&self, name: &str) -> BoxedSpan {
368 self.tracer
369 .span_builder(name.to_string())
370 .with_kind(SpanKind::Client)
371 .start_with_context(
372 &self.tracer,
373 &Context::current().with_remote_span_context(
375 self.http_request_span.span_context().clone(),
376 ),
377 )
378 }
379}
380
381pub type NamedPlugin = (Arc<str>, Arc<dyn Plugin>);
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, strum::Display)]
389#[strum(serialize_all = "snake_case")]
390pub enum CtxLogField {
391 ConnectionId,
392 UpstreamReused,
393 UpstreamStatus,
394 UpstreamAddr,
395 Processing,
396 UpstreamConnected,
397 UpstreamConnectTime,
398 UpstreamConnectTimeHuman,
399 UpstreamProcessingTime,
400 UpstreamProcessingTimeHuman,
401 UpstreamResponseTime,
402 UpstreamResponseTimeHuman,
403 UpstreamTcpConnectTime,
404 UpstreamTcpConnectTimeHuman,
405 UpstreamTlsHandshakeTime,
406 UpstreamTlsHandshakeTimeHuman,
407 UpstreamConnectOffloadWaitTime,
408 UpstreamConnectOffloadWaitTimeHuman,
409 UpstreamConnectionTime,
410 UpstreamConnectionTimeHuman,
411 ConnectionTime,
412 ConnectionTimeHuman,
413 Location,
414 ConnectionReused,
415 TlsVersion,
416 TlsCipher,
417 Ja4,
418 #[strum(serialize = "ja4_r")]
419 Ja4R,
420 #[strum(serialize = "ja4_o")]
421 Ja4O,
422 #[strum(serialize = "ja4_ro")]
423 Ja4Ro,
424 TlsHandshakeTime,
425 TlsHandshakeTimeHuman,
426 CompressionTime,
427 CompressionTimeHuman,
428 CompressionRatio,
429 CacheLookupTime,
430 CacheLookupTimeHuman,
431 CacheLockTime,
432 CacheLockTimeHuman,
433 ServiceTime,
434 ServiceTimeHuman,
435}
436
437#[derive(Default)]
440pub struct Ctx {
441 pub conn: ConnectionInfo,
443 pub upstream: UpstreamInfo,
445 pub timing: Timing,
447 pub state: RequestState,
449 pub cache: Option<Box<CacheInfo>>,
453 pub features: Option<Box<Features>>,
455 pub plugins: Option<Arc<[NamedPlugin]>>,
458}
459
460#[derive(Debug, Default)]
462pub struct DigestDetail {
463 pub connection_reused: bool,
469 pub connection_time: u64,
471 pub tcp_established: u64,
473 pub tls_established: u64,
475 pub tcp_connect: Option<u64>,
479 pub tls_handshake: Option<u64>,
483 pub connect_offload_wait: Option<u64>,
486 pub tls_version: Option<Cow<'static, str>>,
488 pub tls_cipher: Option<Cow<'static, str>>,
490}
491
492#[inline]
493pub(crate) fn timing_to_ms(timing: Option<&Option<TimingDigest>>) -> u64 {
494 match timing {
495 Some(Some(item)) => item
496 .established_ts
497 .duration_since(SystemTime::UNIX_EPOCH)
498 .unwrap_or_default()
499 .as_millis() as u64,
500 _ => 0,
501 }
502}
503
504#[inline]
507pub fn get_digest_detail(digest: &Digest) -> DigestDetail {
508 let tcp_established = timing_to_ms(digest.timing_digest.first());
509 let mut connection_time = 0;
510 let now = now_ms();
511 if tcp_established > 0 && tcp_established < now {
512 connection_time = now - tcp_established;
513 }
514 let connection_reused = connection_time > 100;
515 let tcp_connect = establishment_ms(digest.timing_digest.first());
519 let connect_offload_wait = offload_wait_ms(digest.timing_digest.first());
520
521 let Some(ssl_digest) = &digest.ssl_digest else {
522 return DigestDetail {
523 connection_reused,
524 tcp_established,
525 connection_time,
526 tcp_connect,
527 connect_offload_wait,
528 ..Default::default()
529 };
530 };
531
532 DigestDetail {
533 connection_reused,
534 tcp_established,
535 connection_time,
536 tcp_connect,
537 connect_offload_wait,
538 tls_established: timing_to_ms(digest.timing_digest.last()),
539 tls_handshake: establishment_ms(digest.timing_digest.last()),
540 tls_version: Some(ssl_digest.version.clone()),
542 tls_cipher: Some(ssl_digest.cipher.clone()),
543 }
544}
545
546fn establishment_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
548 timing
549 .and_then(|item| item.as_ref())
550 .and_then(|item| item.establishment_duration)
551 .map(|duration| duration.as_millis() as u64)
552}
553
554fn offload_wait_ms(timing: Option<&Option<TimingDigest>>) -> Option<u64> {
557 timing
558 .and_then(|item| item.as_ref())
559 .and_then(|item| item.offload_wait_duration)
560 .map(|duration| duration.as_millis() as u64)
561}
562
563impl Ctx {
564 pub fn new() -> Self {
569 Self {
570 ..Default::default()
571 }
572 }
573
574 #[inline]
580 pub fn add_variable(&mut self, key: &str, value: &str) {
581 let features = self.features.get_or_insert_default();
583 let variables = features.variables.get_or_insert_with(AHashMap::new);
584 variables.insert(key.to_string(), value.to_string());
585 }
586
587 #[inline]
592 pub fn extend_variables(&mut self, values: AHashMap<String, String>) {
593 let features = self.features.get_or_insert_default();
594 if let Some(variables) = features.variables.as_mut() {
595 variables.extend(values);
596 } else {
597 features.variables = Some(values);
598 }
599 }
600
601 #[inline]
608 pub fn get_variable(&self, key: &str) -> Option<&str> {
609 self.features
610 .as_ref()?
611 .variables
612 .as_ref()?
613 .get(key)
614 .map(|v| v.as_str())
615 }
616
617 #[inline]
623 pub fn add_modify_body_handler(
624 &mut self,
625 name: &str,
626 handler: Box<dyn ModifyResponseBody>,
627 ) {
628 let features = self.features.get_or_insert_default();
629 let handlers = features
630 .modify_body_handlers
631 .get_or_insert_with(AHashMap::new);
632 handlers.insert(name.to_string(), handler);
633 }
634
635 #[inline]
637 pub fn get_modify_body_handler(
638 &mut self,
639 name: &str,
640 ) -> Option<&mut Box<dyn ModifyResponseBody>> {
641 self.features
642 .as_mut()
643 .and_then(|f| f.modify_body_handlers.as_mut())
644 .and_then(|h| h.get_mut(name))
645 }
646
647 #[inline]
650 fn get_time_field(&self, field: Option<i32>) -> Option<u32> {
651 if let Some(value) = field
652 && value >= 0
653 {
654 return Some(value as u32);
655 }
656 None
657 }
658
659 #[inline]
664 pub fn get_upstream_response_time(&self) -> Option<u32> {
665 self.get_time_field(self.timing.upstream_response)
666 }
667
668 #[inline]
673 pub fn get_upstream_connect_time(&self) -> Option<u32> {
674 self.get_time_field(self.timing.upstream_connect)
675 }
676
677 #[inline]
682 pub fn get_upstream_processing_time(&self) -> Option<u32> {
683 self.get_time_field(self.timing.upstream_processing)
684 }
685
686 #[inline]
692 pub fn add_plugin_processing_time(&mut self, name: &Arc<str>, time: u32) {
693 let features = self.features.get_or_insert_default();
695 let times = features
696 .plugin_processing_times
697 .get_or_insert_with(|| Vec::with_capacity(5));
698 if let Some(item) = times.iter_mut().find(|item| &item.0 == name) {
699 item.1 += time;
700 } else {
701 times.push((Arc::clone(name), time));
702 }
703 }
704
705 #[inline]
716 pub fn append_log_value(&self, buf: &mut BytesMut, key: &str) {
717 if let Ok(field) = key.parse::<CtxLogField>() {
719 self.append_log_field(buf, field);
720 }
721 }
722
723 pub fn append_log_field(&self, buf: &mut BytesMut, field: CtxLogField) {
725 macro_rules! append_time {
727 ($val:expr) => {
729 if let Some(ms) = $val {
730 buf.extend(itoa::Buffer::new().format(ms).as_bytes());
731 }
732 };
733 ($val:expr, human) => {
735 if let Some(ms) = $val {
736 format_duration(buf, ms as u64);
737 }
738 };
739 }
740
741 match field {
742 CtxLogField::ConnectionId => {
743 buf.extend(itoa::Buffer::new().format(self.conn.id).as_bytes());
744 },
745 CtxLogField::UpstreamReused => {
746 if self.upstream.reused {
747 buf.extend(b"true");
748 } else {
749 buf.extend(b"false");
750 }
751 },
752 CtxLogField::UpstreamStatus => {
753 if let Some(status) = &self.upstream.status {
754 buf.extend_from_slice(status.as_str().as_bytes());
755 } else {
756 buf.extend_from_slice(b"-");
757 }
758 },
759 CtxLogField::UpstreamAddr => {
760 buf.extend(self.upstream.address.as_bytes())
761 },
762 CtxLogField::Processing => buf.extend(
763 itoa::Buffer::new()
764 .format(self.state.processing_count)
765 .as_bytes(),
766 ),
767 CtxLogField::UpstreamConnected => {
768 if let Some(value) = self.upstream.connected_count {
769 buf.extend(itoa::Buffer::new().format(value).as_bytes());
770 }
771 },
772
773 CtxLogField::UpstreamConnectTime => {
775 append_time!(self.get_upstream_connect_time())
776 },
777 CtxLogField::UpstreamConnectTimeHuman => {
778 append_time!(self.get_upstream_connect_time(), human)
779 },
780
781 CtxLogField::UpstreamProcessingTime => {
782 append_time!(self.get_upstream_processing_time())
783 },
784 CtxLogField::UpstreamProcessingTimeHuman => {
785 append_time!(self.get_upstream_processing_time(), human)
786 },
787 CtxLogField::UpstreamResponseTime => {
788 append_time!(self.get_upstream_response_time())
789 },
790 CtxLogField::UpstreamResponseTimeHuman => {
791 append_time!(self.get_upstream_response_time(), human)
792 },
793 CtxLogField::UpstreamTcpConnectTime => {
794 append_time!(self.timing.upstream_tcp_connect)
795 },
796 CtxLogField::UpstreamTcpConnectTimeHuman => {
797 append_time!(self.timing.upstream_tcp_connect, human)
798 },
799 CtxLogField::UpstreamTlsHandshakeTime => {
800 append_time!(self.timing.upstream_tls_handshake)
801 },
802 CtxLogField::UpstreamTlsHandshakeTimeHuman => {
803 append_time!(self.timing.upstream_tls_handshake, human)
804 },
805 CtxLogField::UpstreamConnectOffloadWaitTime => {
806 append_time!(self.timing.upstream_connect_offload_wait)
807 },
808 CtxLogField::UpstreamConnectOffloadWaitTimeHuman => {
809 append_time!(self.timing.upstream_connect_offload_wait, human)
810 },
811 CtxLogField::UpstreamConnectionTime => {
812 append_time!(self.timing.upstream_connection_duration)
813 },
814 CtxLogField::UpstreamConnectionTimeHuman => {
815 append_time!(self.timing.upstream_connection_duration, human)
816 },
817 CtxLogField::ConnectionTime => {
818 append_time!(Some(self.timing.connection_duration))
819 },
820 CtxLogField::ConnectionTimeHuman => {
821 append_time!(Some(self.timing.connection_duration), human)
822 },
823
824 CtxLogField::Location => {
826 if !self.upstream.location.is_empty() {
827 buf.extend(self.upstream.location.as_bytes())
828 }
829 },
830 CtxLogField::ConnectionReused => {
831 if self.conn.reused {
832 buf.extend(b"true");
833 } else {
834 buf.extend(b"false");
835 }
836 },
837 CtxLogField::TlsVersion => {
838 if let Some(value) = &self.conn.tls_version {
839 buf.extend(value.as_bytes());
840 }
841 },
842 CtxLogField::TlsCipher => {
843 if let Some(value) = &self.conn.tls_cipher {
844 buf.extend(value.as_bytes());
845 }
846 },
847 CtxLogField::Ja4 => {
848 if let Some(fingerprint) = &self.conn.ja4 {
849 buf.extend(fingerprint.ja4().as_bytes());
850 }
851 },
852 CtxLogField::Ja4R => {
853 if let Some(fingerprint) = &self.conn.ja4 {
854 buf.extend(fingerprint.ja4_r().as_bytes());
855 }
856 },
857 CtxLogField::Ja4O => {
858 if let Some(fingerprint) = &self.conn.ja4 {
859 buf.extend(fingerprint.ja4_o().as_bytes());
860 }
861 },
862 CtxLogField::Ja4Ro => {
863 if let Some(fingerprint) = &self.conn.ja4 {
864 buf.extend(fingerprint.ja4_ro().as_bytes());
865 }
866 },
867 CtxLogField::TlsHandshakeTime => {
868 append_time!(self.timing.tls_handshake)
869 },
870 CtxLogField::TlsHandshakeTimeHuman => {
871 append_time!(self.timing.tls_handshake, human)
872 },
873 CtxLogField::CompressionTime => {
874 if let Some(feature) = &self.features
875 && let Some(value) = &feature.compression_stat
876 {
877 append_time!(Some(value.duration.as_millis() as u64))
878 }
879 },
880 CtxLogField::CompressionTimeHuman => {
881 if let Some(feature) = &self.features
882 && let Some(value) = &feature.compression_stat
883 {
884 append_time!(Some(value.duration.as_millis() as u64), human)
885 }
886 },
887 CtxLogField::CompressionRatio => {
888 if let Some(feature) = &self.features
889 && let Some(value) = &feature.compression_stat
890 {
891 let tenths = (value.ratio() * 10.0).round() as u64;
893 buf.extend(
894 itoa::Buffer::new().format(tenths / 10).as_bytes(),
895 );
896 buf.extend_from_slice(b".");
897 buf.extend(
898 itoa::Buffer::new().format(tenths % 10).as_bytes(),
899 );
900 }
901 },
902 CtxLogField::CacheLookupTime => {
903 append_time!(self.timing.cache_lookup)
904 },
905 CtxLogField::CacheLookupTimeHuman => {
906 append_time!(self.timing.cache_lookup, human)
907 },
908 CtxLogField::CacheLockTime => {
909 append_time!(self.timing.cache_lock)
910 },
911 CtxLogField::CacheLockTimeHuman => {
912 append_time!(self.timing.cache_lock, human)
913 },
914 CtxLogField::ServiceTime => {
915 append_time!(Some(self.timing.created_at.elapsed().as_millis()))
916 },
917 CtxLogField::ServiceTimeHuman => {
918 append_time!(
919 Some(self.timing.created_at.elapsed().as_millis()),
920 human
921 )
922 },
923 }
924 }
925
926 pub fn generate_server_timing(&self) -> String {
934 let mut timing_str = String::with_capacity(200);
935 let mut first = true;
937
938 macro_rules! add_timing {
940 ($name:expr, $dur:expr) => {
941 if !first {
942 timing_str.push_str(", ");
943 }
944 let _ = write!(&mut timing_str, "{};dur={}", $name, $dur);
946 first = false;
947 };
948 }
949
950 let mut upstream_time = 0;
952 if let Some(time) = self.get_upstream_connect_time() {
953 upstream_time += time;
954 add_timing!("upstream.connect", time);
955 }
956 if let Some(time) = self.get_upstream_processing_time() {
957 upstream_time += time;
958 add_timing!("upstream.processing", time);
959 }
960 if upstream_time > 0 {
961 add_timing!("upstream", upstream_time);
962 }
963
964 let mut cache_time = 0;
966 if let Some(time) = self.timing.cache_lookup {
967 cache_time += time;
968 add_timing!("cache.lookup", time);
969 }
970 if let Some(time) = self.timing.cache_lock {
971 cache_time += time;
972 add_timing!("cache.lock", time);
973 }
974 if cache_time > 0 {
975 add_timing!("cache", cache_time);
976 }
977
978 if let Some(features) = &self.features
980 && let Some(times) = &features.plugin_processing_times
981 {
982 let mut plugin_time: u32 = 0;
983 for (name, time) in times {
984 if *time == 0 {
985 continue;
986 }
987 plugin_time += time;
988 if !first {
991 timing_str.push_str(", ");
992 }
993 let _ = write!(&mut timing_str, "plugin.{name};dur={time}");
994 first = false;
995 }
996 if plugin_time > 0 {
997 add_timing!("plugin", plugin_time);
998 }
999 }
1000
1001 let service_time = self.timing.created_at.elapsed().as_millis();
1003 if !first {
1005 timing_str.push_str(", ");
1006 }
1007 let _ = write!(&mut timing_str, "total;dur={}", service_time);
1009
1010 timing_str
1011 }
1012
1013 #[inline]
1015 pub fn push_cache_key(&mut self, key: String) {
1016 let cache_info = self.cache.get_or_insert_default();
1017 cache_info
1018 .keys
1019 .get_or_insert_with(|| Vec::with_capacity(2))
1020 .push(key);
1021 }
1022
1023 #[inline]
1025 pub fn extend_cache_keys(&mut self, keys: Vec<String>) {
1026 let cache_info = self.cache.get_or_insert_default();
1027 cache_info
1028 .keys
1029 .get_or_insert_with(|| Vec::with_capacity(keys.len() + 2))
1030 .extend(keys);
1031 }
1032 #[inline]
1034 pub fn update_upstream_timing_from_digest(
1035 &mut self,
1036 digest: &Digest,
1037 reused: bool,
1038 ) {
1039 let detail = get_digest_detail(digest);
1040 self.timing.upstream_connection_duration = Some(detail.connection_time);
1041 if reused {
1042 return;
1043 }
1044
1045 if let Some(tcp_connect) = detail.tcp_connect {
1048 self.timing.upstream_tcp_connect = Some(tcp_connect as i32);
1049 self.timing.upstream_tls_handshake =
1050 detail.tls_handshake.map(|value| value as i32);
1051 self.timing.upstream_connect_offload_wait =
1052 detail.connect_offload_wait.map(|value| value as i32);
1053 return;
1054 }
1055
1056 let upstream_connect_time =
1061 self.timing.upstream_connect.unwrap_or_default();
1062 let mut upstream_tcp_connect = upstream_connect_time;
1063 if detail.tls_established > detail.tcp_established {
1064 let latency =
1065 (detail.tls_established - detail.tcp_established) as i32;
1066 upstream_tcp_connect -= latency;
1067 self.timing.upstream_tls_handshake = Some(latency);
1068 }
1069 if upstream_tcp_connect > 0 {
1070 self.timing.upstream_tcp_connect = Some(upstream_tcp_connect);
1071 }
1072 }
1073}
1074
1075pub fn get_cache_key(ctx: &Ctx, method: &str, uri: &Uri) -> CacheKey {
1086 let Some(cache_info) = &ctx.cache else {
1087 return CacheKey::new("", "");
1089 };
1090 let namespace = cache_info.namespace.as_ref().map_or("", |v| v);
1091 let uri_len = uri.scheme_str().map_or(0, |scheme| scheme.len() + 3)
1095 + uri
1096 .authority()
1097 .map_or(0, |authority| authority.as_str().len())
1098 + uri.path().len()
1099 + uri.query().map_or(0, |query| query.len() + 1);
1100 let keys_len = cache_info
1108 .keys
1109 .as_ref()
1110 .map_or(0, |keys| keys.iter().map(|s| s.len() + 1).sum::<usize>());
1111 let mut key_buf = String::with_capacity(
1112 namespace.len() + keys_len + method.len() + 1 + uri_len,
1113 );
1114 key_buf.push_str(namespace);
1115 if let Some(keys) = &cache_info.keys {
1117 for k in keys {
1118 key_buf.push_str(k);
1119 key_buf.push(':');
1120 }
1121 }
1122 key_buf.push_str(method);
1124 key_buf.push(':');
1125 let _ = write!(&mut key_buf, "{uri}");
1126
1127 CacheKey::new(key_buf, namespace)
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132 use super::*;
1133 use bytes::Bytes;
1134 use bytes::BytesMut;
1135 use pingora::cache::key::CacheHashKey;
1136 use pingora::protocols::tls::SslDigest;
1137 use pingora::protocols::tls::SslDigestExtension;
1138 use pretty_assertions::assert_eq;
1139 use std::{sync::Arc, time::Duration};
1140
1141 #[test]
1142 fn test_ctx_new() {
1143 let ctx = Ctx::new();
1144 let elapsed_ms = ctx.timing.created_at.elapsed().as_millis();
1147 assert!(elapsed_ms < 100, "created_at should be a recent timestamp");
1148 assert!(ctx.cache.is_none());
1150 assert!(ctx.features.is_none());
1151 assert_eq!(ctx.conn.id, 0);
1152 }
1153
1154 #[test]
1156 fn test_add_and_get_variable() {
1157 let mut ctx = Ctx::new();
1158 assert!(
1159 ctx.get_variable("key1").is_none(),
1160 "Should be None before adding"
1161 );
1162
1163 ctx.add_variable("key1", "value1");
1164 ctx.add_variable("key2", "value2");
1165
1166 assert_eq!(ctx.get_variable("key1"), Some("value1"));
1167 assert_eq!(ctx.get_variable("key2"), Some("value2"));
1168 assert_eq!(ctx.get_variable("nonexistent"), None);
1169 }
1170
1171 #[test]
1173 fn test_get_time_field() {
1174 let mut ctx = Ctx::new();
1175
1176 ctx.timing.upstream_response = Some(100);
1178 assert_eq!(ctx.get_upstream_response_time(), Some(100));
1179
1180 ctx.timing.upstream_response = Some(-1);
1182 assert_eq!(
1183 ctx.get_upstream_response_time(),
1184 None,
1185 "Time exceeding one hour should be None"
1186 );
1187
1188 ctx.timing.upstream_response = None;
1190 assert_eq!(ctx.get_upstream_response_time(), None);
1191 }
1192
1193 #[test]
1196 fn test_ctx_log_field_names() {
1197 for (name, field) in [
1198 ("connection_id", CtxLogField::ConnectionId),
1199 (
1200 "upstream_tcp_connect_time_human",
1201 CtxLogField::UpstreamTcpConnectTimeHuman,
1202 ),
1203 (
1204 "upstream_connect_offload_wait_time",
1205 CtxLogField::UpstreamConnectOffloadWaitTime,
1206 ),
1207 ("tls_version", CtxLogField::TlsVersion),
1208 ("ja4", CtxLogField::Ja4),
1209 ("ja4_r", CtxLogField::Ja4R),
1210 ("ja4_o", CtxLogField::Ja4O),
1211 ("ja4_ro", CtxLogField::Ja4Ro),
1212 ("compression_ratio", CtxLogField::CompressionRatio),
1213 ("service_time_human", CtxLogField::ServiceTimeHuman),
1214 ] {
1215 assert_eq!(Ok(field), name.parse::<CtxLogField>(), "{name}");
1216 assert_eq!(name, field.to_string());
1217 }
1218 assert_eq!(true, "unknown_key".parse::<CtxLogField>().is_err());
1219
1220 let mut ctx = Ctx::new();
1221 ctx.conn.id = 7;
1222 let mut by_name = BytesMut::new();
1223 ctx.append_log_value(&mut by_name, "connection_id");
1224 let mut by_field = BytesMut::new();
1225 ctx.append_log_field(&mut by_field, CtxLogField::ConnectionId);
1226 assert_eq!(b"7", by_name.as_ref());
1227 assert_eq!(by_name, by_field);
1228 }
1229
1230 #[test]
1233 fn test_ja4_log_fields() {
1234 let mut ctx = Ctx::new();
1235 let value = |ctx: &Ctx, key: &str| {
1236 let mut buf = BytesMut::new();
1237 ctx.append_log_value(&mut buf, key);
1238 String::from_utf8(buf.to_vec()).unwrap()
1239 };
1240 assert_eq!("", value(&ctx, "ja4"));
1241 let fingerprint = Ja4Fingerprint::from_client_hello(
1242 &crate::ja4::testing::spec_example_body(),
1243 )
1244 .unwrap();
1245 ctx.conn.ja4 = Some(Arc::new(fingerprint.clone()));
1246 assert_eq!("t13d1516h2_8daaf6152771_e5627efa2ab1", value(&ctx, "ja4"));
1247 assert_eq!(fingerprint.ja4_r(), value(&ctx, "ja4_r"));
1248 assert_eq!(fingerprint.ja4_o(), value(&ctx, "ja4_o"));
1249 assert_eq!(fingerprint.ja4_ro(), value(&ctx, "ja4_ro"));
1250 }
1251
1252 #[test]
1254 fn test_append_log_value_coverage() {
1255 let mut ctx = Ctx::new();
1256 let mut buf = BytesMut::new();
1258 ctx.append_log_value(&mut buf, "unknown_key");
1259 assert!(buf.is_empty(), "Unknown key should not append anything");
1260
1261 buf = BytesMut::new();
1263 ctx.conn.reused = true;
1264 ctx.append_log_value(&mut buf, "connection_reused");
1265 assert_eq!(&buf[..], b"true");
1266
1267 ctx.conn.tls_version = Some("TLSv1.3".into());
1269 buf = BytesMut::new();
1270 ctx.append_log_value(&mut buf, "tls_version");
1271 assert_eq!(&buf[..], b"TLSv1.3");
1272
1273 std::thread::sleep(Duration::from_millis(11));
1275 buf = BytesMut::new();
1276 ctx.append_log_value(&mut buf, "service_time");
1277 let service_time: u64 =
1278 std::str::from_utf8(&buf[..]).unwrap().parse().unwrap();
1279 assert!(service_time >= 10, "Service time should be at least 10ms");
1280 }
1281
1282 #[test]
1284 fn test_get_cache_key() {
1285 let method = "GET";
1286 let uri = Uri::from_static("https://example.com/path");
1287
1288 let ctx_no_cache = Ctx::new();
1290 let key1 = get_cache_key(&ctx_no_cache, method, &uri);
1291 assert_eq!(key1.user_tag, "");
1292 assert_eq!(key1.primary_key_str(), Some(""));
1293
1294 let mut ctx_with_ns = Ctx::new();
1296 ctx_with_ns.cache = Some(Box::new(CacheInfo {
1297 namespace: Some("my-ns".to_string()),
1298 ..Default::default()
1299 }));
1300 let key2 = get_cache_key(&ctx_with_ns, method, &uri);
1301 assert_eq!(key2.user_tag, "my-ns");
1302 assert_eq!(
1303 key2.primary_key_str(),
1304 Some("my-nsGET:https://example.com/path")
1305 );
1306 assert_eq!(key2.primary(), "3f45c68799da5997559d474ba4b5775c");
1310
1311 let mut ctx_with_keys = Ctx::new();
1313 ctx_with_keys.cache = Some(Box::new(CacheInfo {
1314 namespace: Some("my-ns".to_string()),
1315 keys: Some(vec!["user-123".to_string(), "desktop".to_string()]),
1316 ..Default::default()
1317 }));
1318 let key3 = get_cache_key(&ctx_with_keys, method, &uri);
1319 assert_eq!(key3.user_tag, "my-ns");
1320 assert_eq!(
1321 key3.primary_key_str(),
1322 Some("my-nsuser-123:desktop:GET:https://example.com/path")
1323 );
1324 }
1325
1326 #[test]
1329 fn test_generate_server_timing() {
1330 let mut ctx = Ctx::new();
1331 ctx.timing.upstream_connect = Some(1);
1332 ctx.timing.upstream_processing = Some(2);
1333 ctx.timing.cache_lookup = Some(6);
1334 ctx.timing.cache_lock = Some(7);
1335 ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
1336
1337 let timing_header = ctx.generate_server_timing();
1338
1339 assert!(timing_header.contains("upstream.connect;dur=1"));
1341 assert!(timing_header.contains("upstream.processing;dur=2"));
1342 assert!(timing_header.contains("upstream;dur=3"));
1343 assert!(timing_header.contains("cache.lookup;dur=6"));
1344 assert!(timing_header.contains("cache.lock;dur=7"));
1345 assert!(timing_header.contains("cache;dur=13"));
1346 assert!(timing_header.contains("plugin.plugin1;dur=100"));
1347 assert!(timing_header.contains("plugin;dur=100"));
1348 assert!(timing_header.contains("total;dur="));
1349 }
1350
1351 #[test]
1352 fn test_format_duration() {
1353 let mut buf = BytesMut::new();
1354 format_duration(&mut buf, (3600 + 3500) * 1000);
1355 assert_eq!(b"1.9h", buf.as_ref());
1356
1357 buf = BytesMut::new();
1358 format_duration(&mut buf, (3600 + 1800) * 1000);
1359 assert_eq!(b"1.5h", buf.as_ref());
1360
1361 buf = BytesMut::new();
1362 format_duration(&mut buf, (3600 + 100) * 1000);
1363 assert_eq!(b"1h", buf.as_ref());
1364
1365 buf = BytesMut::new();
1366 format_duration(&mut buf, (60 + 50) * 1000);
1367 assert_eq!(b"1.8m", buf.as_ref());
1368
1369 buf = BytesMut::new();
1370 format_duration(&mut buf, (60 + 2) * 1000);
1371 assert_eq!(b"1m", buf.as_ref());
1372
1373 buf = BytesMut::new();
1374 format_duration(&mut buf, 1000);
1375 assert_eq!(b"1s", buf.as_ref());
1376
1377 buf = BytesMut::new();
1378 format_duration(&mut buf, 512);
1379 assert_eq!(b"512ms", buf.as_ref());
1380
1381 buf = BytesMut::new();
1382 format_duration(&mut buf, 1112);
1383 assert_eq!(b"1.1s", buf.as_ref());
1384 }
1385
1386 #[test]
1387 fn test_add_variable() {
1388 let mut ctx = Ctx::new();
1389 ctx.add_variable("key1", "value1");
1390 ctx.add_variable("key2", "value2");
1391 ctx.extend_variables(AHashMap::from([
1392 ("key3".to_string(), "value3".to_string()),
1393 ("key4".to_string(), "value4".to_string()),
1394 ]));
1395 let variables =
1396 ctx.features.as_ref().unwrap().variables.as_ref().unwrap();
1397 assert_eq!(variables.get("key1"), Some(&"value1".to_string()));
1400 assert_eq!(variables.get("key2"), Some(&"value2".to_string()));
1401 assert_eq!(variables.get("key3"), Some(&"value3".to_string()));
1402 assert_eq!(variables.get("key4"), Some(&"value4".to_string()));
1403 }
1404
1405 #[test]
1406 fn test_cache_key() {
1407 let mut ctx = Ctx::new();
1408 ctx.push_cache_key("key1".to_string());
1409 ctx.extend_cache_keys(vec!["key2".to_string(), "key3".to_string()]);
1410 assert_eq!(
1411 vec!["key1".to_string(), "key2".to_string(), "key3".to_string()],
1412 ctx.cache.unwrap().keys.unwrap()
1413 );
1414
1415 let mut ctx = Ctx::new();
1416 ctx.cache.get_or_insert_default();
1417 let key = get_cache_key(
1418 &ctx,
1419 "GET",
1420 &Uri::from_static("https://example.com/path"),
1421 );
1422 assert_eq!(key.user_tag, "");
1423 assert_eq!(key.primary_key_str(), Some("GET:https://example.com/path"));
1424 }
1425
1426 #[test]
1427 fn test_state() {
1428 let mut ctx = Ctx::new();
1429
1430 let mut buf = BytesMut::new();
1431 ctx.conn.id = 10;
1432 ctx.append_log_value(&mut buf, "connection_id");
1433 assert_eq!(b"10", buf.as_ref());
1434
1435 buf = BytesMut::new();
1436 ctx.append_log_value(&mut buf, "upstream_reused");
1437 assert_eq!(b"false", buf.as_ref());
1438
1439 buf = BytesMut::new();
1440 ctx.upstream.reused = true;
1441 ctx.append_log_value(&mut buf, "upstream_reused");
1442 assert_eq!(b"true", buf.as_ref());
1443
1444 buf = BytesMut::new();
1445 ctx.upstream.address = "192.168.1.1:80".to_string();
1446 ctx.append_log_value(&mut buf, "upstream_addr");
1447 assert_eq!(b"192.168.1.1:80", buf.as_ref());
1448
1449 buf = BytesMut::new();
1450 ctx.upstream.status = Some(StatusCode::CREATED);
1451 ctx.append_log_value(&mut buf, "upstream_status");
1452 assert_eq!(b"201", buf.as_ref());
1453
1454 buf = BytesMut::new();
1455 ctx.state.processing_count = 10;
1456 ctx.append_log_value(&mut buf, "processing");
1457 assert_eq!(b"10", buf.as_ref());
1458
1459 buf = BytesMut::new();
1460 ctx.timing.upstream_connect = Some(1);
1461 ctx.append_log_value(&mut buf, "upstream_connect_time");
1462 assert_eq!(b"1", buf.as_ref());
1463
1464 buf = BytesMut::new();
1465 ctx.append_log_value(&mut buf, "upstream_connect_time_human");
1466 assert_eq!(b"1ms", buf.as_ref());
1467
1468 buf = BytesMut::new();
1469 ctx.upstream.connected_count = Some(30);
1470 ctx.append_log_value(&mut buf, "upstream_connected");
1471 assert_eq!(b"30", buf.as_ref());
1472
1473 buf = BytesMut::new();
1474 ctx.timing.upstream_processing = Some(2);
1475 ctx.append_log_value(&mut buf, "upstream_processing_time");
1476 assert_eq!(b"2", buf.as_ref());
1477
1478 buf = BytesMut::new();
1479 ctx.append_log_value(&mut buf, "upstream_processing_time_human");
1480 assert_eq!(b"2ms", buf.as_ref());
1481
1482 buf = BytesMut::new();
1483 ctx.timing.upstream_response = Some(3);
1484 ctx.append_log_value(&mut buf, "upstream_response_time");
1485 assert_eq!(b"3", buf.as_ref());
1486
1487 buf = BytesMut::new();
1488 ctx.append_log_value(&mut buf, "upstream_response_time_human");
1489 assert_eq!(b"3ms", buf.as_ref());
1490
1491 buf = BytesMut::new();
1492 ctx.timing.upstream_tcp_connect = Some(100);
1493 ctx.append_log_value(&mut buf, "upstream_tcp_connect_time");
1494 assert_eq!(b"100", buf.as_ref());
1495
1496 buf = BytesMut::new();
1497 ctx.append_log_value(&mut buf, "upstream_tcp_connect_time_human");
1498 assert_eq!(b"100ms", buf.as_ref());
1499
1500 buf = BytesMut::new();
1501 ctx.timing.upstream_tls_handshake = Some(110);
1502 ctx.append_log_value(&mut buf, "upstream_tls_handshake_time");
1503 assert_eq!(b"110", buf.as_ref());
1504
1505 buf = BytesMut::new();
1506 ctx.timing.upstream_connect_offload_wait = Some(3);
1507 ctx.append_log_value(&mut buf, "upstream_connect_offload_wait_time");
1508 assert_eq!(b"3", buf.as_ref());
1509 buf = BytesMut::new();
1510 ctx.append_log_value(
1511 &mut buf,
1512 "upstream_connect_offload_wait_time_human",
1513 );
1514 assert_eq!(b"3ms", buf.as_ref());
1515
1516 buf = BytesMut::new();
1517 ctx.append_log_value(&mut buf, "upstream_tls_handshake_time_human");
1518 assert_eq!(b"110ms", buf.as_ref());
1519
1520 buf = BytesMut::new();
1521 ctx.timing.upstream_connection_duration = Some(120);
1522 ctx.append_log_value(&mut buf, "upstream_connection_time");
1523 assert_eq!(b"120", buf.as_ref());
1524
1525 buf = BytesMut::new();
1526 ctx.append_log_value(&mut buf, "upstream_connection_time_human");
1527 assert_eq!(b"120ms", buf.as_ref());
1528
1529 buf = BytesMut::new();
1530 ctx.upstream.location = "pingap".to_string().into();
1531 ctx.append_log_value(&mut buf, "location");
1532 assert_eq!(b"pingap", buf.as_ref());
1533
1534 buf = BytesMut::new();
1535 ctx.timing.connection_duration = 4;
1536 ctx.append_log_value(&mut buf, "connection_time");
1537 assert_eq!(b"4", buf.as_ref());
1538
1539 buf = BytesMut::new();
1540 ctx.append_log_value(&mut buf, "connection_time_human");
1541 assert_eq!(b"4ms", buf.as_ref());
1542
1543 buf = BytesMut::new();
1544 ctx.conn.reused = false;
1545 ctx.append_log_value(&mut buf, "connection_reused");
1546 assert_eq!(b"false", buf.as_ref());
1547
1548 buf = BytesMut::new();
1549 ctx.conn.reused = true;
1550 ctx.append_log_value(&mut buf, "connection_reused");
1551 assert_eq!(b"true", buf.as_ref());
1552
1553 buf = BytesMut::new();
1554 ctx.conn.tls_version = Some("TLSv1.3".into());
1555 ctx.append_log_value(&mut buf, "tls_version");
1556 assert_eq!(b"TLSv1.3", buf.as_ref());
1557
1558 buf = BytesMut::new();
1559 ctx.conn.tls_cipher =
1560 Some("ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into());
1561 ctx.append_log_value(&mut buf, "tls_cipher");
1562 assert_eq!(b"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", buf.as_ref());
1563
1564 buf = BytesMut::new();
1565 ctx.timing.tls_handshake = Some(101);
1566 ctx.append_log_value(&mut buf, "tls_handshake_time");
1567 assert_eq!(b"101", buf.as_ref());
1568
1569 buf = BytesMut::new();
1570 ctx.append_log_value(&mut buf, "tls_handshake_time_human");
1571 assert_eq!(b"101ms", buf.as_ref());
1572
1573 {
1574 let features = ctx.features.get_or_insert_default();
1575 features.compression_stat = Some(CompressionStat {
1576 in_bytes: 1024,
1577 out_bytes: 500,
1578 duration: Duration::from_millis(5),
1579 ..Default::default()
1580 })
1581 }
1582
1583 buf = BytesMut::new();
1584 ctx.append_log_value(&mut buf, "compression_time");
1585 assert_eq!(b"5", buf.as_ref());
1586
1587 buf = BytesMut::new();
1588 ctx.append_log_value(&mut buf, "compression_time_human");
1589 assert_eq!(b"5ms", buf.as_ref());
1590
1591 buf = BytesMut::new();
1592 ctx.append_log_value(&mut buf, "compression_ratio");
1593 assert_eq!(b"2.0", buf.as_ref());
1594
1595 buf = BytesMut::new();
1596 ctx.timing.cache_lookup = Some(6);
1597 ctx.append_log_value(&mut buf, "cache_lookup_time");
1598 assert_eq!(b"6", buf.as_ref());
1599
1600 buf = BytesMut::new();
1601 ctx.append_log_value(&mut buf, "cache_lookup_time_human");
1602 assert_eq!(b"6ms", buf.as_ref());
1603
1604 buf = BytesMut::new();
1605 ctx.timing.cache_lock = Some(7);
1606 ctx.append_log_value(&mut buf, "cache_lock_time");
1607 assert_eq!(b"7", buf.as_ref());
1608
1609 buf = BytesMut::new();
1610 ctx.append_log_value(&mut buf, "cache_lock_time_human");
1611 assert_eq!(b"7ms", buf.as_ref());
1612 }
1613
1614 #[test]
1615 fn test_add_plugin_processing_time() {
1616 let mut ctx = Ctx::new();
1617 ctx.add_plugin_processing_time(&Arc::from("plugin1"), 100);
1618 ctx.add_plugin_processing_time(&Arc::from("plugin2"), 200);
1619 assert_eq!(
1620 ctx.features.unwrap().plugin_processing_times,
1621 Some(vec![
1622 (Arc::from("plugin1"), 100),
1623 (Arc::from("plugin2"), 200)
1624 ])
1625 );
1626 }
1627
1628 #[test]
1629 fn test_get_digest_detail() {
1630 let mut digest = Digest::default();
1631 let detail = get_digest_detail(&digest);
1632 assert_eq!(detail.connection_reused, false);
1633 assert_eq!(detail.connection_time, 0);
1634 assert_eq!(detail.tcp_established, 0);
1635 assert_eq!(detail.tls_established, 0);
1636 assert_eq!(detail.tls_version, None);
1637 assert_eq!(detail.tls_cipher, None);
1638
1639 digest.timing_digest.push(Some(TimingDigest {
1640 established_ts: SystemTime::UNIX_EPOCH
1641 .checked_add(Duration::from_secs(5))
1642 .unwrap(),
1643 ..Default::default()
1644 }));
1645 digest.timing_digest.push(Some(TimingDigest {
1646 established_ts: SystemTime::UNIX_EPOCH
1647 .checked_add(Duration::from_secs(3))
1648 .unwrap(),
1649 ..Default::default()
1650 }));
1651 digest.ssl_digest = Some(Arc::new(SslDigest {
1652 version: "1.3".into(),
1653 cipher: "123".into(),
1654 organization: Some("cloudflare".to_string()),
1655 serial_number: Some(
1656 "0x00000000000000000000000000000abc".to_string(),
1657 ),
1658 cert_digest: vec![],
1659 extension: SslDigestExtension::default(),
1660 }));
1661 let detail = get_digest_detail(&digest);
1662 assert_eq!(detail.connection_reused, true);
1663 assert_eq!(detail.tcp_established, 5000);
1664 assert_eq!(detail.tls_established, 3000);
1665 assert_eq!(detail.tls_version.as_deref(), Some("1.3"));
1666 assert_eq!(detail.tls_cipher.as_deref(), Some("123"));
1667 assert_eq!(detail.tcp_connect, None);
1669 assert_eq!(detail.tls_handshake, None);
1670
1671 digest.timing_digest = vec![
1673 Some(TimingDigest {
1674 establishment_duration: Some(Duration::from_millis(12)),
1675 ..Default::default()
1676 }),
1677 Some(TimingDigest {
1678 establishment_duration: Some(Duration::from_millis(34)),
1679 ..Default::default()
1680 }),
1681 ];
1682 let detail = get_digest_detail(&digest);
1683 assert_eq!(detail.tcp_connect, Some(12));
1684 assert_eq!(detail.tls_handshake, Some(34));
1685 assert_eq!(detail.connect_offload_wait, None);
1687
1688 digest.timing_digest[0] = Some(TimingDigest {
1690 establishment_duration: Some(Duration::from_millis(12)),
1691 offload_wait_duration: Some(Duration::from_millis(2)),
1692 ..Default::default()
1693 });
1694 let detail = get_digest_detail(&digest);
1695 assert_eq!(detail.connect_offload_wait, Some(2));
1696
1697 digest.ssl_digest = None;
1699 digest.timing_digest.truncate(1);
1700 let detail = get_digest_detail(&digest);
1701 assert_eq!(detail.tcp_connect, Some(12));
1702 assert_eq!(detail.tls_handshake, None);
1703 }
1704
1705 #[test]
1706 fn test_update_upstream_timing_from_digest() {
1707 let measured = |tcp: u64, tls: u64| Digest {
1708 timing_digest: vec![
1709 Some(TimingDigest {
1710 establishment_duration: Some(Duration::from_millis(tcp)),
1711 ..Default::default()
1712 }),
1713 Some(TimingDigest {
1714 establishment_duration: Some(Duration::from_millis(tls)),
1715 ..Default::default()
1716 }),
1717 ],
1718 ssl_digest: Some(Arc::new(SslDigest {
1719 version: "1.3".into(),
1720 cipher: "123".into(),
1721 organization: None,
1722 serial_number: None,
1723 cert_digest: vec![],
1724 extension: SslDigestExtension::default(),
1725 })),
1726 ..Default::default()
1727 };
1728
1729 let mut ctx = Ctx::new();
1732 ctx.timing.upstream_connect = Some(100);
1733 ctx.update_upstream_timing_from_digest(&measured(12, 34), false);
1734 assert_eq!(Some(12), ctx.timing.upstream_tcp_connect);
1735 assert_eq!(Some(34), ctx.timing.upstream_tls_handshake);
1736 assert_eq!(None, ctx.timing.upstream_connect_offload_wait);
1737
1738 let mut ctx = Ctx::new();
1741 let mut offloaded = measured(12, 34);
1742 offloaded.timing_digest[0] = Some(TimingDigest {
1743 establishment_duration: Some(Duration::from_millis(12)),
1744 offload_wait_duration: Some(Duration::from_millis(2)),
1745 ..Default::default()
1746 });
1747 ctx.update_upstream_timing_from_digest(&offloaded, false);
1748 assert_eq!(Some(2), ctx.timing.upstream_connect_offload_wait);
1749
1750 let mut ctx = Ctx::new();
1752 ctx.timing.upstream_connect = Some(100);
1753 ctx.update_upstream_timing_from_digest(&measured(12, 34), true);
1754 assert_eq!(None, ctx.timing.upstream_tcp_connect);
1755 assert_eq!(None, ctx.timing.upstream_tls_handshake);
1756
1757 let mut ctx = Ctx::new();
1760 ctx.timing.upstream_connect = Some(100);
1761 let mut unmeasured = measured(0, 0);
1762 unmeasured.timing_digest = vec![
1763 Some(TimingDigest {
1764 established_ts: SystemTime::UNIX_EPOCH
1765 .checked_add(Duration::from_millis(1_000))
1766 .unwrap(),
1767 ..Default::default()
1768 }),
1769 Some(TimingDigest {
1770 established_ts: SystemTime::UNIX_EPOCH
1771 .checked_add(Duration::from_millis(1_030))
1772 .unwrap(),
1773 ..Default::default()
1774 }),
1775 ];
1776 ctx.update_upstream_timing_from_digest(&unmeasured, false);
1777 assert_eq!(Some(70), ctx.timing.upstream_tcp_connect);
1778 assert_eq!(Some(30), ctx.timing.upstream_tls_handshake);
1779 }
1780
1781 #[test]
1782 fn test_modify_body_handler() {
1783 let mut ctx = Ctx::default();
1784
1785 struct TestHandler {}
1786 impl ModifyResponseBody for TestHandler {
1787 fn handle(
1788 &mut self,
1789 _session: &Session,
1790 body: &mut Option<bytes::Bytes>,
1791 _end_of_stream: bool,
1792 ) -> pingora::Result<()> {
1793 *body = Some(Bytes::from("test"));
1794 Ok(())
1795 }
1796 }
1797
1798 ctx.add_modify_body_handler("test", Box::new(TestHandler {}));
1799 assert_eq!(true, ctx.get_modify_body_handler("test").is_some());
1800 }
1801}