make favorites persistent

This commit is contained in:
GyulyVGC
2026-03-16 19:11:58 +01:00
parent a82e04766b
commit 79eddbaa89
20 changed files with 190 additions and 198 deletions

View File

@@ -96,6 +96,7 @@ pub fn get_boot_task_chain(&self) -> Task<Message> {
#[cfg(test)]
mod tests {
use serial_test::serial;
use std::collections::HashSet;
use crate::gui::pages::types::running_page::RunningPage;
use crate::gui::pages::types::settings_page::SettingsPage;
@@ -103,11 +104,13 @@ mod tests {
use crate::gui::types::conf::Conf;
use crate::gui::types::config_window::ConfigWindow;
use crate::gui::types::export_pcap::ExportPcap;
use crate::gui::types::favorite::FavoriteKey;
use crate::gui::types::filters::Filters;
use crate::gui::types::settings::Settings;
use crate::networking::types::capture_context::CaptureSourcePicklist;
use crate::networking::types::config_device::ConfigDevice;
use crate::networking::types::data_representation::DataRepr;
use crate::networking::types::service::Service;
use crate::notifications::types::notifications::Notifications;
use crate::report::types::sort_type::SortType;
use crate::{Language, Sniffer, StyleType};
@@ -137,6 +140,7 @@ fn test_restore_default_configs() {
},
style: StyleType::DraculaDark,
ip_blacklist: "some-path".to_string(),
favorites: HashSet::from([FavoriteKey::Service(Service::Name("https"))]),
},
device: ConfigDevice {
device_name: "hey-hey".to_string(),

View File

@@ -1,7 +1,8 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fmt::Formatter;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum Country {
AD,
AE,

View File

@@ -255,7 +255,7 @@ fn blacklisted_notification_log<'a>(
let blacklisted_bar = item_bar(
icon,
host.to_blacklist_string(logged_notification.ip),
&data_info_host.data_info_fav.data_info,
&data_info_host.data_info,
data_repr,
first_entry_data_info,
);
@@ -403,14 +403,13 @@ fn data_notification_extra<'a>(
.first()
.unwrap_or(&(Host::default(), DataInfoHost::default()))
.1
.data_info_fav
.data_info;
for (host, data_info_host) in &logged_notification.hosts {
let icon = get_flag_tooltip(host.country, data_info_host, language, false);
let host_bar = item_bar(
icon,
host.to_entry_string(),
&data_info_host.data_info_fav.data_info,
&data_info_host.data_info,
logged_notification.data_repr,
first_data_info,
);

View File

@@ -112,7 +112,7 @@ fn col_favorite_item(
.unwrap_or_default();
for fi in &entries {
let star_button = fi.star_button();
let star_button = fi.star_button(&sniffer.conf.settings.favorites);
let icon = fi.icon(language, program_lookup, false);
let item_bar = item_bar(

View File

@@ -66,7 +66,7 @@
use iced::{Element, Point, Size, Subscription, Task, window};
use listeners::Process;
use rfd::FileHandle;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;
use std::sync::Arc;
@@ -97,8 +97,6 @@ pub struct Sniffer {
pub info_traffic: InfoTraffic,
/// Map of the resolved addresses with their full rDNS value and the corresponding host
pub addresses_resolved: HashMap<IpAddr, (String, Host)>,
/// Collection of the favorite hosts, services and programs
pub favorites: HashSet<FavoriteKey>,
/// Log of the displayed notifications, with the total number of notifications for this capture
pub logged_notifications: LoggedNotifications,
/// Reports if a newer release of the software is available on GitHub
@@ -165,7 +163,6 @@ pub fn new(conf: Conf) -> Self {
preview_captures_rx: None,
info_traffic: InfoTraffic::default(),
addresses_resolved: HashMap::new(),
favorites: HashSet::new(),
logged_notifications: LoggedNotifications::default(),
newer_release_available: None,
capture_source,
@@ -903,7 +900,7 @@ fn refresh_data(&mut self, mut msg: InfoTraffic, no_more_packets: bool) {
&mut self.logged_notifications,
&self.conf.settings.notifications,
&msg,
&self.favorites,
&self.conf.settings.favorites,
&self.capture_source,
&self.addresses_resolved,
);
@@ -1048,7 +1045,6 @@ fn reset(&mut self) -> Task<Message> {
self.current_capture_rx = (self.current_capture_rx.0 + 1, None);
self.info_traffic = InfoTraffic::default();
self.addresses_resolved = HashMap::new();
self.favorites = HashSet::new();
self.logged_notifications = LoggedNotifications::default();
self.pcap_error = None;
self.traffic_chart = TrafficChart::new(style, language, self.conf.data_repr);
@@ -1101,28 +1097,9 @@ fn update_waiting_dots(&mut self) {
fn add_or_remove_favorite(&mut self, fav: &FavoriteKey, add: bool) {
if add {
self.favorites.insert(fav.clone());
self.conf.settings.favorites.insert(fav.clone());
} else {
self.favorites.remove(fav);
}
match fav {
FavoriteKey::Host(host) => {
if let Some(host_info) = self.info_traffic.hosts.get_mut(host) {
host_info.data_info_fav.is_favorite = add;
}
}
FavoriteKey::Service(service) => {
if let Some(service_info) = self.info_traffic.services.get_mut(service) {
service_info.is_favorite = add;
}
}
FavoriteKey::Program(program) => {
let Some(program_lookup) = &mut self.program_lookup else {
return;
};
program_lookup.edit_fav(program, add);
}
self.conf.settings.favorites.remove(fav);
}
}
@@ -1639,43 +1616,55 @@ fn test_add_or_remove_favorite() {
// remove host
sniffer.update(Message::AddOrRemoveFavorite(fav_host.clone(), false));
assert_eq!(sniffer.favorites, HashSet::new());
assert_eq!(sniffer.conf.settings.favorites, HashSet::new());
// remove service
sniffer.update(Message::AddOrRemoveFavorite(fav_service.clone(), false));
assert_eq!(sniffer.favorites, HashSet::new());
assert_eq!(sniffer.conf.settings.favorites, HashSet::new());
// add service
sniffer.update(Message::AddOrRemoveFavorite(fav_service.clone(), true));
assert_eq!(sniffer.favorites, HashSet::from([fav_service.clone()]));
assert_eq!(
sniffer.conf.settings.favorites,
HashSet::from([fav_service.clone()])
);
// remove host
sniffer.update(Message::AddOrRemoveFavorite(fav_host.clone(), false));
assert_eq!(sniffer.favorites, HashSet::from([fav_service.clone()]));
assert_eq!(
sniffer.conf.settings.favorites,
HashSet::from([fav_service.clone()])
);
// add service
sniffer.update(Message::AddOrRemoveFavorite(fav_service.clone(), true));
assert_eq!(sniffer.favorites, HashSet::from([fav_service.clone()]));
assert_eq!(
sniffer.conf.settings.favorites,
HashSet::from([fav_service.clone()])
);
// add host
sniffer.update(Message::AddOrRemoveFavorite(fav_host.clone(), true));
assert_eq!(
sniffer.favorites,
sniffer.conf.settings.favorites,
HashSet::from([fav_host.clone(), fav_service.clone()])
);
// add program
sniffer.update(Message::AddOrRemoveFavorite(fav_program.clone(), true));
assert_eq!(
sniffer.favorites,
sniffer.conf.settings.favorites,
HashSet::from([fav_host.clone(), fav_service.clone(), fav_program.clone()])
);
// remove service
sniffer.update(Message::AddOrRemoveFavorite(fav_service.clone(), false));
assert_eq!(
sniffer.favorites,
sniffer.conf.settings.favorites,
HashSet::from([fav_host.clone(), fav_program.clone()])
);
// remove program
sniffer.update(Message::AddOrRemoveFavorite(fav_program.clone(), false));
assert_eq!(sniffer.favorites, HashSet::from([fav_host.clone()]));
assert_eq!(
sniffer.conf.settings.favorites,
HashSet::from([fav_host.clone()])
);
// remove host
sniffer.update(Message::AddOrRemoveFavorite(fav_host.clone(), false));
assert_eq!(sniffer.favorites, HashSet::new());
assert_eq!(sniffer.conf.settings.favorites, HashSet::new());
}
#[test]
@@ -2139,6 +2128,10 @@ fn test_conf() {
sniffer.update(Message::ChangeRunningPage(RunningPage::Notifications));
sniffer.update(Message::DataReprSelection(DataRepr::Bits));
sniffer.update(Message::LoadIpBlacklist("blacklist_file.csv".to_string()));
sniffer.update(Message::AddOrRemoveFavorite(
FavoriteKey::Service(Service::Name("https")),
true,
));
// force saving configs by quitting the app
sniffer.welcome = Some((false, 0));
@@ -2167,6 +2160,7 @@ fn test_conf() {
},
style: StyleType::DraculaDark,
ip_blacklist: "blacklist_file.csv".to_string(),
favorites: HashSet::from([FavoriteKey::Service(Service::Name("https"))]),
},
window: ConfigWindow::new((1000.0, 999.0), (-5.0, 277.5), (20.0, 20.0)),
device: ConfigDevice::default(),

View File

@@ -6,7 +6,6 @@
use crate::gui::types::conf::Conf;
use crate::gui::types::message::Message;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::networking::types::data_representation::DataRepr;
use crate::networking::types::host::Host;
@@ -23,7 +22,9 @@
use crate::utils::types::icon::Icon;
use iced::widget::{Button, Container, Space, button};
use iced::{Alignment, Element};
use serde::{Deserialize, Serialize};
use std::cmp::min;
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Favorite {
@@ -96,29 +97,25 @@ pub fn sort_arrows<'a>(self, conf: &Conf) -> Container<'a, Message, StyleType> {
#[derive(Clone)]
pub enum FavoriteItem {
Host((Host, DataInfoHost)),
Service((Service, DataInfoFav)),
Program((Program, DataInfoFav)),
Service((Service, DataInfo)),
Program((Program, DataInfo)),
}
impl FavoriteItem {
pub fn data_info(&self) -> DataInfo {
match self {
FavoriteItem::Host((_, data_info_host)) => data_info_host.data_info_fav.data_info,
FavoriteItem::Service((_, data_info_fav))
| FavoriteItem::Program((_, data_info_fav)) => data_info_fav.data_info,
FavoriteItem::Host((_, data_info_host)) => data_info_host.data_info,
FavoriteItem::Service((_, data_info)) | FavoriteItem::Program((_, data_info)) => {
*data_info
}
}
}
fn is_favorite(&self) -> bool {
match self {
FavoriteItem::Host((_, data_info_host)) => data_info_host.data_info_fav.is_favorite,
FavoriteItem::Service((_, data_info_fav))
| FavoriteItem::Program((_, data_info_fav)) => data_info_fav.is_favorite,
}
}
pub fn star_button<'a>(&self) -> Button<'a, Message, StyleType> {
let is_favorite = self.is_favorite();
pub fn star_button<'a>(
&self,
favorites: &HashSet<FavoriteKey>,
) -> Button<'a, Message, StyleType> {
let is_favorite = favorites.contains(&FavoriteKey::from(self.clone()));
let (icon, class) = if is_favorite {
(Icon::StarFull, ButtonType::Starred)
@@ -187,7 +184,7 @@ pub fn new_entry_search(&self) -> SearchParameters {
}
}
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
#[derive(Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum FavoriteKey {
Host(Host),
Service(Service),
@@ -213,11 +210,7 @@ fn get_host_entries(
) -> Vec<FavoriteItem> {
let mut sorted_vec: Vec<(&Host, &DataInfoHost)> = info_traffic.hosts.iter().collect();
sorted_vec.sort_by(|&(_, a), &(_, b)| {
a.data_info_fav
.data_info
.compare(&b.data_info_fav.data_info, sort_type, data_repr)
});
sorted_vec.sort_by(|&(_, a), &(_, b)| a.data_info.compare(&b.data_info, sort_type, data_repr));
let n_entry = min(sorted_vec.len(), 30);
sorted_vec[0..n_entry]
@@ -233,13 +226,13 @@ fn get_service_entries(
data_repr: DataRepr,
sort_type: SortType,
) -> Vec<FavoriteItem> {
let mut sorted_vec: Vec<(&Service, &DataInfoFav)> = info_traffic
let mut sorted_vec: Vec<(&Service, &DataInfo)> = info_traffic
.services
.iter()
.filter(|(service, _)| service != &&Service::NotApplicable)
.collect();
sorted_vec.sort_by(|&(_, a), &(_, b)| a.data_info.compare(&b.data_info, sort_type, data_repr));
sorted_vec.sort_by(|&(_, a), &(_, b)| a.compare(b, sort_type, data_repr));
let n_entry = min(sorted_vec.len(), 30);
sorted_vec[0..n_entry]
@@ -257,22 +250,22 @@ fn get_program_entries(
return Vec::new();
};
let mut sorted_vec: Vec<(&Program, &DataInfoFav)> = program_lookup
let mut sorted_vec: Vec<(&Program, &DataInfo)> = program_lookup
.programs()
.iter()
// Unknown may be inserted, and then all of its data could be reassigned to known programs
.filter(|(_, d)| d.data_info.tot_data(DataRepr::Packets) > 0)
.filter(|(_, d)| d.tot_data(DataRepr::Packets) > 0)
.collect();
sorted_vec.sort_by(|&(p1, a), &(p2, b)| {
if sort_type == SortType::Neutral && a.data_info.is_within_same_second(&b.data_info) {
if sort_type == SortType::Neutral && a.is_within_same_second(b) {
if p1.is_unknown() {
return std::cmp::Ordering::Greater;
} else if p2.is_unknown() {
return std::cmp::Ordering::Less;
}
}
a.data_info.compare(&b.data_info, sort_type, data_repr)
a.compare(b, sort_type, data_repr)
});
let n_entry = min(sorted_vec.len(), 30);

View File

@@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::conf::deserialize_or_default;
use crate::gui::types::favorite::FavoriteKey;
use crate::notifications::types::notifications::Notifications;
use crate::{Language, StyleType};
@@ -27,6 +29,8 @@ pub struct Settings {
pub notifications: Notifications,
#[serde(deserialize_with = "deserialize_or_default")]
pub style: StyleType,
#[serde(deserialize_with = "deserialize_or_default")]
pub favorites: HashSet<FavoriteKey>,
}
impl Default for Settings {
@@ -41,6 +45,7 @@ fn default() -> Self {
style_path: String::new(),
notifications: Notifications::default(),
style: StyleType::default(),
favorites: HashSet::new(),
}
}
}

View File

@@ -14,7 +14,6 @@
use crate::networking::types::bogon::is_bogon;
use crate::networking::types::capture_context::{CaptureContext, CaptureSource, CaptureType};
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::networking::types::host::{Host, HostMessage};
use crate::networking::types::icmp_type::IcmpType;
@@ -244,7 +243,6 @@ pub fn parse_packets(
.entry(host)
.and_modify(|data_info_host| {
data_info_host
.data_info_fav
.data_info
.add_packet(exchanged_bytes, traffic_direction);
})
@@ -262,11 +260,10 @@ pub fn parse_packets(
);
let is_bogon = is_bogon(&address_to_lookup);
DataInfoHost {
data_info_fav: DataInfo::new_with_first_packet(
data_info: DataInfo::new_with_first_packet(
exchanged_bytes,
traffic_direction,
)
.into(),
),
is_loopback,
is_local,
is_bogon,
@@ -280,14 +277,11 @@ pub fn parse_packets(
info_traffic_msg
.services
.entry(service)
.and_modify(|data_info_fav| {
data_info_fav
.data_info
.add_packet(exchanged_bytes, traffic_direction);
.and_modify(|data_info| {
data_info.add_packet(exchanged_bytes, traffic_direction);
})
.or_insert_with(|| {
DataInfo::new_with_first_packet(exchanged_bytes, traffic_direction)
.into()
});
// update dropped packets number
@@ -404,7 +398,7 @@ fn reverse_dns_lookups(
};
let data_info_host = DataInfoHost {
data_info_fav: DataInfoFav::default(),
data_info: DataInfo::default(),
is_local,
is_bogon,
is_loopback,
@@ -459,7 +453,7 @@ fn new_hosts_to_send(&mut self) -> Vec<HostMessage> {
.remove(&address_to_lookup)
.unwrap_or_default();
// overwrite the host message with the collected data
host_msg.data_info_host.data_info_fav.data_info = other_data;
host_msg.data_info_host.data_info = other_data;
// insert the newly resolved host in the collection of resolved addresses
self.addresses_resolved
.insert(address_to_lookup, host_msg.host.clone());

View File

@@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize};
/// Struct to represent an Autonomous System
#[derive(Default, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(Default, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct Asn {
/// Autonomous System number
pub code: String,

View File

@@ -1,17 +0,0 @@
use crate::networking::types::data_info::DataInfo;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
pub struct DataInfoFav {
pub data_info: DataInfo,
/// Determine if this item is one of the favorites
pub is_favorite: bool,
}
impl From<DataInfo> for DataInfoFav {
fn from(data_info: DataInfo) -> Self {
Self {
data_info,
is_favorite: false,
}
}
}

View File

@@ -1,13 +1,13 @@
//! Module defining the `DataInfoHost` struct related to hosts.
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::traffic_type::TrafficType;
/// Host-related information.
#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Hash)]
pub struct DataInfoHost {
/// Incoming and outgoing packets and bytes
pub data_info_fav: DataInfoFav,
pub data_info: DataInfo,
/// Determine if the connection is loopback (the "remote" is loopback)
pub is_loopback: bool,
/// Determine if the connection with this host is local
@@ -20,9 +20,7 @@ pub struct DataInfoHost {
impl DataInfoHost {
pub fn refresh(&mut self, other: &Self) {
self.data_info_fav
.data_info
.refresh(other.data_info_fav.data_info);
self.data_info.refresh(other.data_info);
self.is_loopback = other.is_loopback;
self.is_local = other.is_local;
self.is_bogon = other.is_bogon;

View File

@@ -2,10 +2,11 @@
use crate::networking::types::asn::Asn;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::utils::formatted_strings::clip_text;
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
/// Struct to represent a network host
#[derive(Default, PartialEq, Eq, Hash, Clone, Debug)]
#[derive(Default, PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)]
pub struct Host {
/// Hostname (domain). Obtained from the reverse DNS.
pub domain: String,

View File

@@ -2,7 +2,6 @@
use crate::networking::manage_packets::get_local_port;
use crate::networking::types::address_port_pair::AddressPortPair;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::networking::types::data_representation::DataRepr;
use crate::networking::types::host::Host;
@@ -24,7 +23,7 @@ pub struct InfoTraffic {
/// Map of the traffic
pub map: HashMap<AddressPortPair, InfoAddressPortPair>,
/// Map of the upper layer services with their data info
pub services: HashMap<Service, DataInfoFav>,
pub services: HashMap<Service, DataInfo>,
/// Map of the hosts with their data info
pub hosts: HashMap<Host, DataInfoHost>,
}
@@ -78,7 +77,7 @@ pub fn refresh(&mut self, msg: &mut Self, program_lookup_opt: &mut Option<Progra
for (key, value) in &msg.services {
self.services
.entry(*key)
.and_modify(|x| x.data_info.refresh(value.data_info))
.and_modify(|x| x.refresh(*value))
.or_insert(*value);
}

View File

@@ -6,7 +6,6 @@
pub mod combobox_data_states;
pub mod config_device;
pub mod data_info;
pub mod data_info_fav;
pub mod data_info_host;
pub mod data_representation;
pub mod host;

View File

@@ -1,7 +1,8 @@
use listeners::Process;
use serde::{Deserialize, Serialize};
/// Program / App.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum Program {
/// A known program.
NamePath((String, String)),

View File

@@ -6,7 +6,6 @@
use crate::networking::manage_packets::get_local_port;
use crate::networking::types::address_port_pair::AddressPortPair;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::data_representation::DataRepr;
use crate::networking::types::info_address_port_pair::InfoAddressPortPair;
use crate::networking::types::program::Program;
@@ -28,7 +27,7 @@ pub struct ProgramLookup {
icon_key_tx: Sender<String>,
picon_rx: Receiver<(String, IconHandle)>,
state: HashMap<(u16, Protocol), LookedUpProgram>,
programs: HashMap<Program, DataInfoFav>,
programs: HashMap<Program, DataInfo>,
picons: HashMap<String, IconHandle>,
}
@@ -61,8 +60,8 @@ pub fn lookup_and_add_data(
let res = Program::from_proc(proc.as_ref());
self.programs
.entry(res.clone())
.and_modify(|d| d.data_info.refresh(new_data))
.or_insert(new_data.into());
.and_modify(|d| d.refresh(new_data))
.or_insert(new_data);
res
}
@@ -161,8 +160,8 @@ pub fn update(
// assign to known
self.programs
.entry(program)
.and_modify(|d| d.data_info.refresh(reassigned_data))
.or_insert(reassigned_data.into());
.and_modify(|d| d.refresh(reassigned_data))
.or_insert(reassigned_data);
// remove from Unknown
// NOTE: subtracting reassigned_data from Unknown wouldn't correctly reassign final_instant,
// so let's just reiterate through all the Unknown connections
@@ -176,7 +175,7 @@ pub fn update(
// or_insert not needed: Unknown is already in the map since reassigned data came from it
self.programs
.entry(Program::Unknown)
.and_modify(|d| d.data_info = unknown_data);
.and_modify(|d| *d = unknown_data);
}
}
@@ -193,16 +192,10 @@ pub fn handle_pending_icons(&mut self) {
}
}
pub fn programs(&self) -> &HashMap<Program, DataInfoFav> {
pub fn programs(&self) -> &HashMap<Program, DataInfo> {
&self.programs
}
pub fn edit_fav(&mut self, program: &Program, add: bool) {
if let Some(info) = self.programs.get_mut(program) {
info.is_favorite = add;
}
}
pub fn picon_tooltip<'a>(
&self,
icon_key: &str,

View File

@@ -1,5 +1,8 @@
use serde::de::{self, Deserializer, VariantAccess};
use serde::{Deserialize, Serialize};
/// Upper layer services.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
pub enum Service {
/// One of the known services.
Name(&'static str),
@@ -10,6 +13,55 @@ pub enum Service {
NotApplicable,
}
impl<'de> Deserialize<'de> for Service {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ServiceVisitor;
impl<'de> de::Visitor<'de> for ServiceVisitor {
type Value = Service;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a Service enum")
}
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: de::EnumAccess<'de>,
{
let (variant, access) = data.variant::<String>()?;
match variant.as_str() {
"Name" => {
let s: String = access.newtype_variant()?;
let leaked: &'static str = Box::leak(s.into_boxed_str());
Ok(Service::Name(leaked))
}
"Unknown" => {
access.unit_variant()?;
Ok(Service::Unknown)
}
"NotApplicable" => {
access.unit_variant()?;
Ok(Service::NotApplicable)
}
other => Err(de::Error::unknown_variant(
other,
&["Name", "Unknown", "NotApplicable"],
)),
}
}
}
deserializer.deserialize_enum(
"Service",
&["Name", "Unknown", "NotApplicable"],
ServiceVisitor,
)
}
}
impl Service {
pub fn to_string_with_equal_prefix(self) -> String {
format!("={self}")
@@ -53,4 +105,31 @@ fn test_service_to_string_with_equal_prefix() {
assert_eq!(Service::NotApplicable.to_string_with_equal_prefix(), "=-");
assert_eq!(Service::Unknown.to_string_with_equal_prefix(), "=?");
}
#[test]
fn test_deserialize_name() {
let json = serde_json::to_string(&Service::Name("https")).unwrap();
let deserialized: Service = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, Service::Name("https"));
}
#[test]
fn test_deserialize_unknown() {
let json = serde_json::to_string(&Service::Unknown).unwrap();
let deserialized: Service = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, Service::Unknown);
}
#[test]
fn test_deserialize_not_applicable() {
let json = serde_json::to_string(&Service::NotApplicable).unwrap();
let deserialized: Service = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, Service::NotApplicable);
}
#[test]
fn test_deserialize_invalid_variant() {
let json = r#""InvalidVariant""#;
assert!(serde_json::from_str::<Service>(json).is_err());
}
}

View File

@@ -2,7 +2,6 @@
use crate::networking::manage_packets::get_address_to_lookup;
use crate::networking::types::capture_context::CaptureSource;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::networking::types::data_representation::DataRepr;
use crate::networking::types::host::Host;
@@ -109,14 +108,13 @@ pub fn notify_and_log(
.get(&host)
.copied()
.unwrap_or_default();
data_info_host.data_info_fav.data_info = v.data_info();
data_info_host.data_info = v.data_info();
blacklisted_last_interval
.entry(*address_to_lookup)
.and_modify(|(_, existing_data_info_host)| {
existing_data_info_host
.data_info_fav
.data_info
.refresh(data_info_host.data_info_fav.data_info);
.refresh(data_info_host.data_info);
})
.or_insert((host, data_info_host));
}
@@ -163,11 +161,8 @@ fn threshold_hosts(
.map(|(h, data)| (h.clone(), *data))
.collect();
hosts.sort_by(|(_, a), (_, b)| {
a.data_info_fav.data_info.compare(
&b.data_info_fav.data_info,
SortType::Descending,
data_repr,
)
a.data_info
.compare(&b.data_info, SortType::Descending, data_repr)
});
hosts.truncate(4);
hosts
@@ -181,7 +176,7 @@ fn threshold_services(
.services
.iter()
.filter(|(service, _)| service != &&Service::NotApplicable)
.map(|(s, data_info_fav)| (*s, data_info_fav.data_info))
.map(|(s, data_info)| (*s, *data_info))
.collect();
services.sort_by(|(_, a), (_, b)| a.compare(b, SortType::Descending, data_repr));
services.truncate(4);
@@ -211,13 +206,7 @@ fn favorites_last_interval(
.filter(|v| v.program.eq(p))
.for_each(|v| data_info.refresh(v.data_info()));
if data_info.tot_data(DataRepr::Packets) > 0 {
Some(FavoriteItem::Program((
p.clone(),
DataInfoFav {
data_info,
is_favorite: true,
},
)))
Some(FavoriteItem::Program((p.clone(), data_info)))
} else {
None
}

View File

@@ -82,9 +82,7 @@ pub fn data_info(&self) -> DataInfo {
match self {
LoggedNotification::DataThresholdExceeded(d) => d.data_info,
LoggedNotification::FavoriteTransmitted(f) => f.favorite.data_info(),
LoggedNotification::BlacklistedTransmitted(b) => {
b.data_info_host.data_info_fav.data_info
}
LoggedNotification::BlacklistedTransmitted(b) => b.data_info_host.data_info,
}
}
@@ -180,7 +178,7 @@ fn to_json(&self) -> String {
"domain": self.host.domain,
"asn": self.host.asn.name,
},
"data": DataRepr::Bytes.formatted_string(self.data_info_host.data_info_fav.data_info.tot_data(DataRepr::Bytes)),
"data": DataRepr::Bytes.formatted_string(self.data_info_host.data_info.tot_data(DataRepr::Bytes)),
})
.to_string()
}
@@ -191,7 +189,6 @@ mod tests {
use super::*;
use crate::countries::types::country::Country;
use crate::networking::types::asn::Asn;
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::program::Program;
use crate::networking::types::traffic_direction::TrafficDirection;
use crate::networking::types::traffic_type::TrafficType;
@@ -228,14 +225,7 @@ fn test_favorite_host_transmitted_to_json() {
},
};
let data_info_host = DataInfoHost {
data_info_fav: DataInfoFav {
data_info: {
let mut di = DataInfo::default();
di.add_packets(5, 500, TrafficDirection::Outgoing, Instant::now());
di
},
is_favorite: true,
},
data_info: DataInfo::new_for_tests(0, 5, 0, 500),
is_loopback: false,
is_local: false,
is_bogon: None,
@@ -258,13 +248,7 @@ fn test_favorite_service_transmitted_to_json() {
let service = Service::Name("https");
let mut data_info = DataInfo::default();
data_info.add_packets(12, 1500, TrafficDirection::Incoming, Instant::now());
let favorite_item = FavoriteItem::Service((
service,
DataInfoFav {
data_info,
is_favorite: true,
},
));
let favorite_item = FavoriteItem::Service((service, data_info));
let notification = FavoriteTransmitted {
id: 3,
favorite: favorite_item,
@@ -284,13 +268,7 @@ fn test_favorite_program_transmitted_to_json() {
));
let mut data_info = DataInfo::default();
data_info.add_packets(20, 2_500_000, TrafficDirection::Outgoing, Instant::now());
let favorite_item = FavoriteItem::Program((
program,
DataInfoFav {
data_info,
is_favorite: true,
},
));
let favorite_item = FavoriteItem::Program((program, data_info));
let notification = FavoriteTransmitted {
id: 4,
favorite: favorite_item,
@@ -313,14 +291,7 @@ fn test_blacklisted_transmitted_to_json() {
},
};
let data_info_host = DataInfoHost {
data_info_fav: DataInfoFav {
data_info: {
let mut di = DataInfo::default();
di.add_packets(50, 10_000, TrafficDirection::Incoming, Instant::now());
di
},
is_favorite: false,
},
data_info: DataInfo::new_for_tests(50, 0, 10_000, 0),
is_loopback: false,
is_local: false,
is_bogon: None,

View File

@@ -1,12 +1,10 @@
use std::cmp::min;
use crate::Sniffer;
use crate::gui::types::favorite::FavoriteKey;
use crate::networking::manage_packets::get_address_to_lookup;
use crate::networking::types::address_port_pair::AddressPortPair;
use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_fav::DataInfoFav;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::networking::types::info_address_port_pair::InfoAddressPortPair;
use std::cmp::min;
/// Return the elements that satisfy the search constraints and belong to the given page,
/// and the total number of elements which satisfy the search constraints,
@@ -20,6 +18,7 @@ pub fn get_searched_entries(
) {
let mut agglomerate = DataInfo::default();
let info_traffic = &sniffer.info_traffic;
let favorites = &sniffer.conf.settings.favorites;
let mut all_results: Vec<(&AddressPortPair, &InfoAddressPortPair)> = info_traffic
.map
.iter()
@@ -28,27 +27,15 @@ pub fn get_searched_entries(
let r_dns_host = sniffer.addresses_resolved.get(address_to_lookup);
// is this a favorite host?
let is_favorite_host = if let Some(e) = r_dns_host {
info_traffic
.hosts
.get(&e.1)
.unwrap_or(&DataInfoHost::default())
.data_info_fav
.is_favorite
favorites.contains(&FavoriteKey::Host(e.1.clone()))
} else {
false
};
// is this a favorite service?
let is_favorite_service = info_traffic
.services
.get(&value.service)
.unwrap_or(&DataInfoFav::default())
.is_favorite;
let is_favorite_service = favorites.contains(&FavoriteKey::Service(value.service));
// is this a favorite program?
let is_favorite_program = if let Some(pl) = sniffer.program_lookup.as_ref() {
pl.programs()
.get(&value.program)
.unwrap_or(&DataInfoFav::default())
.is_favorite
let is_favorite_program = if sniffer.program_lookup.is_some() {
favorites.contains(&FavoriteKey::Program(value.program.clone()))
} else {
false
};