use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum CacheTransition {
ColdStart,
WarmLoad,
SteadyState,
Disabled,
}
impl CacheTransition {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::ColdStart => "cold-start",
Self::WarmLoad => "warm-load",
Self::SteadyState => "steady-state",
Self::Disabled => "disabled",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct CacheTransitionRecord {
pub(crate) layer: keyhog_profile::CacheLayerKindV2,
pub(crate) evidence: &'static str,
pub(crate) transition: CacheTransition,
}
fn record(
layer: keyhog_profile::CacheLayerKindV2,
evidence: &'static str,
transition: CacheTransition,
) -> CacheTransitionRecord {
CacheTransitionRecord {
layer,
evidence,
transition,
}
}
pub(crate) fn detector_transition() -> CacheTransitionRecord {
record(
keyhog_profile::CacheLayerKindV2::Detector,
"detector-plan-compiled-in-process",
CacheTransition::ColdStart,
)
}
pub(crate) fn merkle_load_transition(
status: Option<&keyhog_core::MerkleLoadStatus>,
) -> CacheTransitionRecord {
use keyhog_core::MerkleLoadStatus;
let (evidence, transition) = match status {
None => ("merkle-not-configured", CacheTransition::Disabled),
Some(MerkleLoadStatus::Missing { .. }) => {
("merkle-load-missing", CacheTransition::ColdStart)
}
Some(MerkleLoadStatus::Loaded { .. }) => ("merkle-load-ok", CacheTransition::WarmLoad),
Some(MerkleLoadStatus::ReadFailed { .. }) => {
("merkle-load-read-failed", CacheTransition::ColdStart)
}
Some(MerkleLoadStatus::ParseFailed { .. }) => {
("merkle-load-parse-failed", CacheTransition::ColdStart)
}
Some(MerkleLoadStatus::SchemaMismatch { .. }) => {
("merkle-load-schema-mismatch", CacheTransition::ColdStart)
}
Some(MerkleLoadStatus::SpecChanged { .. }) => {
("merkle-load-spec-changed", CacheTransition::ColdStart)
}
Some(MerkleLoadStatus::InvalidEntryHash { .. }) => {
("merkle-load-invalid-entry-hash", CacheTransition::ColdStart)
}
};
record(
keyhog_profile::CacheLayerKindV2::Merkle,
evidence,
transition,
)
}
pub(crate) fn refine_merkle_warmth(
transition: CacheTransition,
skipped_unchanged: u64,
scanned_chunks: u64,
) -> CacheTransition {
if transition == CacheTransition::WarmLoad && skipped_unchanged > 0 && scanned_chunks == 0 {
CacheTransition::SteadyState
} else {
transition
}
}
pub(crate) fn autoroute_transition(cache_path: Option<&std::path::Path>) -> CacheTransitionRecord {
let (evidence, transition) = match cache_path {
None => ("autoroute-cache-not-configured", CacheTransition::Disabled),
Some(path) if path.exists() => (
"autoroute-decision-cache-present",
CacheTransition::WarmLoad,
),
Some(_) => (
"autoroute-decision-cache-absent",
CacheTransition::ColdStart,
),
};
record(
keyhog_profile::CacheLayerKindV2::Autoroute,
evidence,
transition,
)
}
pub(crate) fn verifier_transition(enabled: bool, cache_hits: u64) -> CacheTransitionRecord {
let (evidence, transition) = if !enabled {
("verifier-policy-disabled", CacheTransition::Disabled)
} else if cache_hits > 0 {
("verifier-cache-hit-spans", CacheTransition::WarmLoad)
} else {
(
"verifier-cache-empty-at-process-start",
CacheTransition::ColdStart,
)
};
record(
keyhog_profile::CacheLayerKindV2::Verifier,
evidence,
transition,
)
}
pub(crate) fn daemon_transition() -> CacheTransitionRecord {
record(
keyhog_profile::CacheLayerKindV2::Daemon,
"daemon-route-off-in-process",
CacheTransition::Disabled,
)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SourcePartitionRecord {
pub(crate) index: u64,
pub(crate) kind: String,
pub(crate) units: u64,
pub(crate) bytes: u64,
}
pub(crate) const MAX_RECORDED_PARTITIONS: usize = 1024;
#[derive(Default)]
struct PartitionSink {
records: Vec<SourcePartitionRecord>,
dropped: u64,
}
static SOURCE_PARTITIONS: Mutex<PartitionSink> = Mutex::new(PartitionSink {
records: Vec::new(),
dropped: 0,
});
static MERKLE_SKIPPED_UNCHANGED: AtomicU64 = AtomicU64::new(0);
pub(crate) fn reset_workflow_state() {
let mut sink = SOURCE_PARTITIONS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()); sink.records.clear();
sink.dropped = 0;
MERKLE_SKIPPED_UNCHANGED.store(0, Ordering::Relaxed);
}
pub(crate) fn record_source_partition(kind: &str, units: u64, bytes: u64) {
let mut sink = SOURCE_PARTITIONS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()); if sink.records.len() >= MAX_RECORDED_PARTITIONS {
sink.dropped = sink.dropped.saturating_add(1);
return;
}
let index = u64::try_from(sink.records.len()).unwrap_or(u64::MAX); sink.records.push(SourcePartitionRecord {
index,
kind: kind.to_owned(),
units,
bytes,
});
}
pub(crate) fn take_source_partitions() -> (Vec<SourcePartitionRecord>, u64) {
let mut sink = SOURCE_PARTITIONS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()); (
std::mem::take(&mut sink.records),
std::mem::take(&mut sink.dropped),
)
}
pub(crate) fn record_merkle_skipped_unchanged(skipped: usize) {
MERKLE_SKIPPED_UNCHANGED.fetch_add(skipped as u64, Ordering::Relaxed);
}
pub(crate) fn merkle_skipped_unchanged() -> u64 {
MERKLE_SKIPPED_UNCHANGED.load(Ordering::Relaxed)
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct VerificationAggregate {
pub(crate) enabled: bool,
pub(crate) queued: u64,
pub(crate) network: u64,
pub(crate) cached: u64,
pub(crate) unverifiable: u64,
pub(crate) skipped: u64,
}
impl VerificationAggregate {
pub(crate) fn state_label(&self) -> &'static str {
if !self.enabled {
"disabled"
} else if self.queued == 0 {
"idle"
} else if self.cached > 0 && self.network == 0 {
"cached"
} else if self.network > 0 && self.cached == 0 {
"network"
} else if self.network > 0 && self.cached > 0 {
"mixed"
} else {
"queued"
}
}
}
pub(crate) fn aggregate_verification_findings(
enabled: bool,
findings: &[keyhog_core::VerifiedFinding],
) -> VerificationAggregate {
aggregate_verification_results(
enabled,
findings.iter().map(|finding| &finding.verification),
)
}
pub(crate) fn aggregate_verification_results<'a>(
enabled: bool,
results: impl ExactSizeIterator<Item = &'a keyhog_core::VerificationResult>,
) -> VerificationAggregate {
let mut aggregate = VerificationAggregate {
enabled,
..VerificationAggregate::default()
};
if !enabled {
aggregate.skipped = u64::try_from(results.len()).unwrap_or(u64::MAX); return aggregate;
}
for result in results {
match result {
keyhog_core::VerificationResult::Skipped => {
aggregate.skipped = aggregate.skipped.saturating_add(1);
}
keyhog_core::VerificationResult::Unverifiable => {
aggregate.queued = aggregate.queued.saturating_add(1);
aggregate.unverifiable = aggregate.unverifiable.saturating_add(1);
}
keyhog_core::VerificationResult::Live
| keyhog_core::VerificationResult::Revoked
| keyhog_core::VerificationResult::Dead
| keyhog_core::VerificationResult::RateLimited
| keyhog_core::VerificationResult::Error(_) => {
aggregate.queued = aggregate.queued.saturating_add(1);
aggregate.network = aggregate.network.saturating_add(1);
}
}
}
aggregate
}
pub(crate) fn count_verifier_cache_hits(spans: &[keyhog_profile::SpanRecordV2]) -> u64 {
let parent_of: std::collections::HashMap<u64, (keyhog_profile::MetricId, Option<u64>)> = spans
.iter()
.map(|span| {
let parent = match span.parent_span_id {
keyhog_profile::Evidence::Recorded { value } => Some(value),
keyhog_profile::Evidence::Unavailable { .. } => None,
};
(span.span_id, (span.metric_id, parent))
})
.collect();
let mut hits = 0_u64;
for span in spans {
if span.metric_id != keyhog_profile::MetricId::IncrementalLookup {
continue;
}
let mut cursor = match span.parent_span_id {
keyhog_profile::Evidence::Recorded { value } => Some(value),
keyhog_profile::Evidence::Unavailable { .. } => None,
};
for _ in 0..65 {
let Some(id) = cursor else { break };
let Some((metric, parent)) = parent_of.get(&id) else {
break;
};
if *metric == keyhog_profile::MetricId::LiveVerification {
hits = hits.saturating_add(1);
break;
}
cursor = *parent;
}
}
hits
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct DaemonRequestIdentity {
pub(crate) generation: String,
pub(crate) sequence: u64,
}
pub(crate) fn parse_daemon_request_identity(request_id: &str) -> Option<DaemonRequestIdentity> {
let (generation, sequence) = request_id.rsplit_once('-')?;
if generation.is_empty() || sequence.len() != 16 {
return None;
}
let sequence = u64::from_str_radix(sequence, 16).ok()?; Some(DaemonRequestIdentity {
generation: generation.to_owned(),
sequence,
})
}