use super::pipeline::{stream_candidate_refs_exact, with_extracted_value_spans};
use super::{DecodeAdmissionSketch, DecodeOutputSink, Decoder};
use keyhog_core::Chunk;
pub(crate) struct ReverseDecoder;
const MIN_REVERSE_LEN: usize = super::util::MIN_EVASION_DECODE_LEN;
const MIN_REVERSE_ALNUM_RUN: usize = 12;
impl ReverseDecoder {
pub(super) fn admission_sketch_with_policy(
&self,
chunk: &Chunk,
policy: &super::policy::CompiledDecodeTransformPolicy,
) -> DecodeAdmissionSketch {
if chunk.metadata.source_type.contains("/reverse") {
return DecodeAdmissionSketch::NONE;
}
with_extracted_value_spans(&chunk.data, |candidates| {
let mut count = 0usize;
let mut bytes = 0usize;
for candidate in candidates
.iter()
.filter(|candidate| is_reverse_candidate(candidate, policy))
{
count = count.saturating_add(1);
bytes = bytes.saturating_add(candidate.value.len());
}
if count == 0 {
DecodeAdmissionSketch::NONE
} else {
DecodeAdmissionSketch::possible(DecodeAdmissionSketch::REVERSE, count, bytes)
}
})
}
pub(super) fn decode_chunk_with_policy_into(
&self,
chunk: &Chunk,
policy: &super::policy::CompiledDecodeTransformPolicy,
sink: &mut dyn DecodeOutputSink,
) {
if chunk.metadata.source_type.contains("/reverse") {
return;
}
with_extracted_value_spans(&chunk.data, |candidates| {
stream_candidate_refs_exact(
sink,
chunk,
candidates
.iter()
.filter(|candidate| is_reverse_candidate(candidate, policy)),
|s| Ok(reverse_str(s)),
self.name(),
);
});
}
}
impl Decoder for ReverseDecoder {
fn name(&self) -> &'static str {
"reverse"
}
fn admission_sketch(&self, chunk: &Chunk) -> DecodeAdmissionSketch {
self.admission_sketch_with_policy(chunk, super::policy::bundled_compat_policy())
}
fn decode_chunk_into(&self, chunk: &Chunk, sink: &mut dyn DecodeOutputSink) {
self.decode_chunk_with_policy_into(chunk, super::policy::bundled_compat_policy(), sink);
}
}
fn is_reverse_candidate(
candidate: &super::pipeline::ExtractedValue,
policy: &super::policy::CompiledDecodeTransformPolicy,
) -> bool {
candidate.value.len() >= MIN_REVERSE_LEN
&& !crate::suppression::shape::looks_like_prefixed_hash_digest(&candidate.value)
&& looks_reversible_with_policy(&candidate.value, policy)
}
pub(crate) fn reverse_str(s: &str) -> String {
s.chars().rev().collect()
}
pub(crate) fn looks_reversible(candidate: &str) -> bool {
looks_reversible_with_policy(candidate, super::policy::bundled_compat_policy())
}
fn looks_reversible_with_policy(
candidate: &str,
policy: &super::policy::CompiledDecodeTransformPolicy,
) -> bool {
let bytes = candidate.as_bytes();
let mut run = 0usize;
let mut saw_long_run = false;
for &b in bytes.iter().rev() {
if b.is_ascii_alphanumeric() {
run += 1;
if run >= MIN_REVERSE_ALNUM_RUN {
saw_long_run = true;
break;
}
} else {
run = 0;
}
}
if !saw_long_run {
return false;
}
policy.reverse_matches(candidate)
}