use crate::{EngineCache, Stats};
use asc_apk::{Apk, Entry, Prefix, PrefixStep};
use asc_cache::Tier;
use asc_dex::Dex;
use asc_findrefs::{CompiledQuery, FindRefs};
use std::sync::atomic::{AtomicUsize, Ordering};
const MIN_GAIN: u64 = 256 * 1024;
const SLACK: usize = 64 * 1024;
const WORTH_BELOW: f64 = 0.45;
const WARMUP: usize = 4;
pub(crate) struct Probe<'q> {
queries: Vec<&'q CompiledQuery>,
started: AtomicUsize,
probed: AtomicUsize,
matched: AtomicUsize,
}
impl<'q> Probe<'q> {
pub(crate) fn new(queries: Vec<&'q CompiledQuery>) -> Probe<'q> {
Probe { queries, started: AtomicUsize::new(0), probed: AtomicUsize::new(0), matched: AtomicUsize::new(0) }
}
pub(crate) fn skips(&self, apk: &Apk, entry: &Entry, cache: Option<&EngineCache>, stats: &Stats) -> bool {
if !worth_a_prefix(entry, cache) || !self.admit() {
return false;
}
let skip = self.cannot_match(apk, entry);
self.probed.fetch_add(1, Ordering::Relaxed);
if skip {
stats.skipped.fetch_add(1, Ordering::Relaxed);
} else {
self.matched.fetch_add(1, Ordering::Relaxed);
}
skip
}
fn admit(&self) -> bool {
let probed = self.probed.load(Ordering::Relaxed);
if probed < WARMUP {
return self.started.fetch_add(1, Ordering::Relaxed) < WARMUP;
}
self.matched.load(Ordering::Relaxed) * 2 <= probed
}
fn cannot_match(&self, apk: &Apk, entry: &Entry) -> bool {
let Some(bytes) = id_prefix(apk, entry) else { return false };
let Ok(dex) = Dex::with_name(&bytes, &entry.name) else { return false };
let fr = FindRefs::new(&dex);
self.queries.iter().all(|q| fr.cannot_match(q).unwrap_or(false))
}
}
pub(crate) fn worth_a_prefix(entry: &Entry, cache: Option<&EngineCache>) -> bool {
entry.method == asc_apk::METHOD_DEFLATE && entry.uncomp_size > MIN_GAIN && !kept(cache, entry)
}
pub(crate) fn id_prefix(apk: &Apk, entry: &Entry) -> Option<Vec<u8>> {
let size = entry.uncomp_size as usize;
let budget = (size as f64 * WORTH_BELOW) as usize;
let prefix = apk.inflate_until(entry, |out| {
if out.len() >= 8 && (&out[..4] != b"dex\n" || &out[..8] == asc_dex::container::DEX041_MAGIC) {
return PrefixStep::Abort;
}
if asc_dex::quick::leading_string_data_off(out).is_some_and(|off| off > budget) {
return PrefixStep::Abort;
}
match asc_dex::quick::id_tables_needed_len(out) {
Some(needed) if needed + SLACK <= budget => PrefixStep::Continue(needed + SLACK),
Some(_) => PrefixStep::Abort,
None if out.len() > budget => PrefixStep::Abort,
None => PrefixStep::Continue(0),
}
});
match prefix {
Ok(Prefix::Partial(b) | Prefix::Complete(b)) => Some(b),
Ok(Prefix::Aborted(_)) | Err(_) => None,
}
}
fn kept(cache: Option<&EngineCache>, entry: &Entry) -> bool {
cache.is_some_and(|c| c.memory.is_some() || c.disk.as_ref().is_some_and(|d| d.uses(Tier::Dex) && d.admits_dex(entry.uncomp_size)))
}