agg.rs
1 use std::collections::{HashMap, HashSet, VecDeque}; 2 use crate::decode::FrameSummary; 3 use crate::util::MacAddr; 4 5 #[derive(Debug, Clone)] 6 pub struct ApState { 7 pub bssid: MacAddr, 8 pub ssid: Option<String>, 9 pub channel: Option<u16>, 10 pub channel_mhz: Option<u16>, 11 pub last_rssi_dbm: Option<i8>, 12 pub last_seen_ns: i128, 13 pub total_frames: u64, 14 pub beacon_frames: u64, 15 pub probe_req_frames: u64, 16 pub probe_resp_frames: u64, 17 } 18 19 impl ApState { 20 pub fn new(bssid: MacAddr, ts_ns: i128) -> Self { 21 Self { 22 bssid, 23 ssid: None, 24 channel: None, 25 channel_mhz: None, 26 last_rssi_dbm: None, 27 last_seen_ns: ts_ns, 28 total_frames: 0, 29 beacon_frames: 0, 30 probe_req_frames: 0, 31 probe_resp_frames: 0, 32 } 33 } 34 35 pub fn update(&mut self, f: &FrameSummary) { 36 self.last_seen_ns = f.ts_ns; 37 self.total_frames += 1; 38 if let Some(rssi) = f.rssi_dbm { self.last_rssi_dbm = Some(rssi); } 39 if let Some(ch) = f.channel { self.channel = Some(ch); } 40 if let Some(mhz) = f.channel_mhz { self.channel_mhz = Some(mhz); } 41 if let Some(ssid) = &f.ssid { 42 // Keep last non-empty SSID; empty string is "hidden". 43 if !ssid.is_empty() { 44 self.ssid = Some(ssid.clone()); 45 } 46 } 47 48 if f.dot11_type == Some(0) { 49 match f.dot11_subtype { 50 Some(8) => self.beacon_frames += 1, 51 Some(4) => self.probe_req_frames += 1, 52 Some(5) => self.probe_resp_frames += 1, 53 _ => {} 54 } 55 } 56 } 57 } 58 59 #[derive(Default)] 60 pub struct Aggregator { 61 aps: HashMap<MacAddr, ApState>, 62 pub timeline: VecDeque<FrameSummary>, 63 pub max_timeline: usize, 64 max_aps: usize, 65 } 66 67 impl Aggregator { 68 pub fn new(max_timeline: usize, max_aps: usize) -> Self { 69 Self { 70 aps: HashMap::new(), 71 timeline: VecDeque::new(), 72 max_timeline, 73 max_aps, 74 } 75 } 76 77 pub fn ingest(&mut self, f: FrameSummary) { 78 if let Some(bssid) = f.bssid { 79 let entry = self.aps.entry(bssid).or_insert_with(|| ApState::new(bssid, f.ts_ns)); 80 entry.update(&f); 81 if self.max_aps > 0 && self.aps.len() > self.max_aps { 82 self.prune_aps(); 83 } 84 } 85 if self.max_timeline == 0 { 86 return; 87 } 88 if self.timeline.len() == self.max_timeline { 89 self.timeline.pop_front(); 90 } 91 self.timeline.push_back(f); 92 } 93 94 pub fn ap_states_sorted(&self) -> Vec<&ApState> { 95 let mut v: Vec<&ApState> = self.aps.values().collect(); 96 v.sort_by_key(|a| std::cmp::Reverse(a.last_seen_ns)); 97 v 98 } 99 100 fn prune_aps(&mut self) { 101 let mut entries: Vec<(MacAddr, i128)> = self 102 .aps 103 .iter() 104 .map(|(bssid, ap)| (*bssid, ap.last_seen_ns)) 105 .collect(); 106 entries.sort_by_key(|entry| std::cmp::Reverse(entry.1)); 107 let keep: HashSet<MacAddr> = entries 108 .into_iter() 109 .take(self.max_aps) 110 .map(|entry| entry.0) 111 .collect(); 112 self.aps.retain(|bssid, _| keep.contains(bssid)); 113 } 114 }