//! Numeric278733/278834 counters; aggregation/reporting remains caller work. use std::collections::BTreeMap; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Side { Victim, Actor, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Plan { pub increments: Vec<(Side, u32)>, pub aggregate_pair: bool, pub report: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StatsError { MissingCounter, } pub fn slot(method: i32) -> Option { Some(match method { 14 => 9, 15 => 8, 16 => 11, 17 => 5, 18 => 4, 19 => 2, 20 => 0, 21 => 7, 28 => 6, 30 => 1, 36 => 10, 38 => 3, 39 => 12, 40 => 13, 41 => 14, 42 => 15, 43 => 16, 44 => 17, 45 => 18, _ => return None, }) } /// All participants are existing clients with valid source IDs. Self aliases /// resolve to the actor's storage; an absent actor exits before any requests. pub fn plan(method: i32, self_hit: bool, actor_absent: bool) -> Plan { use Side::{Actor, Victim}; let mut result = Plan { increments: vec![], aggregate_pair: false, report: !actor_absent, }; if actor_absent { return result; } let victim = if self_hit { Actor } else { Victim }; if let Some(slot) = slot(method) { result.increments = vec![ (Actor, 916 + 76 * u32::from(slot)), (victim, 920 + 76 * u32::from(slot)), ]; result.aggregate_pair = true; return result; } let single = match method { 0..=5 | 8 | 9 => Some(2512), 6 => Some(2488), 7 => Some(2492), 31 => Some(2504), 32 | 33 | 35 => Some(2508), 47 => Some(2496), 22 if self_hit => Some(2460), 25 if self_hit => Some(2452), _ => None, }; if let Some(offset) = single { result.increments.push((victim, offset)); return result; } let pair = match method { 12 => Some((2396, 2468)), 13 => Some((2400, 2472)), 22 => Some((2384, 2456)), 23 => Some((2408, 2480)), 24 => Some((2404, 2476)), 25 => Some((2376, 2448)), 34 => Some((2428, 2500)), 37 => Some((2392, 2464)), 48 => Some((2412, 2484)), _ => None, }; if let Some((a, v)) = pair { result.increments = vec![(Actor, a), (victim, v)]; result.aggregate_pair = method != 34; } result } /// Validate storage before mutation, then apply source wrapping increments. /// The returned plan describes reporting intent without executing callbacks. pub fn apply( method: i32, self_hit: bool, actor_absent: bool, actor: &mut BTreeMap, victim: &mut BTreeMap, ) -> Result { let p = plan(method, self_hit, actor_absent); for (side, offset) in &p.increments { if !(if *side == Side::Actor { &*actor } else { &*victim }) .contains_key(offset) { return Err(StatsError::MissingCounter); } } for (side, offset) in &p.increments { let counters = if *side == Side::Actor { &mut *actor } else { &mut *victim }; let v = counters.get_mut(offset).unwrap(); *v = v.wrapping_add(1); } Ok(p) } #[cfg(test)] mod tests { use super::*; #[test] fn all_slots_and_invalid_signed_extremes() { let slots: std::collections::BTreeSet<_> = (0..50).filter_map(slot).collect(); assert_eq!(slots, (0..19).collect()); assert_eq!(slot(i32::MIN), None); assert_eq!(slot(i32::MAX), None); } #[test] fn self_alias_and_fallback_exceptions() { assert_eq!(plan(22, true, false).increments, vec![(Side::Actor, 2460)]); assert_eq!(plan(25, true, false).increments, vec![(Side::Actor, 2452)]); assert!(!plan(34, false, false).aggregate_pair); assert!(plan(20, false, false).aggregate_pair); } #[test] fn apply_wraps_and_keeps_other_counters() { let mut actor = BTreeMap::from([(916, u32::MAX), (999, 17)]); let mut victim = BTreeMap::from([(920, 3)]); apply(20, false, false, &mut actor, &mut victim).unwrap(); assert_eq!(actor, BTreeMap::from([(916, 0), (999, 17)])); assert_eq!(victim[&920], 4); } #[test] fn absent_actor_and_missing_storage_do_not_partially_mutate() { let mut a = BTreeMap::from([(916, 4)]); let mut v = BTreeMap::new(); assert_eq!( apply(20, false, false, &mut a, &mut v), Err(StatsError::MissingCounter) ); assert_eq!(a[&916], 4); let p = apply(20, false, true, &mut a, &mut v).unwrap(); assert!(!p.report); assert!(p.increments.is_empty()); assert_eq!(a[&916], 4); } }