keyhog_scanner/decode/
hex.rs1use super::pipeline::{extract_encoded_values, push_decoded_text_chunk};
2use super::{Decoder, EncodedString};
3use keyhog_core::Chunk;
4
5pub(super) struct HexDecoder;
6
7impl Decoder for HexDecoder {
8 fn name(&self) -> &'static str {
9 "hex"
10 }
11
12 fn decode_chunk(&self, chunk: &Chunk) -> Vec<Chunk> {
13 let mut decoded_chunks = Vec::new();
14 for hex_match in find_hex_strings(&chunk.data, 32) {
15 if let Ok(decoded) = hex_decode(&hex_match.value)
16 && let Ok(text) = String::from_utf8(decoded)
17 {
18 push_decoded_text_chunk(&mut decoded_chunks, chunk, text, self.name());
19 }
20 }
21 decoded_chunks
22 }
23}
24
25fn find_hex_strings(text: &str, min_length: usize) -> Vec<EncodedString> {
26 let mut results = Vec::new();
27 for candidate in extract_encoded_values(text) {
28 if candidate.len() >= min_length
29 && candidate.len() % 2 == 0
30 && candidate.chars().all(|ch| ch.is_ascii_hexdigit())
31 {
32 results.push(EncodedString { value: candidate });
33 }
34 }
35 results
36}
37
38const MAX_HEX_INPUT_LEN: usize = 32 * 1024 * 1024; #[allow(clippy::result_unit_err)]
42pub fn hex_decode(input: &str) -> Result<Vec<u8>, ()> {
43 if !input.len().is_multiple_of(2) || input.len() > MAX_HEX_INPUT_LEN {
44 return Err(());
45 }
46 let mut decoded_bytes = Vec::with_capacity(input.len() / 2);
47 for offset in (0..input.len()).step_by(2) {
48 let high = hex_val(input.as_bytes()[offset])?;
49 let low = hex_val(input.as_bytes()[offset + 1])?;
50 decoded_bytes.push((high << 4) | low);
51 }
52 Ok(decoded_bytes)
53}
54
55pub(super) fn hex_val(byte: u8) -> Result<u8, ()> {
56 match byte {
57 b'0'..=b'9' => Ok(byte - b'0'),
58 b'a'..=b'f' => Ok(byte - b'a' + 10),
59 b'A'..=b'F' => Ok(byte - b'A' + 10),
60 _ => Err(()),
61 }
62}