use super::pipeline::{extract_encoded_values, push_decoded_text_chunk_spliced};
use super::{Decoder, EncodedString};
use keyhog_core::Chunk;
pub(super) struct HexDecoder;
impl Decoder for HexDecoder {
fn name(&self) -> &'static str {
"hex"
}
fn decode_chunk(&self, chunk: &Chunk) -> Vec<Chunk> {
let mut decoded_chunks = Vec::new();
for hex_match in find_hex_strings(&chunk.data, 16) {
let cleaned: String = hex_match.value.chars().filter(|c| *c != '_').collect();
if let Ok(decoded) = hex_decode(&cleaned) {
if let Ok(text) = String::from_utf8(decoded) {
push_decoded_text_chunk_spliced(
&mut decoded_chunks,
chunk,
&hex_match.value,
text,
self.name(),
);
}
}
}
decoded_chunks
}
}
pub fn find_hex_strings(text: &str, min_length: usize) -> Vec<EncodedString> {
let mut results = Vec::new();
for candidate in extract_encoded_values(text) {
let cleaned: String = candidate.chars().filter(|c| *c != '_').collect();
if cleaned.len() >= min_length
&& cleaned.len().is_multiple_of(2)
&& cleaned.chars().all(|ch| ch.is_ascii_hexdigit())
{
results.push(EncodedString { value: candidate });
}
}
results
}
const MAX_HEX_INPUT_LEN: usize = 32 * 1024 * 1024;
#[allow(clippy::result_unit_err)]
pub fn hex_decode(input: &str) -> Result<Vec<u8>, ()> {
let cleaned: String = input.chars().filter(|c| *c != '_').collect();
if !cleaned.len().is_multiple_of(2) || cleaned.len() > MAX_HEX_INPUT_LEN {
return Err(());
}
hex_simd::decode_to_vec(&cleaned).map_err(|_| ())
}
pub(super) fn hex_val(byte: u8) -> Result<u8, ()> {
match byte {
b'0'..=b'9' => Ok(byte - b'0'),
b'a'..=b'f' => Ok(byte - b'a' + 10),
b'A'..=b'F' => Ok(byte - b'A' + 10),
_ => Err(()),
}
}