use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, LazyLock, Mutex};
pub const DEFAULT_HIGH_WATERMARK: u64 = 2500;
pub const DEFAULT_LOW_WATERMARK: u64 = 2000;
pub const DEFAULT_WARNING_WATERMARK: u64 = 2250;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyCountEvent {
Warning { uid: u32, count: u64 },
Limit { uid: u32, count: u64 },
}
pub type ProxyCountCallback = Arc<dyn Fn(ProxyCountEvent) + Send + Sync>;
static PROXY_COUNT: AtomicU64 = AtomicU64::new(0);
static COUNT_BY_UID_ENABLED: AtomicBool = AtomicBool::new(false);
struct State {
high: u64,
low: u64,
warning: u64,
callback: Option<ProxyCountCallback>,
per_uid: HashMap<u32, UidEntry>,
}
#[derive(Default)]
struct UidEntry {
count: u64,
warning_fired: bool,
limit_fired: bool,
}
static STATE: LazyLock<Mutex<State>> = LazyLock::new(|| {
Mutex::new(State {
high: DEFAULT_HIGH_WATERMARK,
low: DEFAULT_LOW_WATERMARK,
warning: DEFAULT_WARNING_WATERMARK,
callback: None,
per_uid: HashMap::new(),
})
});
pub fn get_binder_proxy_count() -> u64 {
PROXY_COUNT.load(Ordering::Relaxed)
}
pub fn get_binder_proxy_count_for_uid(uid: u32) -> u64 {
let state = STATE.lock().expect("proxy_count state poisoned");
state.per_uid.get(&uid).map(|e| e.count).unwrap_or(0)
}
pub fn get_binder_proxy_counts_by_uid() -> Vec<(u32, u64)> {
let state = STATE.lock().expect("proxy_count state poisoned");
state
.per_uid
.iter()
.map(|(uid, e)| (*uid, e.count))
.collect()
}
pub fn set_binder_proxy_count_watermarks(high: u64, low: u64, warning: u64) {
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.high = high;
state.low = low;
state.warning = warning;
}
pub fn set_binder_proxy_count_event_callback(callback: Option<ProxyCountCallback>) {
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.callback = callback;
}
pub fn enable_count_by_uid(enabled: bool) {
COUNT_BY_UID_ENABLED.store(enabled, Ordering::Relaxed);
}
pub fn is_count_by_uid_enabled() -> bool {
COUNT_BY_UID_ENABLED.load(Ordering::Relaxed)
}
pub fn clear_count_by_uid() {
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.per_uid.clear();
}
pub(crate) fn on_proxy_create(uid: u32) -> bool {
PROXY_COUNT.fetch_add(1, Ordering::Relaxed);
if !COUNT_BY_UID_ENABLED.load(Ordering::Relaxed) {
return false;
}
let event = {
let mut state = STATE.lock().expect("proxy_count state poisoned");
let high = state.high;
let warning = state.warning;
let entry = state.per_uid.entry(uid).or_default();
entry.count += 1;
let count = entry.count;
let already_warning = entry.warning_fired;
let already_limit = entry.limit_fired;
if !already_limit && count >= high {
entry.limit_fired = true;
state
.callback
.as_ref()
.map(|cb| (cb.clone(), ProxyCountEvent::Limit { uid, count }))
} else if !already_warning && count >= warning && count < high {
entry.warning_fired = true;
state
.callback
.as_ref()
.map(|cb| (cb.clone(), ProxyCountEvent::Warning { uid, count }))
} else {
None
}
};
if let Some((cb, event)) = event {
cb(event);
}
true
}
pub(crate) fn on_proxy_drop(uid: u32, counted_by_uid: bool) {
PROXY_COUNT.fetch_sub(1, Ordering::Relaxed);
if !counted_by_uid {
return;
}
let mut state = STATE.lock().expect("proxy_count state poisoned");
let low = state.low;
if let Some(entry) = state.per_uid.get_mut(&uid) {
entry.count = entry.count.saturating_sub(1);
if entry.count <= low {
entry.limit_fired = false;
entry.warning_fired = false;
}
if entry.count == 0 {
state.per_uid.remove(&uid);
}
}
}
#[cfg(test)]
pub(crate) fn reset_for_test() {
PROXY_COUNT.store(0, Ordering::Relaxed);
COUNT_BY_UID_ENABLED.store(false, Ordering::Relaxed);
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.high = DEFAULT_HIGH_WATERMARK;
state.low = DEFAULT_LOW_WATERMARK;
state.warning = DEFAULT_WARNING_WATERMARK;
state.callback = None;
state.per_uid.clear();
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex as StdMutex;
static TEST_LOCK: StdMutex<()> = StdMutex::new(());
#[test]
fn global_counter_increments_and_decrements() {
let _g = TEST_LOCK.lock().unwrap();
reset_for_test();
assert_eq!(get_binder_proxy_count(), 0);
on_proxy_create(1000);
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count(), 3);
on_proxy_drop(1000, false);
assert_eq!(get_binder_proxy_count(), 2);
on_proxy_drop(1000, false);
on_proxy_drop(2000, false);
assert_eq!(get_binder_proxy_count(), 0);
}
#[test]
fn per_uid_disabled_by_default_skips_map() {
let _g = TEST_LOCK.lock().unwrap();
reset_for_test();
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count(), 2);
assert_eq!(get_binder_proxy_count_for_uid(1000), 0);
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
on_proxy_drop(1000, false);
on_proxy_drop(2000, false);
}
#[test]
fn per_uid_enabled_tracks_each_uid() {
let _g = TEST_LOCK.lock().unwrap();
reset_for_test();
enable_count_by_uid(true);
on_proxy_create(1000);
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count_for_uid(1000), 2);
assert_eq!(get_binder_proxy_count_for_uid(2000), 1);
assert_eq!(get_binder_proxy_count_for_uid(3000), 0);
let mut snap = get_binder_proxy_counts_by_uid();
snap.sort_by_key(|(uid, _)| *uid);
assert_eq!(snap, vec![(1000, 2), (2000, 1)]);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(2000, true);
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
}
#[test]
fn limit_callback_fires_once_until_low_watermark_resets() {
let _g = TEST_LOCK.lock().unwrap();
reset_for_test();
enable_count_by_uid(true);
set_binder_proxy_count_watermarks( 5, 2, 4);
let fired = Arc::new(StdMutex::new(Vec::<ProxyCountEvent>::new()));
let f = fired.clone();
set_binder_proxy_count_event_callback(Some(Arc::new(move |event| {
f.lock().unwrap().push(event);
})));
for _ in 0..7 {
on_proxy_create(1000);
}
let snap: Vec<_> = fired.lock().unwrap().clone();
assert_eq!(snap.len(), 2, "warning + limit, once each: {snap:?}");
assert!(matches!(
snap[0],
ProxyCountEvent::Warning { uid: 1000, .. }
));
assert!(matches!(snap[1], ProxyCountEvent::Limit { uid: 1000, .. }));
for _ in 0..6 {
on_proxy_drop(1000, true);
}
assert_eq!(get_binder_proxy_count_for_uid(1000), 1);
on_proxy_create(1000); on_proxy_create(1000); on_proxy_create(1000); on_proxy_create(1000); let snap: Vec<_> = fired.lock().unwrap().clone();
assert_eq!(snap.len(), 4, "re-fire after below-low reset: {snap:?}");
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
}
#[test]
fn callback_runs_outside_state_lock() {
let _g = TEST_LOCK.lock().unwrap();
reset_for_test();
enable_count_by_uid(true);
set_binder_proxy_count_watermarks(2, 1, 2);
set_binder_proxy_count_event_callback(Some(Arc::new(|_event| {
let _snapshot = get_binder_proxy_count_for_uid(1000);
})));
on_proxy_create(1000);
on_proxy_create(1000); on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
}
#[test]
fn clear_count_by_uid_resets_map_only() {
let _g = TEST_LOCK.lock().unwrap();
reset_for_test();
enable_count_by_uid(true);
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count(), 2);
clear_count_by_uid();
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
assert_eq!(get_binder_proxy_count(), 2);
on_proxy_drop(1000, true);
on_proxy_drop(2000, true);
}
#[test]
fn per_uid_count_survives_tracking_toggled_off_while_live() {
let _g = TEST_LOCK.lock().unwrap();
reset_for_test();
enable_count_by_uid(true);
let c1 = on_proxy_create(1000);
let c2 = on_proxy_create(1000);
assert!(c1 && c2);
assert_eq!(get_binder_proxy_count_for_uid(1000), 2);
enable_count_by_uid(false);
let c3 = on_proxy_create(1000);
assert!(!c3);
on_proxy_drop(1000, c3); on_proxy_drop(1000, c2);
on_proxy_drop(1000, c1);
assert_eq!(get_binder_proxy_count_for_uid(1000), 0);
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
}
}