use crate::crypto::decrypt_entry;
use crate::types::{Entry, SessionKey};
use aho_corasick::{AhoCorasick, Input};
#[allow(clippy::too_many_arguments)]
pub(crate) fn process_safe_region<F, G, E>(
buffer: &mut Vec<u8>,
ac: &AhoCorasick,
entries: &[Entry],
session_key: &SessionKey,
eof: bool,
max_hold: usize,
emit: &mut F,
on_aead_failure: &mut G,
) -> Result<(), E>
where
F: FnMut(&[u8]) -> Result<(), E>,
G: FnMut(usize) -> E,
{
let mut cursor = 0;
loop {
let remaining = buffer.len() - cursor;
let safe_end = if eof {
buffer.len()
} else if remaining > max_hold {
buffer.len() - max_hold
} else {
break;
};
if safe_end <= cursor {
break;
}
match ac.find(Input::new(&buffer[..]).range(cursor..)) {
Some(mat) if mat.start() < safe_end => {
let match_start = mat.start();
let match_end = mat.end();
let pattern_idx = mat.pattern().as_usize();
if match_start > cursor {
emit(&buffer[cursor..match_start])?;
}
let entry = &entries[pattern_idx];
match decrypt_entry(session_key, entry) {
Ok(plaintext) => {
let plaintext = zeroize::Zeroizing::new(plaintext);
emit(&plaintext)?;
}
Err(_) => {
return Err(on_aead_failure(pattern_idx));
}
}
cursor = match_end;
}
Some(_) => {
emit(&buffer[cursor..safe_end])?;
cursor = safe_end;
break;
}
None => {
emit(&buffer[cursor..safe_end])?;
cursor = safe_end;
break;
}
}
}
if cursor > 0 {
buffer.drain(..cursor);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_safe_region_no_matches() {
let mut buffer = b"hello world".to_vec();
use aho_corasick::MatchKind;
let fakes: Vec<&[u8]> = vec![];
let ac = AhoCorasick::builder()
.match_kind(MatchKind::LeftmostFirst)
.build(&fakes)
.unwrap();
let entries: Vec<Entry> = vec![];
let session_key = SessionKey::from_bytes([0u8; 32]);
let mut output = Vec::new();
let mut emit = |bytes: &[u8]| -> Result<(), ()> {
output.extend_from_slice(bytes);
Ok(())
};
let mut on_err = |_idx: usize| ();
process_safe_region(
&mut buffer,
&ac,
&entries,
&session_key,
true,
0,
&mut emit,
&mut on_err,
)
.unwrap();
assert_eq!(output, b"hello world");
assert!(buffer.is_empty());
}
}