1use crate::config::PrtConfig;
7use crate::core::bandwidth::BandwidthTracker;
8use crate::core::{container, scanner, suspicious};
9use crate::i18n;
10use crate::known_ports;
11use crate::model::{EntryStatus, SortState, TrackedEntry, GONE_RETENTION};
12use std::time::Instant;
13
14pub struct Session {
23 pub entries: Vec<TrackedEntry>,
24 pub sort: SortState,
25 pub is_elevated: bool,
26 pub is_root: bool,
27 pub config: PrtConfig,
28 pub bandwidth: BandwidthTracker,
29}
30
31impl Default for Session {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl Session {
38 pub fn new() -> Self {
39 Self {
40 entries: Vec::new(),
41 sort: SortState::default(),
42 is_elevated: false,
43 is_root: scanner::is_root(),
44 config: crate::config::load_config(),
45 bandwidth: BandwidthTracker::new(),
46 }
47 }
48
49 pub fn refresh(&mut self) -> Result<(), String> {
51 let was_elevated = self.is_elevated;
52 if was_elevated {
53 self.sync_elevation_state(scanner::has_elevated_access());
54 }
55
56 let scan_result = if self.is_elevated {
57 scanner::scan_elevated()
58 } else {
59 scanner::scan()
60 };
61
62 match scan_result {
63 Ok(new_entries) => {
64 if was_elevated {
65 self.sync_elevation_state(scanner::has_elevated_access());
66 }
67 let now = Instant::now();
68 self.entries = scanner::diff_entries(&self.entries, new_entries, now);
69
70 self.enrich_service_names();
72 self.enrich_suspicious();
73 self.enrich_containers();
74
75 self.entries.retain(|e| {
76 e.status != EntryStatus::Gone || now.duration_since(e.seen_at) < GONE_RETENTION
77 });
78
79 self.bandwidth.sample();
81
82 scanner::sort_entries(&mut self.entries, &self.sort);
83 Ok(())
84 }
85 Err(e) => {
86 let s = i18n::strings();
87 Err(s.fmt_scan_error(&e.to_string()))
88 }
89 }
90 }
91
92 fn enrich_service_names(&mut self) {
95 for entry in &mut self.entries {
96 if entry.status != EntryStatus::Gone {
97 entry.service_name =
98 known_ports::lookup(entry.entry.local_port(), &self.config.known_ports);
99 }
100 }
101 }
102
103 fn enrich_suspicious(&mut self) {
106 for entry in &mut self.entries {
107 if entry.status != EntryStatus::Gone {
108 entry.suspicious = suspicious::check(&entry.entry);
109 }
110 }
111 }
112
113 fn enrich_containers(&mut self) {
116 let pids: Vec<u32> = self
117 .entries
118 .iter()
119 .filter(|e| e.status != EntryStatus::Gone)
120 .map(|e| e.entry.process.pid)
121 .collect();
122
123 let names = container::resolve_container_names(&pids);
124
125 for entry in &mut self.entries {
126 if entry.status != EntryStatus::Gone {
127 entry.container_name = names.get(&entry.entry.process.pid).cloned();
128 }
129 }
130 }
131
132 pub fn filtered_indices(&self, query: &str) -> Vec<usize> {
133 scanner::filter_indices(&self.entries, query)
134 }
135
136 fn sync_elevation_state(&mut self, has_elevated_access: bool) {
137 if self.is_elevated && !has_elevated_access {
138 self.is_elevated = false;
139 self.is_root = scanner::is_root();
140 }
141 }
142
143 pub fn try_sudo(&mut self, password: &str) -> String {
145 let s = i18n::strings();
146 match scanner::scan_with_sudo(password) {
147 Ok(new_entries) => {
148 self.is_elevated = true;
149 self.is_root = true;
150 let now = Instant::now();
151 self.entries = scanner::diff_entries(&self.entries, new_entries, now);
152
153 self.entries.retain(|e| {
155 e.status != EntryStatus::Gone || e.seen_at.elapsed() < GONE_RETENTION
156 });
157
158 self.enrich_service_names();
160 self.enrich_suspicious();
161 self.enrich_containers();
162
163 self.bandwidth.sample();
165
166 scanner::sort_entries(&mut self.entries, &self.sort);
167 s.sudo_elevated.to_string()
168 }
169 Err(e) => {
170 self.is_elevated = false;
171 let msg = e.to_string();
172 if msg.contains("incorrect password") || msg.contains("Sorry") {
173 s.sudo_wrong_password.to_string()
174 } else {
175 s.fmt_sudo_error(&msg)
176 }
177 }
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn sync_elevation_state_clears_expired_sudo_cache() {
188 let mut session = Session::new();
189 session.is_elevated = true;
190 session.is_root = true;
191
192 session.sync_elevation_state(false);
193
194 assert!(!session.is_elevated);
195 assert_eq!(session.is_root, scanner::is_root());
196 }
197}