From d5721b0077ce328ff2b1a571558b257f56e2dfe0 Mon Sep 17 00:00:00 2001 From: 18601673727 <18601673727@163.com> Date: Sun, 29 Jun 2025 20:00:38 +0800 Subject: [PATCH 01/74] Simplified Chinese translations_4 --- src/translations/translations_4.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 41f96f85..6cd717dd 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -11,6 +11,7 @@ pub fn reserved_address_translation(language: Language, info: &str) -> String { Language::IT => format!("Indirizzo riservato ({info})"), Language::PT => format!("Endereço reservado ({info})"), Language::UK => format!("Зарезервована адреса ({info})"), + Language::ZH => format!("预留地址 ({info})"), Language::ZH_TW => format!("保留的網路位址 ({info})"), _ => format!("Reserved address ({info})"), } @@ -20,6 +21,7 @@ pub fn share_feedback_translation(language: Language) -> &'static str { match language { Language::EN => "Share your feedback", Language::IT => "Condividi il tuo feedback", + Language::ZH_TW => "分享您的反馈", Language::ZH_TW => "分享您的意見回饋", _ => "Share your feedback", } @@ -30,6 +32,7 @@ pub fn excluded_translation(language: Language) -> &'static str { match language { Language::EN => "Excluded", Language::IT => "Esclusi", + Language::ZH => "已被过滤", Language::ZH_TW => "已排除", _ => "Excluded", } @@ -39,6 +42,7 @@ pub fn import_capture_translation(language: Language) -> &'static str { match language { Language::EN => "Import capture file", Language::IT => "Importa file di cattura", + Language::ZH => "导入捕获文件", _ => "Import capture file", } } @@ -47,6 +51,7 @@ pub fn select_capture_translation(language: Language) -> &'static str { match language { Language::EN => "Select capture file", Language::IT => "Seleziona file di cattura", + Language::ZH => "选择捕获文件", _ => "Select capture file", } } @@ -64,6 +69,11 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text {file_name_translation}: {file}\n\n\ Sei sicuro che il file che hai selezionato non sia vuoto?" ), + Language::ZH => format!( + "从文件中读取封包...\n\n\ + {file_name_translation}: {file}\n\n\ + 您确定选中的文件不是空的吗?" + ), _ => format!( "Reading packets from file...\n\n\ {file_name_translation}: {file}\n\n\ @@ -76,6 +86,7 @@ pub fn data_exceeded_translation(language: Language) -> &'static str { match language { Language::EN => "Data threshold exceeded", Language::IT => "Soglia di dati superata", + Language::ZH => "已超出数据阈值", _ => "Data threshold exceeded", } } @@ -85,6 +96,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { match language { Language::EN => "Bits threshold exceeded", Language::IT => "Soglia di bit superata", + Language::ZH => "已超出比特阈值", _ => "Bits threshold exceeded", } } @@ -93,6 +105,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { pub fn bits_translation(language: Language) -> &'static str { match language { Language::EN | Language::IT => "Bits", + Language::ZH => "比特", _ => "Bits", } } @@ -102,6 +115,7 @@ pub fn pause_translation(language: Language) -> &'static str { match language { Language::EN => "Pause", Language::IT => "Pausa", + Language::ZH => "暂停", _ => "Pause", } } From f1d5e684c395b0aacd887cf6c05785a3db528044 Mon Sep 17 00:00:00 2001 From: 18601673727 <18601673727@163.com> Date: Sun, 29 Jun 2025 20:03:31 +0800 Subject: [PATCH 02/74] Update translations_4.rs --- src/translations/translations_4.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 6cd717dd..363fb88d 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -21,7 +21,7 @@ pub fn share_feedback_translation(language: Language) -> &'static str { match language { Language::EN => "Share your feedback", Language::IT => "Condividi il tuo feedback", - Language::ZH_TW => "分享您的反馈", + Language::ZH => "分享您的反馈", Language::ZH_TW => "分享您的意見回饋", _ => "Share your feedback", } From 76485570656d9525f2ca50387ade02f2c48ad840 Mon Sep 17 00:00:00 2001 From: shu-kitamura Date: Thu, 3 Jul 2025 21:50:14 +0900 Subject: [PATCH 03/74] Add japanese translations for v4 This PR contains a Japanese translation for v4. Congratulations on the release of version 1.4.0! issue: #60 --- src/translations/translations_4.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 41f96f85..d77749f8 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -9,6 +9,7 @@ pub fn reserved_address_translation(language: Language, info: &str) -> String { match language { Language::EN => format!("Reserved address ({info})"), Language::IT => format!("Indirizzo riservato ({info})"), + Language::JA => format!("予約済みアドレス ({info})"), Language::PT => format!("Endereço reservado ({info})"), Language::UK => format!("Зарезервована адреса ({info})"), Language::ZH_TW => format!("保留的網路位址 ({info})"), @@ -20,6 +21,7 @@ pub fn share_feedback_translation(language: Language) -> &'static str { match language { Language::EN => "Share your feedback", Language::IT => "Condividi il tuo feedback", + Language::JA => "フィードバックを共有", Language::ZH_TW => "分享您的意見回饋", _ => "Share your feedback", } @@ -30,6 +32,7 @@ pub fn excluded_translation(language: Language) -> &'static str { match language { Language::EN => "Excluded", Language::IT => "Esclusi", + Language::JA => "除外", Language::ZH_TW => "已排除", _ => "Excluded", } @@ -39,6 +42,7 @@ pub fn import_capture_translation(language: Language) -> &'static str { match language { Language::EN => "Import capture file", Language::IT => "Importa file di cattura", + Language::JA => "キャプチャファイルをインポート", _ => "Import capture file", } } @@ -47,6 +51,7 @@ pub fn select_capture_translation(language: Language) -> &'static str { match language { Language::EN => "Select capture file", Language::IT => "Seleziona file di cattura", + Language::JA => "キャプチャファイルを選択", _ => "Select capture file", } } @@ -64,6 +69,11 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text {file_name_translation}: {file}\n\n\ Sei sicuro che il file che hai selezionato non sia vuoto?" ), + Language::JA => format!( + "ファイルからパケットを読み込み中...\n\n\ + {file_name_translation}: {file}\n\n\ + 選択したファイルが空でないことを確認しましたか?" + ), _ => format!( "Reading packets from file...\n\n\ {file_name_translation}: {file}\n\n\ @@ -76,6 +86,7 @@ pub fn data_exceeded_translation(language: Language) -> &'static str { match language { Language::EN => "Data threshold exceeded", Language::IT => "Soglia di dati superata", + Language::JA => "データの閾値を超えました", _ => "Data threshold exceeded", } } @@ -85,6 +96,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { match language { Language::EN => "Bits threshold exceeded", Language::IT => "Soglia di bit superata", + Language::JA => "ビットの閾値を超えました", _ => "Bits threshold exceeded", } } @@ -93,6 +105,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { pub fn bits_translation(language: Language) -> &'static str { match language { Language::EN | Language::IT => "Bits", + Language::JA => "ビット", _ => "Bits", } } @@ -102,6 +115,7 @@ pub fn pause_translation(language: Language) -> &'static str { match language { Language::EN => "Pause", Language::IT => "Pausa", + Language::JA => "一時停止", _ => "Pause", } } @@ -111,6 +125,7 @@ pub fn resume_translation(language: Language) -> &'static str { match language { Language::EN => "Resume", Language::IT => "Riprendi", + Language::JA => "再開", _ => "Resume", } } From 75e41530f7d25e87e7dffb874e8bb9cee85d24cc Mon Sep 17 00:00:00 2001 From: 18601673727 <18601673727@163.com> Date: Mon, 7 Jul 2025 15:44:23 +0800 Subject: [PATCH 04/74] Update translations_4.rs --- src/translations/translations_4.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 363fb88d..f9d69441 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -70,7 +70,7 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text Sei sicuro che il file che hai selezionato non sia vuoto?" ), Language::ZH => format!( - "从文件中读取封包...\n\n\ + "从文件中读取数据包...\n\n\ {file_name_translation}: {file}\n\n\ 您确定选中的文件不是空的吗?" ), @@ -125,6 +125,7 @@ pub fn resume_translation(language: Language) -> &'static str { match language { Language::EN => "Resume", Language::IT => "Riprendi", + Language::ZH => "恢复", _ => "Resume", } } From ffb85827ba3739b203f84f1fad07cad6328a2eef Mon Sep 17 00:00:00 2001 From: Lion Rayonnant <106342136+lionrayonnant@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:09:44 +0200 Subject: [PATCH 05/74] Update translations_4.rs : Adding french --- src/translations/translations_4.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 41f96f85..00289f00 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -12,6 +12,7 @@ pub fn reserved_address_translation(language: Language, info: &str) -> String { Language::PT => format!("Endereço reservado ({info})"), Language::UK => format!("Зарезервована адреса ({info})"), Language::ZH_TW => format!("保留的網路位址 ({info})"), + Language::FR => format!("Adresse réservée ({info})"), _ => format!("Reserved address ({info})"), } } @@ -21,6 +22,7 @@ pub fn share_feedback_translation(language: Language) -> &'static str { Language::EN => "Share your feedback", Language::IT => "Condividi il tuo feedback", Language::ZH_TW => "分享您的意見回饋", + Language::FR => "Partagez vos commentaires", _ => "Share your feedback", } } @@ -31,6 +33,7 @@ pub fn excluded_translation(language: Language) -> &'static str { Language::EN => "Excluded", Language::IT => "Esclusi", Language::ZH_TW => "已排除", + Language::FR => "Exclus", _ => "Excluded", } } @@ -39,6 +42,7 @@ pub fn import_capture_translation(language: Language) -> &'static str { match language { Language::EN => "Import capture file", Language::IT => "Importa file di cattura", + Language::FR => "Importer un fichier de capture", _ => "Import capture file", } } @@ -47,6 +51,7 @@ pub fn select_capture_translation(language: Language) -> &'static str { match language { Language::EN => "Select capture file", Language::IT => "Seleziona file di cattura", + Language::FR => "Sélectionner un fichier de capture", _ => "Select capture file", } } @@ -56,18 +61,23 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text Text::new(match language { Language::EN => format!( "Reading packets from file...\n\n\ - {file_name_translation}: {file}\n\n\ - Are you sure the file you selected isn't empty?" + {file_name_translation}: {file}\n\n\ + Are you sure the file you selected isn't empty?" ), Language::IT => format!( "Lettura pacchetti da file...\n\n\ - {file_name_translation}: {file}\n\n\ - Sei sicuro che il file che hai selezionato non sia vuoto?" + {file_name_translation}: {file}\n\n\ + Sei sicuro che il file che hai selezionato non sia vuoto?" + ), + Language::FR => format!( + "Lecture des paquets depuis le fichier...\n\n\ + {file_name_translation} : {file}\n\n\ + Êtes-vous sûr que le fichier sélectionné n'est pas vide ?" ), _ => format!( "Reading packets from file...\n\n\ - {file_name_translation}: {file}\n\n\ - Are you sure the file you selected isn't empty?" + {file_name_translation}: {file}\n\n\ + Are you sure the file you selected isn't empty?" ), }) } @@ -76,6 +86,7 @@ pub fn data_exceeded_translation(language: Language) -> &'static str { match language { Language::EN => "Data threshold exceeded", Language::IT => "Soglia di dati superata", + Language::FR => "Seuil de données dépassé", _ => "Data threshold exceeded", } } @@ -85,6 +96,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { match language { Language::EN => "Bits threshold exceeded", Language::IT => "Soglia di bit superata", + Language::FR => "Seuil de bits dépassé", _ => "Bits threshold exceeded", } } @@ -92,7 +104,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { #[allow(dead_code)] pub fn bits_translation(language: Language) -> &'static str { match language { - Language::EN | Language::IT => "Bits", + Language::EN | Language::IT | Language::FR => "Bits", _ => "Bits", } } @@ -102,6 +114,7 @@ pub fn pause_translation(language: Language) -> &'static str { match language { Language::EN => "Pause", Language::IT => "Pausa", + Language::FR => "Pause", _ => "Pause", } } @@ -111,6 +124,7 @@ pub fn resume_translation(language: Language) -> &'static str { match language { Language::EN => "Resume", Language::IT => "Riprendi", + Language::FR => "Reprendre", _ => "Resume", } } From 2394bce7c36d6e5b4291237b41dfb7e86a94fb08 Mon Sep 17 00:00:00 2001 From: Aris Konstantoulas <25184469+aris1009@users.noreply.github.com> Date: Sat, 19 Jul 2025 23:25:53 +0300 Subject: [PATCH 06/74] feat: add missing greek translations and improve existing one --- src/translations/translations.rs | 24 ++++++++++++------------ src/translations/translations_2.rs | 22 ++++++++++++++++++++++ src/translations/translations_3.rs | 22 ++++++++++++++++++++++ src/translations/translations_4.rs | 15 +++++++++++++++ 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/translations/translations.rs b/src/translations/translations.rs index 010bef21..08bc237f 100644 --- a/src/translations/translations.rs +++ b/src/translations/translations.rs @@ -21,7 +21,7 @@ pub fn choose_adapters_translation<'a>(language: Language) -> Text<'a, StyleType Language::TR => "İncelemek için bir ağ adaptörü seçiniz", Language::RU => "Выберите сетевой адаптер для инспекции", Language::PT => "Selecione o adaptador de rede a inspecionar", - Language::EL => "Επίλεξε τον προσαρμογέα δικτύου για επιθεώρηση", + Language::EL => "Επιλέξτε τον προσαρμογέα δικτύου για ανάλυση", // Language::FA => "مبدل شبکه را برای بازرسی انتخاب کنید", Language::SV => "Välj nätverksadapter att inspektera", Language::FI => "Valitse tarkasteltava verkkosovitin", @@ -76,7 +76,7 @@ pub fn select_filters_translation<'a>(language: Language) -> Text<'a, StyleType> Language::TR => "Ağ trafiğine uygulanacak filtreleri seçiniz", Language::RU => "Выберите фильтры для применения к сетевому трафику", Language::PT => "Selecione os filtros a serem aplicados no tráfego de rede", - Language::EL => "Επίλεξε τα φίλτρα για εφαρμογή στην κίνηση του δικτύου", + Language::EL => "Επιλέξτε τα φίλτρα που θα εφαρμοστούν στην κίνηση του δικτύου", // Language::FA => "صافی ها را جهت اعمال بر آمد و شد شبکه انتخاب کنید", Language::SV => "Välj filtren som ska appliceras på nätverkstrafiken", Language::FI => "Valitse suodattimet verkkoliikenteelle", @@ -361,7 +361,7 @@ pub fn ask_quit_translation<'a>(language: Language) -> Text<'a, StyleType> { Language::TR => "Bu analizden çıkmak istediğine emin misin?", Language::RU => "Вы уверены, что хотите выйти из текущего анализа?", Language::PT => "Tem a certeza que deseja sair desta análise?", - Language::EL => "Είσαι σίγουρος ότι θες να κλείσεις την ανάλυση;", + Language::EL => "Είστε βέβαιοι ότι θέλετε να τερματίσετε την ανάλυση;", // Language::FA => "آیا مطمئن هستید می خواهید از این تحلیل خارج شوید؟", Language::SV => "Är du säker på att du vill avsluta analysen?", Language::FI => "Haluatko varmasti lopettaa analyysin?", @@ -417,7 +417,7 @@ pub fn ask_clear_all_translation<'a>(language: Language) -> Text<'a, StyleType> Language::TR => "Bildirimleri temizlemek istediğine emin misin?", Language::RU => "Вы уверены, что хотите удлить все уведомления?", Language::PT => "Tem a certeza que deseja eliminar as notificações?", - Language::EL => "Είσαι σίγουρος ότι θες να κάνεις εκκαθάριση των ειδοποιήσεων;", + Language::EL => "Είστε βέβαιοι ότι θέλετε να εκκαθαρίσετε τις ειδοποιήσεις;", // Language::FA => "آیا مطمئن هستید می خواهید اعلان ها را پاک کنید؟", Language::SV => "Är du säker på att du vill radera notifikationerna?", Language::FI => "Haluatko varmasti tyhjentää ilmoitukset?", @@ -1462,7 +1462,7 @@ pub fn appearance_title_translation<'a>(language: Language) -> Text<'a, StyleTyp Language::TR => "Favori temanızı seçin", Language::RU => "Выберите предпочительную тему", Language::PT => "Escolha o seu tema favorito", - Language::EL => "Επίλεξε το αγαπημένο σου θέμα", + Language::EL => "Επιλέξτε το αγαπημένο σας θέμα", // Language::FA => "زمینه دلخواه خود را انتخاب کنید", Language::SV => "Välj ditt favorittema", Language::FI => "Valitse suosikkiteemasi", @@ -1787,7 +1787,7 @@ pub fn overview_translation(language: Language) -> &'static str { Language::TR => "Ön izleme", Language::RU => "Обзор", Language::PT => "Visão geral", - Language::EL => "επισκόπηση", + Language::EL => "Επισκόπηση", // Language::FA => "نمای کلی", Language::SV => "Översikt", Language::FI => "Yleiskatsaus", @@ -2167,7 +2167,7 @@ pub fn favorite_transmitted_translation(language: Language) -> &'static str { Language::TR => "Favorilerden yeni veri aktarıldı", Language::RU => "Новый обмен данными в избранных соедиениях", Language::PT => "Novos dados trocados dos favoritos", - Language::EL => "Καινούρια δεδομένα έχουν ανταλλαγεί στα αγαπημένα", + Language::EL => "Νέα δεδομένα έχουν ανταλλαγεί στα αγαπημένα", // Language::FA => "مبادله داده جدید از پسندیده ها", Language::SV => "Ny data utbytt av favoriter", Language::FI => "Uusia tietoja vaihdettu suosikeista", @@ -2253,9 +2253,9 @@ pub fn no_notifications_set_translation<'a>(language: Language) -> Text<'a, Styl Pode ativar as notificações nas definições:" } Language::EL => { - "Δεν έχεις ενεργοποιήσει τις ειδοποιήσεις ακόμη!\n\n\ - Αφότου τις ενεργοποιήσεις, αυτή η σελίδα θα απεικονίσει μια καταγραφή των ειδοποιήσεών σου\n\n\ - Μπορείς να ενεργοποιήσεις τις ειδοποιήσεις από τις ρυθμίσεις:" + "Δεν έχετε ενεργοποιήσει τις ειδοποιήσεις ακόμη!\n\n\ + Αφότου τις ενεργοποιήσετε, αυτή η σελίδα θα εμφανίσει ένα αρχείο καταγραφής των ειδοποιήσεών σας\n\n\ + Μπορείτε να ενεργοποιήσετε τις ειδοποιήσεις από τις ρυθμίσεις:" } // Language::FA => "شما هنوز اعلان ها را فعال نکرده اید!\n\n\ // پس از آنکه آن ها را فعال کنید، این صفحه یک کارنامه از اعلان های شما را نمایش خواهد داد\n\n @@ -2357,8 +2357,8 @@ pub fn no_notifications_received_translation<'a>(language: Language) -> Text<'a, Quando receber uma notificação, ela será mostrada aqui" } Language::EL => { - "Δεν υπάρχει κάτι για απεικόνιση αυτή τη στιγμή...\n\n\ - Όταν λάβεις μια ειδοποίηση, αυτή θα εμφανιστεί εδώ" + "Δεν υπάρχουν ειδοποιήσεις αυτή τη στιγμή...\n\n\ + Όταν λάβετε μια ειδοποίηση, θα εμφανιστεί εδώ" } // Language::FA => { // "در حال حاضر هیچ چیزی برای دیدن نیست...\n\n\ diff --git a/src/translations/translations_2.rs b/src/translations/translations_2.rs index 66c3a981..c6d00170 100644 --- a/src/translations/translations_2.rs +++ b/src/translations/translations_2.rs @@ -54,6 +54,7 @@ pub fn inspect_translation(language: Language) -> &'static str { Language::VI => "Quan sát", Language::ID => "Memeriksa", Language::NL => "Inspecteren", + Language::EL => "Επιθεώρηση", _ => "Inspect", } } @@ -82,6 +83,7 @@ pub fn connection_details_translation(language: Language) -> &'static str { Language::VI => "Thông tin kết nối", Language::ID => "Rincian koneksi", Language::NL => "Verbindingsdetails", + Language::EL => "Λεπτομέρειες σύνδεσης", _ => "Connection details", } } @@ -110,6 +112,7 @@ pub fn dropped_translation(language: Language) -> &'static str { Language::VI => "Mất", Language::ID => "Dihapus", Language::NL => "Verloren", + Language::EL => "Απορριμμένα", _ => "Dropped", } } @@ -138,6 +141,7 @@ pub fn data_representation_translation(language: Language) -> &'static str { Language::VI => "Miêu tả dữ liệu", Language::ID => "Penyajian ulang data", Language::NL => "Gegevensweergave", + Language::EL => "Αναπαράσταση δεδομένων", _ => "Data representation", } } @@ -166,6 +170,7 @@ pub fn host_translation(language: Language) -> &'static str { Language::VI => "Máy chủ", Language::ID => "Jaringan asal", Language::NL => "Netwerk host", + Language::EL => "Κόμβος δικτύου", _ => "Network host", } } @@ -194,6 +199,7 @@ pub fn only_top_30_items_translation(language: Language) -> &'static str { Language::VI => "Chỉ có 30 mục gần nhất được hiển thị ở đây", Language::ID => "Hanya 30 teratas yang ditampilkan disini", Language::NL => "Alleen de bovenste 30 items worden hier weergegeven", + Language::EL => "Εμφανίζονται μόνο τα κορυφαία 30 στοιχεία", _ => "Only the top 30 items are displayed here", } } @@ -248,6 +254,7 @@ pub fn local_translation(language: Language) -> &'static str { Language::VI => "Mạng nội bộ", Language::ID => "Jaringan lokal", Language::NL => "Lokaal netwerk", + Language::EL => "Τοπικό δίκτυο", _ => "Local network", } } @@ -276,6 +283,7 @@ pub fn unknown_translation(language: Language) -> &'static str { Language::VI => "Không rõ địa điểm", Language::ID => "Lokasi tidak diketahui", Language::NL => "Onbekende locatie", + Language::EL => "Άγνωστη τοποθεσία", _ => "Unknown location", } } @@ -304,6 +312,7 @@ pub fn your_network_adapter_translation(language: Language) -> &'static str { Language::VI => "Network adapter của bạn", Language::ID => "Adaptor jaringan kamu", Language::NL => "Uw netwerkadapter", + Language::EL => "Ο προσαρμογέας δικτύου σας", _ => "Your network adapter", } } @@ -332,6 +341,7 @@ pub fn socket_address_translation(language: Language) -> &'static str { Language::VI => "Địa chỉ socket", Language::ID => "Alamat sambungan", Language::NL => "Socket adres", + Language::EL => "Διεύθυνση υποδοχής", _ => "Socket address", } } @@ -360,6 +370,7 @@ pub fn mac_address_translation(language: Language) -> &'static str { Language::VI => "Địa chỉ MAC", Language::ID => "Alamat MAC", Language::NL => "MAC-adres", + Language::EL => "Διεύθυνση MAC", _ => "MAC address", } } @@ -388,6 +399,7 @@ pub fn source_translation(language: Language) -> &'static str { Language::VI => "Nguồn", Language::ID => "Asal", Language::NL => "Bron", + Language::EL => "Πηγή", _ => "Source", } } @@ -414,6 +426,7 @@ pub fn destination_translation(language: Language) -> &'static str { Language::VI => "Đích", Language::ID => "Tujuan", Language::NL => "Bestemming", + Language::EL => "Προορισμός", _ => "Destination", } } @@ -441,6 +454,7 @@ pub fn fqdn_translation(language: Language) -> &'static str { Language::VI => "Tên miền đầy đủ", Language::ID => "Nama domain yang memenuhi syarat", Language::NL => "Volledig gekwalificeerde domeinnaam", + Language::EL => "Πλήρως προσδιορισμένο όνομα τομέα", _ => "Fully qualified domain name", } } @@ -469,6 +483,7 @@ pub fn administrative_entity_translation(language: Language) -> &'static str { Language::VI => "Tên Autonomous System", Language::ID => "Nama System Otomatis", Language::NL => "Naam van het autonome systeem", + Language::EL => "Όνομα αυτόνομου συστήματος", _ => "Autonomous System name", } } @@ -497,6 +512,7 @@ pub fn transmitted_data_translation(language: Language) -> &'static str { Language::VI => "Dữ liệu được truyền", Language::ID => "Data terkirim", Language::NL => "Verzonden gegevens", + Language::EL => "Μεταδιδόμενα δεδομένα", _ => "Transmitted data", } } @@ -524,6 +540,7 @@ pub fn country_translation(language: Language) -> &'static str { Language::VI => "Quốc gia", Language::ID => "Negara", Language::NL => "Land", + Language::EL => "Χώρα", _ => "Country", } } @@ -552,6 +569,7 @@ pub fn domain_name_translation(language: Language) -> &'static str { Language::VI => "Tên miền", Language::ID => "Nama Domain", Language::NL => "Domeinnaam", + Language::EL => "Όνομα τομέα", _ => "Domain name", } } @@ -580,6 +598,7 @@ pub fn only_show_favorites_translation(language: Language) -> &'static str { Language::VI => "Chỉ hiển thị mục ưa thích", Language::ID => "Hanya tunjukkan favorit", Language::NL => "Toon alleen favorieten", + Language::EL => "Εμφάνιση μόνο αγαπημένων", _ => "Only show favorites", } } @@ -635,6 +654,7 @@ pub fn no_search_results_translation(language: Language) -> &'static str { Language::VI => "Không có kết quả nào theo các bộ lọc được chỉ định", Language::ID => "Tidak ada hasil berdasarkan filter pencarian spesifik", Language::NL => "Geen resultaten beschikbaar volgens de opgegeven zoekfilters", + Language::EL => "Δεν υπάρχουν διαθέσιμα αποτελέσματα σύμφωνα με τα καθορισμένα φίλτρα αναζήτησης", _ => "No result available according to the specified search filters", } } @@ -670,6 +690,7 @@ pub fn showing_results_translation( Language::NL => { format!("{start}-{end} van de {total} totale resultaten worden weergegeven") } + Language::EL => format!("Εμφάνιση {start}-{end} από {total} συνολικά αποτελέσματα"), _ => format!("Showing {start}-{end} of {total} total results"), } } @@ -699,6 +720,7 @@ pub fn color_gradients_translation(language: Language) -> &'static str { Language::VI => "Áp dụng color gradients", Language::ID => "Aplikasikan gradasi warna", Language::NL => "Kleurverlopen toepassen", + Language::EL => "Εφαρμογή χρωματικών διαβαθμίσεων", _ => "Apply color gradients", } } diff --git a/src/translations/translations_3.rs b/src/translations/translations_3.rs index 3cfe33c4..9b430828 100644 --- a/src/translations/translations_3.rs +++ b/src/translations/translations_3.rs @@ -28,6 +28,7 @@ pub fn general_translation(language: Language) -> &'static str { Language::UK => "Загальні", Language::ID => "Umum", Language::NL => "Algemeen", + Language::EL => "Γενικά", _ => "General", } } @@ -55,6 +56,7 @@ pub fn zoom_translation(language: Language) -> &'static str { Language::TR => "Yakınlaştırma", Language::UK => "Масштабування", Language::ID => "Perbesar", + Language::EL => "Εστίαση", _ => "Zoom", } } @@ -82,6 +84,7 @@ pub fn mmdb_files_translation(language: Language) -> &'static str { Language::UK => "Файли бази даних", Language::ID => "Berkas database", Language::NL => "Database bestanden", + Language::EL => "Αρχεία βάσης δεδομένων", _ => "Database files", } } @@ -109,6 +112,7 @@ pub fn params_not_editable_translation(language: Language) -> &'static str { Language::UK => "Наступні параметри не можна змінювати під час аналізу трафіку", Language::ID => "Parameter berikut tidak bisa diubah saat dianalisa", Language::NL => "De volgende parameters kunnen niet worden aangepast tijdens de analyse", + Language::EL => "Οι ακόλουθες παράμετροι δεν μπορούν να τροποποιηθούν κατά τη διάρκεια της ανάλυσης", _ => "The following parameters can't be modified during the analysis", } } @@ -135,6 +139,7 @@ pub fn custom_style_translation(language: Language) -> &'static str { Language::UK => "Власний стиль", Language::ID => "Ubah Model", Language::NL => "Aangepaste stijl", + Language::EL => "Προσαρμοσμένο στυλ", _ => "Custom style", } } @@ -160,6 +165,7 @@ pub fn copy_translation(language: Language) -> &'static str { Language::UK => "Копіювати", Language::ID => "Salin", Language::NL => "Kopiëren", + Language::EL => "Αντιγραφή", _ => "Copy", } } @@ -186,6 +192,7 @@ pub fn port_translation(language: Language) -> &'static str { Language::UK => "Порт", Language::ID => "Port", Language::NL => "Poort", + Language::EL => "Θύρα", _ => "Port", } } @@ -212,6 +219,7 @@ pub fn invalid_filters_translation(language: Language) -> &'static str { Language::UK => "Неправильний формат фільтрів", Language::ID => "Filter salah", Language::NL => "Ongeldige filters", + Language::EL => "Μη έγκυρα φίλτρα", _ => "Invalid filters", } } @@ -238,6 +246,7 @@ pub fn messages_translation(language: Language) -> &'static str { Language::UK => "Повідомлення", Language::ID => "Pesan", Language::NL => "Berichten", + Language::EL => "Μηνύματα", _ => "Messages", } } @@ -264,6 +273,7 @@ pub fn link_type_translation(language: Language) -> &'static str { Language::PT => "Tipo de conexão", Language::UK => "Різновид зʼєднання", Language::ID => "Tipe koneksi", + Language::EL => "Τύπος σύνδεσης", _ => "Link type", } } @@ -324,6 +334,9 @@ pub fn unsupported_link_type_translation<'a>( Language::NL => { "Het linktype dat is gekoppeld aan deze adapter wordt nog niet ondersteund door Sniffnet..." } + Language::EL => { + "Ο τύπος σύνδεσης που σχετίζεται με αυτόν τον προσαρμογέα δεν υποστηρίζεται ακόμη από το Sniffnet..." + } _ => "The link type associated with this adapter is not supported by Sniffnet yet...", }; @@ -356,6 +369,7 @@ pub fn style_from_file_translation(language: Language) -> &'static str { Language::UK => "Виберіть стиль з файлу", Language::ID => "Pilih model / gaya dari berkas", Language::NL => "Selecteer stijl vanuit een bestand", + Language::EL => "Επιλογή στυλ από αρχείο", _ => "Select style from a file", } } @@ -383,6 +397,7 @@ pub fn database_from_file_translation(language: Language) -> &'static str { Language::UK => "Виберіть файл бази даних", Language::ID => "Pilih berkas database", Language::NL => "Selecteer database bestand", + Language::EL => "Επιλογή αρχείου βάσης δεδομένων", _ => "Select database file", } } @@ -410,6 +425,7 @@ pub fn filter_by_host_translation(language: Language) -> &'static str { Language::UK => "Фільтр за хостом мережі", Language::ID => "Filter berdasarkan jaringan asal", Language::NL => "Filteren op netwerk host", + Language::EL => "Φίλτρο ανά διακομιστή δικτύου", _ => "Filter by network host", } } @@ -434,6 +450,7 @@ pub fn service_translation(language: Language) -> &'static str { Language::UK => "Сервіс", Language::ID => "Layanan", Language::NL => "Dienst", + Language::EL => "Υπηρεσία", _ => "Service", } } @@ -461,6 +478,7 @@ pub fn export_capture_translation(language: Language) -> &'static str { Language::ID => "Ekspor data tangkapan", Language::ES => "Exportar archivo de captura", Language::NL => "Exporteer capture bestand", + Language::EL => "Εξαγωγή αρχείου καταγραφής", _ => "Export capture file", } } @@ -487,6 +505,7 @@ pub fn directory_translation(language: Language) -> &'static str { Language::ID => "Direktori", Language::ES => "Directorio", Language::NL => "Map", + Language::EL => "Κατάλογος", _ => "Directory", } } @@ -514,6 +533,7 @@ pub fn select_directory_translation(language: Language) -> &'static str { Language::ID => "Pilih direktori tujuan", Language::ES => "Selecciona el directorio de destino", Language::NL => "Selecteer doelmap", + Language::EL => "Επιλογή καταλόγου προορισμού", _ => "Select destination directory", } } @@ -541,6 +561,7 @@ pub fn file_name_translation(language: Language) -> &'static str { Language::ID => "Nama berkas", Language::ES => "Nombre del archivo", Language::NL => "Bestandsnaam", + Language::EL => "Όνομα αρχείου", _ => "File name", } } @@ -567,6 +588,7 @@ pub fn thumbnail_mode_translation(language: Language) -> &'static str { Language::UK => "Режим мініатюри", Language::ID => "Mode gambar kecil", Language::NL => "Miniatuur modus", + Language::EL => "Λειτουργία μικρογραφιών", _ => "Thumbnail mode", } } diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 895ae240..04897e41 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -13,6 +13,7 @@ pub fn reserved_address_translation(language: Language, info: &str) -> String { Language::UK => format!("Зарезервована адреса ({info})"), Language::ZH_TW => format!("保留的網路位址 ({info})"), Language::NL => format!("Gereserveerd adres ({info})"), + Language::EL => format!("Δεσμευμένη διεύθυνση ({info})"), _ => format!("Reserved address ({info})"), } } @@ -23,6 +24,7 @@ pub fn share_feedback_translation(language: Language) -> &'static str { Language::IT => "Condividi il tuo feedback", Language::ZH_TW => "分享您的意見回饋", Language::NL => "Deel uw feedback", + Language::EL => "Μοιραστείτε τα σχόλιά σας", _ => "Share your feedback", } } @@ -34,6 +36,7 @@ pub fn excluded_translation(language: Language) -> &'static str { Language::IT => "Esclusi", Language::ZH_TW => "已排除", Language::NL => "Uitgesloten", + Language::EL => "Εξαιρούμενα", _ => "Excluded", } } @@ -43,6 +46,7 @@ pub fn import_capture_translation(language: Language) -> &'static str { Language::EN => "Import capture file", Language::IT => "Importa file di cattura", Language::NL => "Importeer capture bestand", + Language::EL => "Εισαγωγή αρχείου καταγραφής", _ => "Import capture file", } } @@ -52,6 +56,7 @@ pub fn select_capture_translation(language: Language) -> &'static str { Language::EN => "Select capture file", Language::IT => "Seleziona file di cattura", Language::NL => "Selecteer capture bestand", + Language::EL => "Επιλογή αρχείου καταγραφής", _ => "Select capture file", } } @@ -74,6 +79,11 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text {file_name_translation}: {file}\n\n\ Weet je zeker dat het geselecteerde bestand niet leeg is?" ), + Language::EL => format!( + "Ανάγνωση πακέτων από αρχείο...\n\n\ + {file_name_translation}: {file}\n\n\ + Είστε βέβαιοι ότι το επιλεγμένο αρχείο δεν είναι κενό;" + ), _ => format!( "Reading packets from file...\n\n\ {file_name_translation}: {file}\n\n\ @@ -87,6 +97,7 @@ pub fn data_exceeded_translation(language: Language) -> &'static str { Language::EN => "Data threshold exceeded", Language::IT => "Soglia di dati superata", Language::NL => "Gegevenslimiet overschreden", + Language::EL => "Υπέρβαση ορίου δεδομένων", _ => "Data threshold exceeded", } } @@ -97,6 +108,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { Language::EN => "Bits threshold exceeded", Language::IT => "Soglia di bit superata", Language::NL => "Bits limiet overschreden", + Language::EL => "Υπέρβαση ορίου δυφίων", _ => "Bits threshold exceeded", } } @@ -106,6 +118,7 @@ pub fn bits_translation(language: Language) -> &'static str { match language { Language::EN | Language::IT => "Bits", Language::NL => "Bits", + Language::EL => "Δυφία", _ => "Bits", } } @@ -116,6 +129,7 @@ pub fn pause_translation(language: Language) -> &'static str { Language::EN => "Pause", Language::IT => "Pausa", Language::NL => "Pauzeren", + Language::EL => "Παύση", _ => "Pause", } } @@ -126,6 +140,7 @@ pub fn resume_translation(language: Language) -> &'static str { Language::EN => "Resume", Language::IT => "Riprendi", Language::NL => "Hervatten", + Language::EL => "Συνέχεια", _ => "Resume", } } From e50043a36a35b8e05f36b35cfc933305cd5d08b0 Mon Sep 17 00:00:00 2001 From: MelcuGoa <80478973+MelcuGoa@users.noreply.github.com> Date: Mon, 21 Jul 2025 13:57:55 +0300 Subject: [PATCH 07/74] Update translations_4.rs Added translation for Romanian --- src/translations/translations_4.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 895ae240..c74ca2c3 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -13,6 +13,7 @@ pub fn reserved_address_translation(language: Language, info: &str) -> String { Language::UK => format!("Зарезервована адреса ({info})"), Language::ZH_TW => format!("保留的網路位址 ({info})"), Language::NL => format!("Gereserveerd adres ({info})"), + Language::RO => format!("Adresă rezervată ({info})"), _ => format!("Reserved address ({info})"), } } @@ -23,6 +24,7 @@ pub fn share_feedback_translation(language: Language) -> &'static str { Language::IT => "Condividi il tuo feedback", Language::ZH_TW => "分享您的意見回饋", Language::NL => "Deel uw feedback", + Language::RO => "Împărtășiți feedback-ul dvs.", _ => "Share your feedback", } } @@ -34,6 +36,7 @@ pub fn excluded_translation(language: Language) -> &'static str { Language::IT => "Esclusi", Language::ZH_TW => "已排除", Language::NL => "Uitgesloten", + Language::RO => "Excluși", _ => "Excluded", } } @@ -43,6 +46,7 @@ pub fn import_capture_translation(language: Language) -> &'static str { Language::EN => "Import capture file", Language::IT => "Importa file di cattura", Language::NL => "Importeer capture bestand", + Language::RO => "Importă fișierul de captură", _ => "Import capture file", } } @@ -52,6 +56,7 @@ pub fn select_capture_translation(language: Language) -> &'static str { Language::EN => "Select capture file", Language::IT => "Seleziona file di cattura", Language::NL => "Selecteer capture bestand", + Language::RO => "Selectează fișierul de captură", _ => "Select capture file", } } @@ -74,6 +79,11 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text {file_name_translation}: {file}\n\n\ Weet je zeker dat het geselecteerde bestand niet leeg is?" ), + Language::RO => format!( + "Citirea pachetelor din fișier...\n\n\ + {file_name_translation}: {file}\n\n\ + Ești sigur că fișierul selectat nu este gol?" + ), _ => format!( "Reading packets from file...\n\n\ {file_name_translation}: {file}\n\n\ @@ -87,6 +97,7 @@ pub fn data_exceeded_translation(language: Language) -> &'static str { Language::EN => "Data threshold exceeded", Language::IT => "Soglia di dati superata", Language::NL => "Gegevenslimiet overschreden", + Language::RO => "Limita de date depășită", _ => "Data threshold exceeded", } } @@ -97,6 +108,7 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { Language::EN => "Bits threshold exceeded", Language::IT => "Soglia di bit superata", Language::NL => "Bits limiet overschreden", + Language::RO => "Limita de biți depășită", _ => "Bits threshold exceeded", } } @@ -106,6 +118,7 @@ pub fn bits_translation(language: Language) -> &'static str { match language { Language::EN | Language::IT => "Bits", Language::NL => "Bits", + Language::RO => "Biți", _ => "Bits", } } @@ -116,6 +129,7 @@ pub fn pause_translation(language: Language) -> &'static str { Language::EN => "Pause", Language::IT => "Pausa", Language::NL => "Pauzeren", + Language::RO => "Pauză", _ => "Pause", } } @@ -126,6 +140,7 @@ pub fn resume_translation(language: Language) -> &'static str { Language::EN => "Resume", Language::IT => "Riprendi", Language::NL => "Hervatten", + Language::RO => "Continuă", _ => "Resume", } } From 00fac5e61490d9361ed45258fea9fc70a4dec51e Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 23 Jul 2025 17:31:02 +0200 Subject: [PATCH 08/74] update package CI/CD to sign the Windows Installer using SignPath --- .github/workflows/package.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index f89dbbe4..6e683797 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -318,9 +318,29 @@ jobs: cargo wix --no-build --nocapture --target ${{ matrix.target }} Move-Item -Path target\wix\sniffnet*.msi -Destination .\artifacts\Sniffnet_Windows_${{ matrix.arch }}.msi - - name: Upload package artifacts + - name: Upload unsigned package artifacts + id: upload-unsigned-artifact uses: actions/upload-artifact@v4 with: name: msi-${{ matrix.arch }} path: artifacts/ if-no-files-found: error + + - name: Sign package artifacts + uses: signpath/github-action-submit-signing-request@v1.1 + with: + api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' + organization-id: '3b533e02-73c3-4908-a018-d09a34498a6a' + project-slug: 'sniffnet' + signing-policy-slug: 'test-signing' + github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}' + wait-for-completion: true + output-artifact-directory: './artifacts' + + - name: Upload signed package artifacts (overwrite unsigned) + uses: actions/upload-artifact@v4 + with: + name: msi-${{ matrix.arch }} + path: artifacts/ + if-no-files-found: error + overwrite: true From 1fa7ec924d1d3c9375ce1539d802a573279efdaf Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 25 Jul 2025 22:30:59 +0200 Subject: [PATCH 09/74] update CHANGELOG and README --- .github/workflows/package.yml | 2 +- CHANGELOG.md | 1 + README.md | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 6e683797..31a82a86 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -332,7 +332,7 @@ jobs: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '3b533e02-73c3-4908-a018-d09a34498a6a' project-slug: 'sniffnet' - signing-policy-slug: 'test-signing' + signing-policy-slug: 'release-signing' github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}' wait-for-completion: true output-artifact-directory: './artifacts' diff --git a/CHANGELOG.md b/CHANGELOG.md index e8faa8ac..27323952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ # Changelog ## [UNRELEASED] - Added Dutch translation 🇳🇱 ([#854](https://github.com/GyulyVGC/sniffnet/pull/854)) +- The Windows Installer is now signed with a code signing certificate provided by the [SignPath Foundation](https://signpath.org/) ([#897](https://github.com/GyulyVGC/sniffnet/pull/897) — fixes [#894](https://github.com/GyulyVGC/sniffnet/issues/894)) - Updated some of the existing translations to v1.4: - German ([#833](https://github.com/GyulyVGC/sniffnet/pull/833)) diff --git a/README.md b/README.md index 524e1d0f..8b31066d 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ ## Acknowledgements - A big shout-out to [all the contributors](https://github.com/GyulyVGC/sniffnet/blob/main/CONTRIBUTORS.md) of Sniffnet! - The graphical user interface has been realized with [iced](https://github.com/iced-rs/iced), a cross-platform GUI library for Rust focused on simplicity and type-safety - IP geolocation and ASN data are provided by [MaxMind](https://www.maxmind.com) +- Free code signing for Windows Installer is provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) - [Sniffnet](https://ads.fund/token/0xadfc251f8ef00ceaeca2b5c1882dabe5db0833df) project is supported by ADS.FUND - Last but not least, thanks to [every single stargazer](https://github.com/GyulyVGC/sniffnet/stargazers): all forms of support made it possible to keep improving Sniffnet! From c88fb8051020d23c676e33c2d028a7b280706c84 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 26 Jul 2025 22:33:09 +0200 Subject: [PATCH 10/74] docs: add islameehassan as a contributor for code (#905) * docs: update CONTRIBUTORS.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7db83d3d..e2c07230 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -721,6 +721,15 @@ "contributions": [ "platform" ] + }, + { + "login": "islameehassan", + "name": "islameehassan", + "avatar_url": "https://avatars.githubusercontent.com/u/98806155?v=4", + "profile": "https://github.com/islameehassan", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9aeb7a50..e31820db 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -92,19 +92,20 @@ glitsj16
glitsj16

📦 guilherme-demarchi
guilherme-demarchi

🌍 hirotake111
hirotake111

🌍 + islameehassan
islameehassan

💻 louis-ym4
louis-ym4

🎨 - luca3s
luca3s

🌍 + luca3s
luca3s

🌍 pia
pia

🌍 pin
pin

📦 shu-kitamura
shu-kitamura

💻 starccy
starccy

💻 tiansheng li
tiansheng li

💵 vtiinanen
vtiinanen

🌍 - yossarian
yossarian

🌍 + yossarian
yossarian

🌍 陈寒彤
陈寒彤

🌍 From 9857679c711e133b6d74fbb1dabfbde913fb0c7e Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Tue, 29 Jul 2025 12:49:11 +0200 Subject: [PATCH 11/74] update deps --- Cargo.lock | 131 +++++++++++++++++++++++++++++------------------------ Cargo.toml | 8 ++-- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28ee19c3..fd0efb42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1167,7 +1167,7 @@ checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" dependencies = [ "cfg-if", "libc", - "socket2", + "socket2 0.5.10", "windows-sys 0.48.0", ] @@ -1325,9 +1325,9 @@ dependencies = [ [[package]] name = "etherparse" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff83a5facf1a7cbfef93cfb48d6d4fb6a1f42d8ac2341a96b3255acb4d4f860" +checksum = "54a48e7bdc36cdf86876a6890c0840957029374ea07f6697c96fa15898730375" dependencies = [ "arrayvec", ] @@ -2022,9 +2022,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", @@ -2038,7 +2038,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -2409,9 +2409,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -2550,11 +2550,12 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" dependencies = [ "arrayvec", + "euclid", "smallvec", ] @@ -2593,7 +2594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -2604,13 +2605,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7" dependencies = [ "bitflags 2.9.1", "libc", - "redox_syscall 0.5.14", + "redox_syscall 0.5.17", ] [[package]] @@ -3433,9 +3434,9 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ "ttf-parser 0.25.1", ] @@ -3513,7 +3514,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.14", + "redox_syscall 0.5.17", "smallvec", "windows-targets 0.52.6", ] @@ -3837,7 +3838,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", @@ -3874,7 +3875,7 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -3961,9 +3962,9 @@ checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" [[package]] name = "rangemap" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" +checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" [[package]] name = "raw-window-handle" @@ -4021,9 +4022,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.14" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -4218,21 +4219,20 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rstest" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" dependencies = [ "futures-timer", "futures-util", "rstest_macros", - "rustc_version", ] [[package]] name = "rstest_macros" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" dependencies = [ "cfg-if", "glob", @@ -4258,9 +4258,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -4740,7 +4740,7 @@ dependencies = [ "serial_test", "splines", "tokio", - "toml 0.9.2", + "toml 0.9.3", "winres", ] @@ -4754,6 +4754,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "softbuffer" version = "0.4.6" @@ -4774,7 +4784,7 @@ dependencies = [ "objc2-foundation 0.2.2", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.14", + "redox_syscall 0.5.17", "rustix 0.38.44", "tiny-xlib", "wasm-bindgen", @@ -4846,7 +4856,7 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ - "kurbo 0.11.2", + "kurbo 0.11.3", "siphasher", ] @@ -5121,9 +5131,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" dependencies = [ "backtrace", "bytes", @@ -5132,9 +5142,9 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5204,9 +5214,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +checksum = "e06723639aaded957e5a80be250c1f82f274b9d23ebb4d94163668470623461c" dependencies = [ "indexmap", "serde", @@ -5497,7 +5507,7 @@ dependencies = [ "flate2", "fontdb 0.18.0", "imagesize", - "kurbo 0.11.2", + "kurbo 0.11.3", "log", "pico-args", "roxmltree", @@ -5659,13 +5669,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.44", + "rustix 1.0.8", "scoped-tls", "smallvec", "wayland-sys", @@ -5673,12 +5683,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.1", - "rustix 0.38.44", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] @@ -5696,20 +5706,20 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.8", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.8" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -5745,9 +5755,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml", @@ -5756,9 +5766,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -6132,7 +6142,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -6183,10 +6193,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -6409,9 +6420,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winit" -version = "0.30.11" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash 0.8.12", "android-activity", diff --git a/Cargo.toml b/Cargo.toml index ba545074..65b430bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ strip = true [dependencies] pcap = "2.3.0" -etherparse = "0.18.0" +etherparse = "0.18.2" chrono = { version = "0.4.41", default-features = false, features = ["clock"] } plotters = { version = "0.3.7", default-features = false, features = ["area_series", "line_series"] } iced = { version = "0.13.1", features = ["tokio", "svg", "advanced", "lazy", "image"] } @@ -48,14 +48,14 @@ confy = "1.0.0" serde = { version = "1.0.219", default-features = false, features = ["derive"] } rodio = { version = "0.21.1", default-features = false, features = ["mp3", "playback"] } dns-lookup = "2.0.4" -toml = "0.9.2" +toml = "0.9.3" ctrlc = { version = "3.4.7", features = ["termination"] } rfd = "0.15.4" phf = "0.12.1" phf_shared = "0.12.1" splines = "5.0.0" clap = { version = "4.5.41", features = ["derive"] } -tokio = { version = "1.46.1", features = ["macros"] } +tokio = { version = "1.47.0", features = ["macros"] } async-channel = "2.5.0" [target.'cfg(windows)'.dependencies] @@ -71,7 +71,7 @@ reqwest = { version = "0.12.22", features = ["json"] } [dev-dependencies] serde_test = "1.0.177" -rstest = "0.25.0" +rstest = "0.26.1" serial_test = { version = "3.2.0", default-features = false } #─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── From d9af55f981b6d53e6d484570fd3579241c39999b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 03:12:40 +0000 Subject: [PATCH 12/74] Bump signpath/github-action-submit-signing-request from 1.1 to 1.2 Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 1.1 to 1.2. - [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v1.1...v1.2) --- updated-dependencies: - dependency-name: signpath/github-action-submit-signing-request dependency-version: '1.2' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 31a82a86..0c83bbf4 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -327,7 +327,7 @@ jobs: if-no-files-found: error - name: Sign package artifacts - uses: signpath/github-action-submit-signing-request@v1.1 + uses: signpath/github-action-submit-signing-request@v1.2 with: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '3b533e02-73c3-4908-a018-d09a34498a6a' From 0bdca2d98acd3f41f94bbbc0f9ca1007e0c9da0f Mon Sep 17 00:00:00 2001 From: allem43 <16104173+AlleM43@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:16:42 +0200 Subject: [PATCH 13/74] Implement AppImage Packaging in CI --- .github/workflows/package.yml | 67 ++++++++++++++++++- .../packaging/linux/AppImage/sniffnet.yml | 9 +++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 resources/packaging/linux/AppImage/sniffnet.yml diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 31a82a86..a7977bf1 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -81,14 +81,15 @@ jobs: - name: Test (release mode) if: matrix.os == 'macos' || matrix.os == 'ubuntu' || matrix.os == 'windows' && matrix.arch == 'amd64' - run: | - cargo test --release --verbose -- --nocapture && - cargo clean + run: cargo test --release --verbose -- --nocapture && - name: Install Cross if: matrix.os == 'ubuntu' run: cargo install cross --git https://github.com/cross-rs/cross + - name: Clean + run: cargo clean + - name: Build binary (Linux) if: matrix.os == 'ubuntu' run: cross build --release --target ${{ matrix.target }} @@ -158,6 +159,66 @@ jobs: path: artifacts/ if-no-files-found: error + appimage: + runs-on: ubuntu-latest + container: + image: debian:latest + options: --privileged + needs: deb + strategy: + fail-fast: true + matrix: + include: + - arch: amd64 + appimgarch: x86_64 + - arch: arm64 + appimgarch: aarch64 + - arch: i386 + appimgarch: i686 + - arch: armhf + appimgarch: armhf + + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: apt-get update -y && apt-get install -y git build-essential graphicsmagick-imagemagick-compat wget file desktop-file-utils libfuse2 + + - name: Download Debian package + uses: actions/download-artifact@v4 + with: + name: deb-${{ matrix.arch }} + path: /target/ + + - name: Download pkg2appimage + shell: bash + run: git clone https://github.com/AppImageCommunity/pkg2appimage.git + + - name: Replace placeholders. pkg2appimage bundled implementation of apt update does not allow download of packages for different architectures by default. Override this. + shell: bash + run: | + sed -i -e 's/REPLACE_TAG/Sniffnet_LinuxDEB_${{ matrix.arch }}.deb/g' resources/packaging/linux/AppImage/sniffnet.yml + sed -i -e 's/binary-${arch}/binary-${{ matrix.arch }}/g' pkg2appimage/functions.sh + + - name: Repackage for AppImage + shell: bash + run: | + cd pkg2appimage + ARCH=${{ matrix.appimgarch }} FUNCTIONS_SH=$(pwd)/functions.sh ./pkg2appimage ../resources/packaging/linux/AppImage/sniffnet.yml + mkdir /out + mv out/*.AppImage /out/Sniffnet_LinuxAppImage_${{ matrix.arch }}.AppImage + ls -lah /out + pwd + + - name: Upload package artifacts + uses: actions/upload-artifact@v4 + with: + name: appimage-${{ matrix.arch }} + path: /out/ + if-no-files-found: error + rpm: runs-on: ubuntu-latest container: diff --git a/resources/packaging/linux/AppImage/sniffnet.yml b/resources/packaging/linux/AppImage/sniffnet.yml new file mode 100644 index 00000000..da04ab96 --- /dev/null +++ b/resources/packaging/linux/AppImage/sniffnet.yml @@ -0,0 +1,9 @@ +app: Sniffnet +ingredients: + dist: bookworm + packages: + - libpcap0.8 + sources: + - deb https://deb.debian.org/debian stable main + debs: + - /target/REPLACE_TAG From 3134aa5187fe521e434a4b29deccf01da00eb51e Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 1 Aug 2025 12:47:57 +0200 Subject: [PATCH 14/74] update deps --- Cargo.lock | 40 ++++++++++++++++++++-------------------- Cargo.toml | 4 ++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd0efb42..25c7df25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -634,9 +634,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", "clap_derive", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstream", "anstyle", @@ -1111,7 +1111,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", + "redox_users 0.5.2", "windows-sys 0.60.2", ] @@ -2605,9 +2605,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", @@ -4042,9 +4042,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -4311,9 +4311,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "ring", @@ -4504,9 +4504,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -4740,7 +4740,7 @@ dependencies = [ "serial_test", "splines", "tokio", - "toml 0.9.3", + "toml 0.9.4", "winres", ] @@ -5214,9 +5214,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e06723639aaded957e5a80be250c1f82f274b9d23ebb4d94163668470623461c" +checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" dependencies = [ "indexmap", "serde", @@ -5729,9 +5729,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -5742,9 +5742,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ "bitflags 2.9.1", "wayland-backend", diff --git a/Cargo.toml b/Cargo.toml index 65b430bd..0df024df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,13 +48,13 @@ confy = "1.0.0" serde = { version = "1.0.219", default-features = false, features = ["derive"] } rodio = { version = "0.21.1", default-features = false, features = ["mp3", "playback"] } dns-lookup = "2.0.4" -toml = "0.9.3" +toml = "0.9.4" ctrlc = { version = "3.4.7", features = ["termination"] } rfd = "0.15.4" phf = "0.12.1" phf_shared = "0.12.1" splines = "5.0.0" -clap = { version = "4.5.41", features = ["derive"] } +clap = { version = "4.5.42", features = ["derive"] } tokio = { version = "1.47.0", features = ["macros"] } async-channel = "2.5.0" From 74abc3ad8bbdc750de5d2298e5bb3e95e898d626 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 2 Aug 2025 16:20:36 +0200 Subject: [PATCH 15/74] add missing characters --- resources/fonts/full/subset_characters.txt | 18 ++++++++++++++++++ .../subset/sarasa-mono-sc-bold.subset.ttf | Bin 235956 -> 240972 bytes .../subset/sarasa-mono-sc-regular.subset.ttf | Bin 238796 -> 243840 bytes 3 files changed, 18 insertions(+) diff --git a/resources/fonts/full/subset_characters.txt b/resources/fonts/full/subset_characters.txt index 33c02891..9e06465c 100644 --- a/resources/fonts/full/subset_characters.txt +++ b/resources/fonts/full/subset_characters.txt @@ -352,6 +352,7 @@ z 」 あ い +え お か が @@ -454,6 +455,7 @@ z 义 也 了 +予 互 些 交 @@ -486,6 +488,7 @@ z 値 值 偏 +停 傳 僅 元 @@ -498,6 +501,7 @@ z 关 其 内 +再 出 击 分 @@ -525,6 +529,7 @@ z 原 参 參 +反 发 取 受 @@ -564,6 +569,7 @@ z 士 変 复 +外 多 夢 夹 @@ -608,6 +614,7 @@ z 從 志 总 +恢 息 您 情 @@ -695,9 +702,11 @@ z 檔 檢 欢 +止 正 此 每 +比 気 沒 没 @@ -711,6 +720,7 @@ z 深 淺 清 +済 渐 測 源 @@ -721,6 +731,7 @@ z 為 無 版 +特 率 現 生 @@ -754,6 +765,7 @@ z 篩 类 紀 +約 索 累 細 @@ -785,6 +797,7 @@ z 處 號 表 +被 裡 製 複 @@ -805,6 +818,7 @@ z 認 語 誤 +読 調 請 计 @@ -815,6 +829,7 @@ z 语 误 请 +读 資 贴 超 @@ -823,6 +838,7 @@ z 転 輸 输 +込 达 过 近 @@ -873,8 +889,10 @@ z 顯 页 项 +预 题 饋 +馈 體 黑 가 diff --git a/resources/fonts/subset/sarasa-mono-sc-bold.subset.ttf b/resources/fonts/subset/sarasa-mono-sc-bold.subset.ttf index 0e28e1ab70f7a19a0bf4635c5e7e025fc18ec51b..096c63618afd22c828abc0691c1e4e9226e7dc63 100644 GIT binary patch delta 16223 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 KWL&gL1pFT@K;GB@ delta 11455 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 A^8f$< From 3764788fbb2f1ab36c923bdc2a69c525ed132f92 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 2 Aug 2025 16:27:48 +0200 Subject: [PATCH 16/74] translations update: ZH, JA, FR --- CHANGELOG.md | 3 +++ src/translations/types/language.rs | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f31cef..f6be2a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ ## [UNRELEASED] - Updated some of the existing translations to v1.4: - German ([#833](https://github.com/GyulyVGC/sniffnet/pull/833)) - Uzbek ([#834](https://github.com/GyulyVGC/sniffnet/pull/834)) + - Simplified Chinese ([#838](https://github.com/GyulyVGC/sniffnet/pull/838)) + - Japanese ([#849](https://github.com/GyulyVGC/sniffnet/pull/849)) + - French ([#864](https://github.com/GyulyVGC/sniffnet/pull/864)) ## [1.4.0] - 2025-06-27 - Import PCAP files ([#795](https://github.com/GyulyVGC/sniffnet/pull/795) — fixes [#283](https://github.com/GyulyVGC/sniffnet/issues/283)) diff --git a/src/translations/types/language.rs b/src/translations/types/language.rs index 6092f077..5d8bc77a 100644 --- a/src/translations/types/language.rs +++ b/src/translations/types/language.rs @@ -121,7 +121,14 @@ pub fn get_flag<'a>(self) -> Svg<'a, StyleType> { pub fn is_up_to_date(self) -> bool { matches!( self, - Language::EN | Language::IT | Language::NL | Language::DE | Language::UZ + Language::EN + | Language::IT + | Language::NL + | Language::DE + | Language::UZ + | Language::ZH + | Language::JA + | Language::FR ) } } From 91e0c109ae16ec5604c0d21a793c1f3d32b85e90 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 2 Aug 2025 16:43:55 +0200 Subject: [PATCH 17/74] minor fixes --- src/translations/translations_4.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index bc136124..46319c56 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -91,13 +91,13 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text ), Language::IT => format!( "Lettura pacchetti da file...\n\n\ - {file_name_translation}: {file}\n\n\ - Sei sicuro che il file che hai selezionato non sia vuoto?" + {file_name_translation}: {file}\n\n\ + Sei sicuro che il file che hai selezionato non sia vuoto?" ), Language::FR => format!( "Lecture des paquets depuis le fichier...\n\n\ - {file_name_translation} : {file}\n\n\ - Êtes-vous sûr que le fichier sélectionné n'est pas vide ?" + {file_name_translation}: {file}\n\n\ + Êtes-vous sûr que le fichier sélectionné n'est pas vide?" ), Language::JA => format!( "ファイルからパケットを読み込み中...\n\n\ @@ -126,8 +126,8 @@ pub fn reading_from_pcap_translation<'a>(language: Language, file: &str) -> Text ), _ => format!( "Reading packets from file...\n\n\ - {file_name_translation}: {file}\n\n\ - Are you sure the file you selected isn't empty?" + {file_name_translation}: {file}\n\n\ + Are you sure the file you selected isn't empty?" ), }) } From 46278fca180960172ba323eb5c8c1a9116267f31 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 2 Aug 2025 16:50:28 +0200 Subject: [PATCH 18/74] docs: add 18601673727 as a contributor for translation (#916) * docs: update CONTRIBUTORS.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index e2c07230..60c40c4e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -730,6 +730,15 @@ "contributions": [ "code" ] + }, + { + "login": "18601673727", + "name": "18601673727", + "avatar_url": "https://avatars.githubusercontent.com/u/3302620?v=4", + "profile": "https://github.com/18601673727", + "contributions": [ + "translation" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e31820db..d9abfc73 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -6,105 +6,106 @@ + - + - + - + - + - + - + - + - + - + - + - + From ed148d20c0555b68d951a83e170b061c356bb7e8 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 2 Aug 2025 16:51:24 +0200 Subject: [PATCH 19/74] docs: add shu-kitamura as a contributor for translation (#917) * docs: update CONTRIBUTORS.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 3 ++- CONTRIBUTORS.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 60c40c4e..a8a3a98b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -601,7 +601,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/171437458?v=4", "profile": "https://github.com/shu-kitamura", "contributions": [ - "code" + "code", + "translation" ] }, { diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d9abfc73..66fa40bb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -100,7 +100,7 @@ - + From 4b1be79124943d34622628422634f35059d8a605 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 2 Aug 2025 16:56:28 +0200 Subject: [PATCH 20/74] docs: add lionrayonnant as a contributor for translation (#918) * docs: update CONTRIBUTORS.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Giuliano Bellini <100347457+GyulyVGC@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index a8a3a98b..8232ba08 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -732,6 +732,15 @@ "code" ] }, + { + "login": "lionrayonnant", + "name": "Lion Rayonnant", + "avatar_url": "https://avatars.githubusercontent.com/u/106342136?v=4", + "profile": "https://github.com/lionrayonnant", + "contributions": [ + "translation" + ] + }, { "login": "18601673727", "name": "18601673727", diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 66fa40bb..9dfb6d1c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -54,6 +54,7 @@ + From e533fa77c38f11467a2d7ae3f99466533c290173 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 2 Aug 2025 17:12:30 +0200 Subject: [PATCH 21/74] fix CONTRIBUTORS.md --- .gitignore | 3 +++ CONTRIBUTORS.md | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index fe36f504..bc65973c 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ $RECYCLE.BIN/ ### Custom... ### lcov.info *.pcap +node_modules +package.json +yarn.lock diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9dfb6d1c..64591891 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -58,54 +58,54 @@ - + - + - + - + - + - + From 49776645bcd2afaa2eebc809cbb29e9e779ad9cd Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 3 Aug 2025 15:47:13 +0200 Subject: [PATCH 22/74] revert edits to cargo clean command in CI/CD --- .github/workflows/package.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index a7977bf1..884214bb 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -81,15 +81,14 @@ jobs: - name: Test (release mode) if: matrix.os == 'macos' || matrix.os == 'ubuntu' || matrix.os == 'windows' && matrix.arch == 'amd64' - run: cargo test --release --verbose -- --nocapture && + run: | + cargo test --release --verbose -- --nocapture && + cargo clean - name: Install Cross if: matrix.os == 'ubuntu' run: cargo install cross --git https://github.com/cross-rs/cross - - name: Clean - run: cargo clean - - name: Build binary (Linux) if: matrix.os == 'ubuntu' run: cross build --release --target ${{ matrix.target }} From d6adb759212b811e2fc423041856c511fb28aaaa Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 3 Aug 2025 16:39:14 +0200 Subject: [PATCH 23/74] update CHANGELOG --- .github/workflows/package.yml | 5 +---- CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 884214bb..4430354a 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -177,7 +177,6 @@ jobs: - arch: armhf appimgarch: armhf - steps: - name: Checkout repository uses: actions/checkout@v4 @@ -195,7 +194,7 @@ jobs: shell: bash run: git clone https://github.com/AppImageCommunity/pkg2appimage.git - - name: Replace placeholders. pkg2appimage bundled implementation of apt update does not allow download of packages for different architectures by default. Override this. + - name: Replace placeholders shell: bash run: | sed -i -e 's/REPLACE_TAG/Sniffnet_LinuxDEB_${{ matrix.arch }}.deb/g' resources/packaging/linux/AppImage/sniffnet.yml @@ -208,8 +207,6 @@ jobs: ARCH=${{ matrix.appimgarch }} FUNCTIONS_SH=$(pwd)/functions.sh ./pkg2appimage ../resources/packaging/linux/AppImage/sniffnet.yml mkdir /out mv out/*.AppImage /out/Sniffnet_LinuxAppImage_${{ matrix.arch }}.AppImage - ls -lah /out - pwd - name: Upload package artifacts uses: actions/upload-artifact@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f31cef..77167c41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # Changelog All Sniffnet releases with the relative changes are documented in this file. ## [UNRELEASED] +- An AppImage of Sniffnet is now available ([#859](https://github.com/GyulyVGC/sniffnet/pull/859) — fixes [#900](https://github.com/GyulyVGC/sniffnet/issues/900)) - Added Dutch translation 🇳🇱 ([#854](https://github.com/GyulyVGC/sniffnet/pull/854)) - The Windows Installer is now signed with a code signing certificate provided by the [SignPath Foundation](https://signpath.org/) ([#897](https://github.com/GyulyVGC/sniffnet/pull/897) — fixes [#894](https://github.com/GyulyVGC/sniffnet/issues/894)) - Updated some of the existing translations to v1.4: From 1e433ee1b9fc4377e6e85c9a9a54e6bdba30c63d Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 16:43:16 +0200 Subject: [PATCH 24/74] docs: add AlleM43 as a contributor for platform (#919) * docs: update CONTRIBUTORS.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 21 +++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8232ba08..e69e69f4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -749,6 +749,15 @@ "contributions": [ "translation" ] + }, + { + "login": "AlleM43", + "name": "AlleM43", + "avatar_url": "https://avatars.githubusercontent.com/u/16104173?v=4", + "profile": "https://github.com/AlleM43", + "contributions": [ + "platform" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 64591891..0be7fc41 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -15,96 +15,97 @@ + - + - + - + - + - + - + - + - + - + - + From 1cf21e6fc21495e7228afca05fa80b730ff890dc Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 3 Aug 2025 17:39:07 +0200 Subject: [PATCH 25/74] improve Docker CI/CD to exit if an image already exists for the current version --- .github/workflows/docker.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fd513caa..7427a43b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,9 +1,6 @@ name: Docker on: - # push: - # tags: - # - 'v[0-9]+.[0-9]+.[0-9]+' workflow_dispatch: jobs: @@ -21,6 +18,13 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_ENV echo "version=$VERSION" >> $GITHUB_OUTPUT + - name: Check there is no existing image with the same version + run: | + if docker manifest inspect ghcr.io/gyulyvgc/sniffnet:${{ env.VERSION }}; then + echo "Image with version ${{ env.VERSION }} already exists" + exit 1 + fi + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -43,4 +47,3 @@ jobs: tags: | ghcr.io/gyulyvgc/sniffnet:latest ghcr.io/gyulyvgc/sniffnet:${{ env.VERSION }} -# ghcr.io/gyulyvgc/sniffnet:${{ github.ref_name }} From 39c66e9ab158043335c1ed0154ef0fe15defc280 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 4 Aug 2025 17:54:46 +0200 Subject: [PATCH 26/74] update README's Download section --- README.md | 8 +++++--- resources/repository/badges/linux.svg | 13 +++++++++++++ resources/repository/badges/linux_deb.svg | 1 - resources/repository/badges/linux_rpm.svg | 1 - resources/repository/badges/macos.svg | 14 +++++++++++++- resources/repository/badges/windows.svg | 14 +++++++++++++- 6 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 resources/repository/badges/linux.svg delete mode 100644 resources/repository/badges/linux_deb.svg delete mode 100644 resources/repository/badges/linux_rpm.svg diff --git a/README.md b/README.md index 8b31066d..7294e6d2 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,11 @@ ## _Support Sniffnet's development_ 💖 ## Download -| Windows | macOS | Linux (.deb) | Linux (.rpm) | -|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| -|         [64‑bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_64-bit.msi) \| [32‑bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_32-bit.msi)         | [Intel](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_Intel.dmg) \| [Apple silicon](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_AppleSilicon.dmg) | [amd64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_amd64.deb) \| [arm64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_arm64.deb) \| [i386](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_i386.deb) \| [armhf](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_armhf.deb) |         [x86_64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_x86_64.rpm) \| [aarch64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_aarch64.rpm)         | +| _Operating System_ | _Download links_ | +|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Windows | [64-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_64-bit.msi) \| [32-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_32-bit.msi) | +| macOS | [Intel](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_Intel.dmg) \| [Apple silicon](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_AppleSilicon.dmg) | +| Linux | DEB: [amd64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_amd64.deb) \| [arm64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_arm64.deb) \| [i386](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_i386.deb) \| [armhf](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_armhf.deb)
RPM: [x86_64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_x86_64.rpm) \| [aarch64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_aarch64.rpm) | Links in the table above will download the latest version of Sniffnet directly from [GitHub releases](https://github.com/GyulyVGC/sniffnet/releases).
Not what you're looking for? Check out [alternative installation methods](https://github.com/GyulyVGC/sniffnet/wiki/Alternative-installation-methods). diff --git a/resources/repository/badges/linux.svg b/resources/repository/badges/linux.svg new file mode 100644 index 00000000..8b294af5 --- /dev/null +++ b/resources/repository/badges/linux.svg @@ -0,0 +1,13 @@ + + Linux + + + + + + Linux + + \ No newline at end of file diff --git a/resources/repository/badges/linux_deb.svg b/resources/repository/badges/linux_deb.svg deleted file mode 100644 index ee254170..00000000 --- a/resources/repository/badges/linux_deb.svg +++ /dev/null @@ -1 +0,0 @@ -LINUX (.DEB)LINUX (.DEB) \ No newline at end of file diff --git a/resources/repository/badges/linux_rpm.svg b/resources/repository/badges/linux_rpm.svg deleted file mode 100644 index b5688b2a..00000000 --- a/resources/repository/badges/linux_rpm.svg +++ /dev/null @@ -1 +0,0 @@ -LINUX (.RPM)LINUX (.RPM) \ No newline at end of file diff --git a/resources/repository/badges/macos.svg b/resources/repository/badges/macos.svg index 865f992c..ab25399d 100644 --- a/resources/repository/badges/macos.svg +++ b/resources/repository/badges/macos.svg @@ -1 +1,13 @@ -MACOSMACOS \ No newline at end of file + + macOS + + + + + + macOS + + \ No newline at end of file diff --git a/resources/repository/badges/windows.svg b/resources/repository/badges/windows.svg index e877fa7c..d3329f3a 100644 --- a/resources/repository/badges/windows.svg +++ b/resources/repository/badges/windows.svg @@ -1 +1,13 @@ -WINDOWSWINDOWS \ No newline at end of file + + Windows + + + + + + Windows + + \ No newline at end of file From 59df42d921fef404c49f3c35ff11c5791ca3443d Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 4 Aug 2025 18:18:59 +0200 Subject: [PATCH 27/74] update README's Download section --- resources/repository/badges/linux.svg | 2 +- resources/repository/badges/macos.svg | 2 +- resources/repository/badges/windows.svg | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/repository/badges/linux.svg b/resources/repository/badges/linux.svg index 8b294af5..7b1aa388 100644 --- a/resources/repository/badges/linux.svg +++ b/resources/repository/badges/linux.svg @@ -2,7 +2,7 @@ aria-label="Linux"> Linux - + diff --git a/resources/repository/badges/macos.svg b/resources/repository/badges/macos.svg index ab25399d..4ee39fff 100644 --- a/resources/repository/badges/macos.svg +++ b/resources/repository/badges/macos.svg @@ -2,7 +2,7 @@ aria-label="macOS"> macOS - + diff --git a/resources/repository/badges/windows.svg b/resources/repository/badges/windows.svg index d3329f3a..b2050534 100644 --- a/resources/repository/badges/windows.svg +++ b/resources/repository/badges/windows.svg @@ -2,11 +2,11 @@ aria-label="Windows"> Windows - + - Windows From c36c35cefd3ecab0d6b0e6dfba60a3e8f1efd429 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Tue, 5 Aug 2025 17:39:06 +0200 Subject: [PATCH 28/74] add giscus.json --- giscus.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 giscus.json diff --git a/giscus.json b/giscus.json new file mode 100644 index 00000000..80f1a31a --- /dev/null +++ b/giscus.json @@ -0,0 +1,5 @@ +{ + "origins": ["https://sniffnet.net"], + "originsRegex": ["http://localhost:[0-9]+"], + "defaultCommentOrder": "oldest" +} \ No newline at end of file From 2ec52e38826cab9417e373487b188e61e911bd39 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Tue, 5 Aug 2025 22:52:34 +0200 Subject: [PATCH 29/74] update deps --- Cargo.lock | 83 ++++++++++++--------------- Cargo.toml | 8 +-- resources/repository/badges/linux.svg | 4 +- resources/repository/badges/macos.svg | 4 +- src/networking/types/icmp_type.rs | 10 ++-- 5 files changed, 50 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25c7df25..3b84f68c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -174,22 +174,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -319,9 +319,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener", "event-listener-strategy", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.30" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -1161,14 +1161,14 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "dns-lookup" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" +checksum = "91adf1f5ae09290d87cca8f4f0a8e49bcc30672993eb8aa11a5c9d8872d16a98" dependencies = [ "cfg-if", "libc", - "socket2 0.5.10", - "windows-sys 0.48.0", + "socket2 0.6.0", + "windows-sys 0.60.2", ] [[package]] @@ -1325,9 +1325,9 @@ dependencies = [ [[package]] name = "etherparse" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a48e7bdc36cdf86876a6890c0840957029374ea07f6697c96fa15898730375" +checksum = "b119b9796ff800751a220394b8b3613f26dd30c48f254f6837e64c464872d1c7" dependencies = [ "arrayvec", ] @@ -1343,9 +1343,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1591,9 +1591,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -3728,9 +3728,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", @@ -4606,9 +4606,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -4740,7 +4740,7 @@ dependencies = [ "serial_test", "splines", "tokio", - "toml 0.9.4", + "toml 0.9.5", "winres", ] @@ -5131,9 +5131,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -5180,9 +5180,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -5214,9 +5214,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ "indexmap", "serde", @@ -5261,9 +5261,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ "winnow", ] @@ -6109,15 +6109,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -6815,9 +6806,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 0df024df..603aba78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ strip = true [dependencies] pcap = "2.3.0" -etherparse = "0.18.2" +etherparse = "0.19.0" chrono = { version = "0.4.41", default-features = false, features = ["clock"] } plotters = { version = "0.3.7", default-features = false, features = ["area_series", "line_series"] } iced = { version = "0.13.1", features = ["tokio", "svg", "advanced", "lazy", "image"] } @@ -47,15 +47,15 @@ maxminddb = "0.26.0" confy = "1.0.0" serde = { version = "1.0.219", default-features = false, features = ["derive"] } rodio = { version = "0.21.1", default-features = false, features = ["mp3", "playback"] } -dns-lookup = "2.0.4" -toml = "0.9.4" +dns-lookup = "2.1.0" +toml = "0.9.5" ctrlc = { version = "3.4.7", features = ["termination"] } rfd = "0.15.4" phf = "0.12.1" phf_shared = "0.12.1" splines = "5.0.0" clap = { version = "4.5.42", features = ["derive"] } -tokio = { version = "1.47.0", features = ["macros"] } +tokio = { version = "1.47.1", features = ["macros"] } async-channel = "2.5.0" [target.'cfg(windows)'.dependencies] diff --git a/resources/repository/badges/linux.svg b/resources/repository/badges/linux.svg index 7b1aa388..c5aefb37 100644 --- a/resources/repository/badges/linux.svg +++ b/resources/repository/badges/linux.svg @@ -6,8 +6,8 @@ - - Linux + Linux \ No newline at end of file diff --git a/resources/repository/badges/macos.svg b/resources/repository/badges/macos.svg index 4ee39fff..236b51ae 100644 --- a/resources/repository/badges/macos.svg +++ b/resources/repository/badges/macos.svg @@ -6,8 +6,8 @@ - - macOS + macOS \ No newline at end of file diff --git a/src/networking/types/icmp_type.rs b/src/networking/types/icmp_type.rs index ddb2c31f..6859ab6d 100644 --- a/src/networking/types/icmp_type.rs +++ b/src/networking/types/icmp_type.rs @@ -216,15 +216,15 @@ pub fn from_etherparse(icmpv6type: &Icmpv6Type) -> IcmpType { Icmpv6Type::ParameterProblem(_) => Self::ParameterProblem, Icmpv6Type::EchoRequest(_) => Self::EchoRequest, Icmpv6Type::EchoReply(_) => Self::EchoReply, + Icmpv6Type::RouterSolicitation => Self::RouterSolicitation, + Icmpv6Type::RouterAdvertisement(_) => Self::RouterAdvertisement, + Icmpv6Type::NeighborSolicitation => Self::NeighborSolicitation, + Icmpv6Type::NeighborAdvertisement(_) => Self::NeighborAdvertisement, + Icmpv6Type::Redirect => Self::RedirectMessage, Icmpv6Type::Unknown { type_u8: id, .. } => match id { 130 => Self::MulticastListenerQuery, 131 => Self::MulticastListenerReport, 132 => Self::MulticastListenerDone, - 133 => Self::RouterSolicitation, - 134 => Self::RouterAdvertisement, - 135 => Self::NeighborSolicitation, - 136 => Self::NeighborAdvertisement, - 137 => Self::RedirectMessage, 138 => Self::RouterRenumbering, 139 => Self::ICMPNodeInformationQuery, 140 => Self::ICMPNodeInformationResponse, From 4ea02209ec7ab61cd91e6776a09cace116732028 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 6 Aug 2025 13:56:40 +0200 Subject: [PATCH 30/74] update README's Download section --- README.md | 6 +++--- resources/repository/badges/linux.svg | 10 +++++----- resources/repository/badges/macos.svg | 8 ++++---- resources/repository/badges/windows.svg | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 7294e6d2..ede2317d 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,9 @@ ## Download | _Operating System_ | _Download links_ | |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [64-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_64-bit.msi) \| [32-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_32-bit.msi) | -| macOS | [Intel](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_Intel.dmg) \| [Apple silicon](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_AppleSilicon.dmg) | -| Linux | DEB: [amd64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_amd64.deb) \| [arm64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_arm64.deb) \| [i386](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_i386.deb) \| [armhf](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_armhf.deb)
RPM: [x86_64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_x86_64.rpm) \| [aarch64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_aarch64.rpm) | +| Windows | [64-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_64-bit.msi) \| [32-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_32-bit.msi) | +| macOS | [Intel](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_Intel.dmg) \| [Apple silicon](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_AppleSilicon.dmg) | +| Linux | DEB: [amd64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_amd64.deb) \| [arm64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_arm64.deb) \| [i386](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_i386.deb) \| [armhf](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_armhf.deb)
RPM: [x86_64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_x86_64.rpm) \| [aarch64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_aarch64.rpm) | Links in the table above will download the latest version of Sniffnet directly from [GitHub releases](https://github.com/GyulyVGC/sniffnet/releases).
Not what you're looking for? Check out [alternative installation methods](https://github.com/GyulyVGC/sniffnet/wiki/Alternative-installation-methods). diff --git a/resources/repository/badges/linux.svg b/resources/repository/badges/linux.svg index c5aefb37..51830469 100644 --- a/resources/repository/badges/linux.svg +++ b/resources/repository/badges/linux.svg @@ -1,13 +1,13 @@ - Linux - + - - Linux + + Linux \ No newline at end of file diff --git a/resources/repository/badges/macos.svg b/resources/repository/badges/macos.svg index 236b51ae..f046a790 100644 --- a/resources/repository/badges/macos.svg +++ b/resources/repository/badges/macos.svg @@ -1,13 +1,13 @@ - macOS - + - - macOS + macOS \ No newline at end of file diff --git a/resources/repository/badges/windows.svg b/resources/repository/badges/windows.svg index b2050534..40c1eacd 100644 --- a/resources/repository/badges/windows.svg +++ b/resources/repository/badges/windows.svg @@ -1,13 +1,13 @@ - Windows - + - - Windows + + Windows \ No newline at end of file From 04cdea9dfe7dabf04ef7d887290ae1f57af377cc Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 6 Aug 2025 14:15:39 +0200 Subject: [PATCH 31/74] update README's Download section --- README.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ede2317d..b3e95376 100644 --- a/README.md +++ b/README.md @@ -59,11 +59,33 @@ ## _Support Sniffnet's development_ 💖 ## Download -| _Operating System_ | _Download links_ | -|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [64-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_64-bit.msi) \| [32-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_32-bit.msi) | -| macOS | [Intel](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_Intel.dmg) \| [Apple silicon](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_AppleSilicon.dmg) | -| Linux | DEB: [amd64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_amd64.deb) \| [arm64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_arm64.deb) \| [i386](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_i386.deb) \| [armhf](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_armhf.deb)
RPM: [x86_64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_x86_64.rpm) \| [aarch64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_aarch64.rpm) | +
18601673727
18601673727

🌍
ADS Fund
ADS Fund

💵
Abdullah
Abdullah

🤔 🖋
Adriano
Adriano

🤔
Aguacero 🌧️
Aguacero 🌧️

🌍
Ahmet Kaan GÜMÜŞ
Ahmet Kaan GÜMÜŞ

🌍
Alexandr Shashkin
Alexandr Shashkin

🐛
AmadeusGraves
AmadeusGraves

🌍
AmadeusGraves
AmadeusGraves

🌍
Angelos Bousis
Angelos Bousis

🌍
Antoine Colombier
Antoine Colombier

⚠️ 🌍
BugsQuanti
BugsQuanti

🌍
Charpy
Charpy

🌍
Christoph Wanja
Christoph Wanja

💵
Colin Delahunty
Colin Delahunty

⚠️
Cornelius Roemer
Cornelius Roemer

🤔
Cornelius Roemer
Cornelius Roemer

🤔
CosminPerRam
CosminPerRam

💻
Cristiano
Cristiano

💻 🤔
Cthulu201
Cthulu201

💵
Dinar Shagaliev
Dinar Shagaliev

🌍
Dominic Kim
Dominic Kim

🌍
Echo
Echo

💵
Embers-of-the-Fire
Embers-of-the-Fire

🌍
Embers-of-the-Fire
Embers-of-the-Fire

🌍
Francisco Salgueiro
Francisco Salgueiro

🌍
GNUser
GNUser

📖 📦
George Shuklin
George Shuklin

🌍
Giusy Digital
Giusy Digital

🐛
Hiroki Tagato
Hiroki Tagato

📦
Hubert
Hubert

🌍
Hüseyin Fahri Uzun
Hüseyin Fahri Uzun

🌍
Hüseyin Fahri Uzun
Hüseyin Fahri Uzun

🌍
IPinfo
IPinfo

💵
Ilmi2
Ilmi2

💵
Jan Walter
Jan Walter

💵
Jauder Ho
Jauder Ho

🚇
Joshua Megnauth
Joshua Megnauth

💻 🎨
Julian Schmid
Julian Schmid

💻 🤔
LiChenG-P
LiChenG-P

💻
LiChenG-P
LiChenG-P

💻
Liam OBrien
Liam OBrien

🤔
Limdongju
Limdongju

🌍
Ludwig Stecher
Ludwig Stecher

🤔 💻
Marc Gavilán
Marc Gavilán

🌍
Marco Cadetg
Marco Cadetg

📦
Matthias Braun
Matthias Braun

📖
Michel Hansma
Michel Hansma

🎨 ️️️️♿️
Michel Hansma
Michel Hansma

🎨 ️️️️♿️
Morgan Hill
Morgan Hill

🛡️
Muhammadali Hakimov
Muhammadali Hakimov

🌍
Nubi
Nubi

🌍
Oleksii Filonenko
Oleksii Filonenko

🌍
Orhun Parmaksız
Orhun Parmaksız

📖 📦 💵
Peter Dave Hello
Peter Dave Hello

🌍
Phil Clifford
Phil Clifford

📦
Phil Clifford
Phil Clifford

📦
Quetzal-coalt
Quetzal-coalt

🌍
Ron
Ron

🤔
Safaraliev Maxim
Safaraliev Maxim

🌍
Shawn Yeager
Shawn Yeager

💵
SignPath GmbH
SignPath GmbH

📦
The Artifex
The Artifex

🌍 📦
Trịnh Duy Hưng
Trịnh Duy Hưng

🌍
Trịnh Duy Hưng
Trịnh Duy Hưng

🌍
TyseEX
TyseEX

🐛
Victor Nilsson
Victor Nilsson

🌍 💻
Wang Zishi
Wang Zishi

🌍
Yevhen
Yevhen

🌍
Ylva
Ylva

🌍
ZEROF
ZEROF

💵
ZeroDot1
ZeroDot1

🎨 ️️️️♿️
ZeroDot1
ZeroDot1

🎨 ️️️️♿️
clr
clr

📖 🌍
ervinpopescu
ervinpopescu

🌍
glitsj16
glitsj16

📦
guilherme-demarchi
guilherme-demarchi

🌍
hirotake111
hirotake111

🌍
islameehassan
islameehassan

💻
louis-ym4
louis-ym4

🎨
louis-ym4
louis-ym4

🎨
luca3s
luca3s

🌍
pia
pia

🌍
pin
pin

📦
shu-kitamura
shu-kitamura

💻
starccy
starccy

💻
tiansheng li
tiansheng li

💵
vtiinanen
vtiinanen

🌍
vtiinanen
vtiinanen

🌍
yossarian
yossarian

🌍
陈寒彤
陈寒彤

🌍
luca3s
luca3s

🌍
pia
pia

🌍
pin
pin

📦
shu-kitamura
shu-kitamura

💻
shu-kitamura
shu-kitamura

💻 🌍
starccy
starccy

💻
tiansheng li
tiansheng li

💵
LiChenG-P
LiChenG-P

💻
Liam OBrien
Liam OBrien

🤔
Limdongju
Limdongju

🌍
Lion Rayonnant
Lion Rayonnant

🌍
Ludwig Stecher
Ludwig Stecher

🤔 💻
Marc Gavilán
Marc Gavilán

🌍
Marco Cadetg
Marco Cadetg

📦
Ludwig Stecher
Ludwig Stecher

🤔 💻
Marc Gavilán
Marc Gavilán

🌍
Marco Cadetg
Marco Cadetg

📦
Matthias Braun
Matthias Braun

📖
Matthias Braun
Matthias Braun

📖
Michel Hansma
Michel Hansma

🎨 ️️️️♿️
Morgan Hill
Morgan Hill

🛡️
Muhammadali Hakimov
Muhammadali Hakimov

🌍
Nubi
Nubi

🌍
Oleksii Filonenko
Oleksii Filonenko

🌍
Orhun Parmaksız
Orhun Parmaksız

📖 📦 💵
Peter Dave Hello
Peter Dave Hello

🌍
Peter Dave Hello
Peter Dave Hello

🌍
Phil Clifford
Phil Clifford

📦
Quetzal-coalt
Quetzal-coalt

🌍
Ron
Ron

🤔
Safaraliev Maxim
Safaraliev Maxim

🌍
Shawn Yeager
Shawn Yeager

💵
SignPath GmbH
SignPath GmbH

📦
The Artifex
The Artifex

🌍 📦
The Artifex
The Artifex

🌍 📦
Trịnh Duy Hưng
Trịnh Duy Hưng

🌍
TyseEX
TyseEX

🐛
Victor Nilsson
Victor Nilsson

🌍 💻
Wang Zishi
Wang Zishi

🌍
Yevhen
Yevhen

🌍
Ylva
Ylva

🌍
ZEROF
ZEROF

💵
ZEROF
ZEROF

💵
ZeroDot1
ZeroDot1

🎨 ️️️️♿️
clr
clr

📖 🌍
ervinpopescu
ervinpopescu

🌍
glitsj16
glitsj16

📦
guilherme-demarchi
guilherme-demarchi

🌍
hirotake111
hirotake111

🌍
islameehassan
islameehassan

💻
islameehassan
islameehassan

💻
louis-ym4
louis-ym4

🎨
luca3s
luca3s

🌍
pia
pia

🌍
pin
pin

📦
shu-kitamura
shu-kitamura

💻 🌍
starccy
starccy

💻
tiansheng li
tiansheng li

💵
tiansheng li
tiansheng li

💵
vtiinanen
vtiinanen

🌍
yossarian
yossarian

🌍
陈寒彤
陈寒彤

🌍
Alexandr Shashkin
Alexandr Shashkin

🐛
AlleM43
AlleM43

📦
AmadeusGraves
AmadeusGraves

🌍
Angelos Bousis
Angelos Bousis

🌍
Antoine Colombier
Antoine Colombier

⚠️ 🌍
BugsQuanti
BugsQuanti

🌍
Charpy
Charpy

🌍
Christoph Wanja
Christoph Wanja

💵
Colin Delahunty
Colin Delahunty

⚠️
Colin Delahunty
Colin Delahunty

⚠️
Cornelius Roemer
Cornelius Roemer

🤔
CosminPerRam
CosminPerRam

💻
Cristiano
Cristiano

💻 🤔
Cthulu201
Cthulu201

💵
Dinar Shagaliev
Dinar Shagaliev

🌍
Dominic Kim
Dominic Kim

🌍
Echo
Echo

💵
Echo
Echo

💵
Embers-of-the-Fire
Embers-of-the-Fire

🌍
Francisco Salgueiro
Francisco Salgueiro

🌍
GNUser
GNUser

📖 📦
George Shuklin
George Shuklin

🌍
Giusy Digital
Giusy Digital

🐛
Hiroki Tagato
Hiroki Tagato

📦
Hubert
Hubert

🌍
Hubert
Hubert

🌍
Hüseyin Fahri Uzun
Hüseyin Fahri Uzun

🌍
IPinfo
IPinfo

💵
Ilmi2
Ilmi2

💵
Jan Walter
Jan Walter

💵
Jauder Ho
Jauder Ho

🚇
Joshua Megnauth
Joshua Megnauth

💻 🎨
Julian Schmid
Julian Schmid

💻 🤔
Julian Schmid
Julian Schmid

💻 🤔
LiChenG-P
LiChenG-P

💻
Liam OBrien
Liam OBrien

🤔
Limdongju
Limdongju

🌍
Lion Rayonnant
Lion Rayonnant

🌍
Ludwig Stecher
Ludwig Stecher

🤔 💻
Marc Gavilán
Marc Gavilán

🌍
Marco Cadetg
Marco Cadetg

📦
Marco Cadetg
Marco Cadetg

📦
Matthias Braun
Matthias Braun

📖
Michel Hansma
Michel Hansma

🎨 ️️️️♿️
Morgan Hill
Morgan Hill

🛡️
Muhammadali Hakimov
Muhammadali Hakimov

🌍
Nubi
Nubi

🌍
Oleksii Filonenko
Oleksii Filonenko

🌍
Orhun Parmaksız
Orhun Parmaksız

📖 📦 💵
Orhun Parmaksız
Orhun Parmaksız

📖 📦 💵
Peter Dave Hello
Peter Dave Hello

🌍
Phil Clifford
Phil Clifford

📦
Quetzal-coalt
Quetzal-coalt

🌍
Ron
Ron

🤔
Safaraliev Maxim
Safaraliev Maxim

🌍
Shawn Yeager
Shawn Yeager

💵
SignPath GmbH
SignPath GmbH

📦
SignPath GmbH
SignPath GmbH

📦
The Artifex
The Artifex

🌍 📦
Trịnh Duy Hưng
Trịnh Duy Hưng

🌍
TyseEX
TyseEX

🐛
Victor Nilsson
Victor Nilsson

🌍 💻
Wang Zishi
Wang Zishi

🌍
Yevhen
Yevhen

🌍
Ylva
Ylva

🌍
Ylva
Ylva

🌍
ZEROF
ZEROF

💵
ZeroDot1
ZeroDot1

🎨 ️️️️♿️
clr
clr

📖 🌍
ervinpopescu
ervinpopescu

🌍
glitsj16
glitsj16

📦
guilherme-demarchi
guilherme-demarchi

🌍
hirotake111
hirotake111

🌍
hirotake111
hirotake111

🌍
islameehassan
islameehassan

💻
louis-ym4
louis-ym4

🎨
luca3s
luca3s

🌍
pia
pia

🌍
pin
pin

📦
shu-kitamura
shu-kitamura

💻 🌍
starccy
starccy

💻
starccy
starccy

💻
tiansheng li
tiansheng li

💵
vtiinanen
vtiinanen

🌍
yossarian
yossarian

🌍
+ + + + + + + + + + + + +
+ Windows + + 64-bit | 32-bit +
+ macOS + + Intel | Apple silicon +
+ Linux + + DEB: amd64 | arm64 | i386 | armhf
+ RPM: x86_64 | aarch64 +
Links in the table above will download the latest version of Sniffnet directly from [GitHub releases](https://github.com/GyulyVGC/sniffnet/releases).
Not what you're looking for? Check out [alternative installation methods](https://github.com/GyulyVGC/sniffnet/wiki/Alternative-installation-methods). From e50266e57fb700e67517b46c24f6d3c387d3db88 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Tue, 12 Aug 2025 11:52:51 +0200 Subject: [PATCH 32/74] update deps and fix new clippy lints --- Cargo.lock | 78 ++++++++++++------------ Cargo.toml | 4 +- src/chart/manage_chart_data.rs | 8 +-- src/chart/types/traffic_chart.rs | 2 +- src/gui/components/header.rs | 2 +- src/gui/components/tab.rs | 12 ++-- src/gui/pages/connection_details_page.rs | 2 +- src/gui/pages/initial_page.rs | 18 +++--- src/gui/pages/inspect_page.rs | 6 +- src/gui/pages/notifications_page.rs | 4 +- src/gui/pages/overview_page.rs | 4 +- src/gui/pages/settings_general_page.rs | 4 +- src/gui/pages/settings_style_page.rs | 2 +- src/gui/pages/thumbnail_page.rs | 2 +- src/gui/sniffer.rs | 2 +- src/networking/manage_packets.rs | 17 +++--- src/networking/parse_packets.rs | 2 +- src/networking/types/capture_context.rs | 2 +- src/translations/translations.rs | 2 +- 19 files changed, 89 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b84f68c..39ec831a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,18 +529,18 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.31" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "jobserver", "libc", @@ -634,9 +634,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" dependencies = [ "clap_builder", "clap_derive", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -791,7 +791,7 @@ checksum = "f29222b549d4e3ded127989d523da9e928918d0d0d7f7c1690b439d0d538bae9" dependencies = [ "directories", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "toml 0.8.23", ] @@ -1741,9 +1741,9 @@ checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "glow" @@ -1830,9 +1830,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -1878,9 +1878,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "hassle-rs" @@ -2395,7 +2395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -2573,9 +2573,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" @@ -2746,7 +2746,7 @@ dependencies = [ "log", "memchr", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -3781,9 +3781,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] @@ -3839,7 +3839,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tracing", "web-time", @@ -3860,7 +3860,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.14", "tinyvec", "tracing", "web-time", @@ -4048,7 +4048,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -4346,9 +4346,9 @@ dependencies = [ [[package]] name = "rustrict" -version = "0.7.35" +version = "0.7.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfe349049fa49baa564f8483d40e7561ff19ccaa308ab4f844bb59d2c5d8d34" +checksum = "d93719b9aa6a53f9beae62fe36f34ed88226be314aea6829031ed0f878ca493d" dependencies = [ "arrayvec", "bitflags 1.3.2", @@ -4362,9 +4362,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustybuzz" @@ -4646,9 +4646,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" @@ -5025,11 +5025,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -5045,9 +5045,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", @@ -6806,9 +6806,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 603aba78..266bc1f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ rfd = "0.15.4" phf = "0.12.1" phf_shared = "0.12.1" splines = "5.0.0" -clap = { version = "4.5.42", features = ["derive"] } +clap = { version = "4.5.44", features = ["derive"] } tokio = { version = "1.47.1", features = ["macros"] } async-channel = "2.5.0" @@ -79,7 +79,7 @@ serial_test = { version = "3.2.0", default-features = false } [build-dependencies] phf_codegen = "0.12.1" phf_shared = "0.12.1" -rustrict = { version = "0.7.35", default-features = false, features = ["censor"] } +rustrict = { version = "0.7.36", default-features = false, features = ["censor"] } [target."cfg(windows)".build-dependencies] winres = "0.1.12" diff --git a/src/chart/manage_chart_data.rs b/src/chart/manage_chart_data.rs index 27558ec3..aaaf219f 100644 --- a/src/chart/manage_chart_data.rs +++ b/src/chart/manage_chart_data.rs @@ -145,10 +145,10 @@ fn reduce_all_time_data(all_time: &mut Vec<(f32, f32)>) { while all_time.len() > 150 { let mut new_vec = Vec::new(); all_time.iter().enumerate().for_each(|(i, (x, y))| { - if i % 2 == 0 { - if let Some(next) = all_time.get(i + 1) { - new_vec.push((*x, (y + next.1) / 2.0)); - } + if i % 2 == 0 + && let Some(next) = all_time.get(i + 1) + { + new_vec.push((*x, (y + next.1) / 2.0)); } }); *all_time = new_vec; diff --git a/src/chart/types/traffic_chart.rs b/src/chart/types/traffic_chart.rs index 835ff54c..d9562e48 100644 --- a/src/chart/types/traffic_chart.rs +++ b/src/chart/types/traffic_chart.rs @@ -80,7 +80,7 @@ pub fn new(style: StyleType, language: Language) -> Self { } } - pub fn view(&self) -> Element { + pub fn view(&self) -> Element<'_, Message, StyleType> { let x_labels = if self.is_live_capture || self.thumbnail { None } else { diff --git a/src/gui/components/header.rs b/src/gui/components/header.rs index f90cca1e..af7ad3f0 100644 --- a/src/gui/components/header.rs +++ b/src/gui/components/header.rs @@ -19,7 +19,7 @@ use crate::utils::types::icon::Icon; use crate::{Language, SNIFFNET_TITLECASE, StyleType}; -pub fn header(sniffer: &Sniffer) -> Container { +pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let thumbnail = sniffer.thumbnail; let ConfigSettings { style, diff --git a/src/gui/components/tab.rs b/src/gui/components/tab.rs index a70bca4a..cbf5e033 100644 --- a/src/gui/components/tab.rs +++ b/src/gui/components/tab.rs @@ -99,12 +99,12 @@ fn new_page_tab<'a>( .align_y(alignment::Alignment::Center), ); - if let Some(num) = unread { - if num > 0 { - content = content - .push(Space::with_width(7)) - .push(notifications_badge(font_headers, num)); - } + if let Some(num) = unread + && num > 0 + { + content = content + .push(Space::with_width(7)) + .push(notifications_badge(font_headers, num)); } content = content.push(horizontal_space()); diff --git a/src/gui/pages/connection_details_page.rs b/src/gui/pages/connection_details_page.rs index 3e479ee3..9ac47694 100644 --- a/src/gui/pages/connection_details_page.rs +++ b/src/gui/pages/connection_details_page.rs @@ -44,7 +44,7 @@ pub fn connection_details_page( sniffer: &Sniffer, key: AddressPortPair, -) -> Container { +) -> Container<'_, Message, StyleType> { Container::new(page_content(sniffer, &key)) } diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index eb39e06d..cae46f1e 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -43,7 +43,7 @@ use crate::{ConfigSettings, IpVersion, Language, Protocol, StyleType}; /// Computes the body of gui initial page -pub fn initial_page(sniffer: &Sniffer) -> Container { +pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, language, @@ -129,7 +129,7 @@ fn col_ip_buttons( active_ip_filters: &HashSet, font: Font, language: Language, -) -> Column { +) -> Column<'_, Message, StyleType> { let mut buttons_row = Row::new().spacing(5).padding(Padding::ZERO.left(5)); for option in IpVersion::ALL { let is_active = active_ip_filters.contains(&option); @@ -168,7 +168,7 @@ fn col_protocol_buttons( active_protocol_filters: &HashSet, font: Font, language: Language, -) -> Column { +) -> Column<'_, Message, StyleType> { let mut buttons_row = Row::new().spacing(5).padding(Padding::ZERO.left(5)); for option in Protocol::ALL { let is_active = active_protocol_filters.contains(&option); @@ -203,7 +203,11 @@ fn col_protocol_buttons( .push(buttons_row) } -fn col_address_input(value: &str, font: Font, language: Language) -> Column { +fn col_address_input( + value: &str, + font: Font, + language: Language, +) -> Column<'_, Message, StyleType> { let is_error = if value.is_empty() { false } else { @@ -234,7 +238,7 @@ fn col_address_input(value: &str, font: Font, language: Language) -> Column Column { +fn col_port_input(value: &str, font: Font, language: Language) -> Column<'_, Message, StyleType> { let is_error = if value.is_empty() { false } else { @@ -270,7 +274,7 @@ fn button_start( language: Language, color_gradient: GradientType, filters: &Filters, -) -> Tooltip { +) -> Tooltip<'_, Message, StyleType> { let mut content = button( Icon::Rocket .to_text() @@ -299,7 +303,7 @@ fn button_start( .class(ContainerType::Tooltip) } -fn get_col_adapter(sniffer: &Sniffer, font: Font) -> Column { +fn get_col_adapter(sniffer: &Sniffer, font: Font) -> Column<'_, Message, StyleType> { let ConfigSettings { language, .. } = sniffer.configs.settings; let mut dev_str_list = vec![]; diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index 0274f63a..0f3b8bf2 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -40,7 +40,7 @@ use crate::{ConfigSettings, Language, ReportSortType, RunningPage, Sniffer, StyleType}; /// Computes the body of gui inspect page -pub fn inspect_page(sniffer: &Sniffer) -> Container { +pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, language, .. } = sniffer.configs.settings; @@ -178,7 +178,7 @@ fn report_header_row( search_params: &SearchParameters, font: Font, sort_type: ReportSortType, -) -> Row { +) -> Row<'_, Message, StyleType> { let mut ret_val = Row::new().padding([0, 2]).align_y(Alignment::Center); for report_col in ReportCol::ALL { let (title_display, title_small_display, tooltip_val) = @@ -476,7 +476,7 @@ fn filter_combobox( combo_box_state: &combo_box::State, search_params: SearchParameters, font: Font, -) -> Container { +) -> Container<'_, Message, StyleType> { let filter_value = filter_input_type.current_value(&search_params).to_string(); let is_filter_active = !filter_value.is_empty(); diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 0249de8d..662b0159 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -38,7 +38,7 @@ use std::cmp::max; /// Computes the body of gui notifications page -pub fn notifications_page(sniffer: &Sniffer) -> Container { +pub fn notifications_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, language, @@ -128,7 +128,7 @@ fn body_no_notifications_received( font: Font, language: Language, dots: &str, -) -> Column { +) -> Column<'_, Message, StyleType> { Column::new() .padding(5) .spacing(5) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 9f8341e2..2959d7d2 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -52,7 +52,7 @@ use std::fmt::Write; /// Computes the body of gui overview page -pub fn overview_page(sniffer: &Sniffer) -> Container { +pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, language, .. } = sniffer.configs.settings; @@ -507,7 +507,7 @@ fn col_info<'a>(sniffer: &Sniffer) -> Container<'a, Message, StyleType> { .class(ContainerType::BorderedRound) } -fn container_chart(sniffer: &Sniffer, font: Font) -> Container { +fn container_chart(sniffer: &Sniffer, font: Font) -> Container<'_, Message, StyleType> { let ConfigSettings { language, .. } = sniffer.configs.settings; let traffic_chart = &sniffer.traffic_chart; diff --git a/src/gui/pages/settings_general_page.rs b/src/gui/pages/settings_general_page.rs index f4b2aafc..deacf03c 100644 --- a/src/gui/pages/settings_general_page.rs +++ b/src/gui/pages/settings_general_page.rs @@ -27,7 +27,7 @@ use crate::utils::types::web_page::WebPage; use crate::{ConfigSettings, Language, RunningPage, Sniffer, StyleType}; -pub fn settings_general_page(sniffer: &Sniffer) -> Container { +pub fn settings_general_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, language, @@ -56,7 +56,7 @@ pub fn settings_general_page(sniffer: &Sniffer) -> Container .class(ContainerType::Modal) } -fn column_all_general_setting(sniffer: &Sniffer, font: Font) -> Column { +fn column_all_general_setting(sniffer: &Sniffer, font: Font) -> Column<'_, Message, StyleType> { let ConfigSettings { language, scale_factor, diff --git a/src/gui/pages/settings_style_page.rs b/src/gui/pages/settings_style_page.rs index f3a41a61..0a8519ec 100644 --- a/src/gui/pages/settings_style_page.rs +++ b/src/gui/pages/settings_style_page.rs @@ -27,7 +27,7 @@ use crate::utils::types::icon::Icon; use crate::{ConfigSettings, Language, Sniffer, StyleType}; -pub fn settings_style_page(sniffer: &Sniffer) -> Container { +pub fn settings_style_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, language, diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs index 86a3ae61..47bbec31 100644 --- a/src/gui/pages/thumbnail_page.rs +++ b/src/gui/pages/thumbnail_page.rs @@ -23,7 +23,7 @@ const MAX_CHARS_SERVICE: usize = 13; /// Computes the body of the thumbnail view -pub fn thumbnail_page(sniffer: &Sniffer) -> Container { +pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, .. } = sniffer.configs.settings; let font = style.get_extension().font; diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 79f74228..c8251247 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -565,7 +565,7 @@ pub fn update(&mut self, message: Message) -> Task { Task::none() } - pub fn view(&self) -> Element { + pub fn view(&self) -> Element<'_, Message, StyleType> { let ConfigSettings { style, language, diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 90037db6..38a6f581 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -337,14 +337,15 @@ fn get_traffic_direction( .collect(); // first let's handle TCP and UDP loopback - if source_ip.is_loopback() && destination_ip.is_loopback() { - if let (Some(sport), Some(dport)) = (source_port, dest_port) { - return if sport > dport { - TrafficDirection::Outgoing - } else { - TrafficDirection::Incoming - }; - } + if source_ip.is_loopback() + && destination_ip.is_loopback() + && let (Some(sport), Some(dport)) = (source_port, dest_port) + { + return if sport > dport { + TrafficDirection::Outgoing + } else { + TrafficDirection::Incoming + }; } // if interface_addresses is empty, check if the IP is a bogon (useful when importing pcap files) diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index b18ce3a3..14762bba 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -304,7 +304,7 @@ fn get_sniffable_headers<'a>( } } -fn from_null(packet: &[u8]) -> Result { +fn from_null(packet: &[u8]) -> Result, LaxHeaderSliceError> { if packet.len() <= 4 { return Err(LaxHeaderSliceError::Len(LenError { required_len: 4, diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index 43e441cf..c2595cff 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -97,7 +97,7 @@ pub enum CaptureType { } impl CaptureType { - pub fn next_packet(&mut self) -> Result { + pub fn next_packet(&mut self) -> Result, Error> { match self { Self::Live(on) => on.next_packet(), Self::Offline(off) => off.next_packet(), diff --git a/src/translations/translations.rs b/src/translations/translations.rs index 010bef21..ff833e63 100644 --- a/src/translations/translations.rs +++ b/src/translations/translations.rs @@ -1038,7 +1038,7 @@ pub fn some_observed_translation<'a>(language: Language, observed: u128) -> Text // }) // } -pub fn error_translation(language: Language, error: &str) -> Text { +pub fn error_translation(language: Language, error: &str) -> Text<'_, StyleType> { Text::new(match language { Language::EN => format!( "An error occurred! \n\n\ From 2a0103c05c4011706bc59251a36b23e1c15dd949 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Wed, 13 Aug 2025 23:10:35 +0200 Subject: [PATCH 33/74] fix support for IPinfo's databases --- CHANGELOG.md | 1 + resources/test/ipinfo_asn_sample.mmdb | Bin 24042 -> 24334 bytes resources/test/ipinfo_country_asn_sample.mmdb | Bin 35365 -> 0 bytes resources/test/ipinfo_country_sample.mmdb | Bin 30313 -> 0 bytes resources/test/ipinfo_lite_sample.mmdb | Bin 0 -> 6885 bytes resources/test/ipinfo_location_sample.mmdb | Bin 0 -> 8780 bytes src/mmdb/asn.rs | 44 +++++++++--------- src/mmdb/country.rs | 36 +++++++------- src/mmdb/types/mmdb_country_entry.rs | 6 ++- 9 files changed, 43 insertions(+), 44 deletions(-) delete mode 100644 resources/test/ipinfo_country_asn_sample.mmdb delete mode 100644 resources/test/ipinfo_country_sample.mmdb create mode 100644 resources/test/ipinfo_lite_sample.mmdb create mode 100644 resources/test/ipinfo_location_sample.mmdb diff --git a/CHANGELOG.md b/CHANGELOG.md index bfaabc36..a1ab69aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ## [UNRELEASED] - Simplified Chinese ([#838](https://github.com/GyulyVGC/sniffnet/pull/838)) - Japanese ([#849](https://github.com/GyulyVGC/sniffnet/pull/849)) - French ([#864](https://github.com/GyulyVGC/sniffnet/pull/864)) +- Fix support for IPinfo's databases (the most recent version renamed the `country` field to `country_code`) ## [1.4.0] - 2025-06-27 - Import PCAP files ([#795](https://github.com/GyulyVGC/sniffnet/pull/795) — fixes [#283](https://github.com/GyulyVGC/sniffnet/issues/283)) diff --git a/resources/test/ipinfo_asn_sample.mmdb b/resources/test/ipinfo_asn_sample.mmdb index 6e86ae38d0d3320b05b911135632a24b19d2830b..b1e36399b8466bf75c671ae057c5d4811b820bec 100644 GIT binary patch literal 24334 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 literal 24042 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 BAYT9g diff --git a/resources/test/ipinfo_lite_sample.mmdb b/resources/test/ipinfo_lite_sample.mmdb new file mode 100644 index 0000000000000000000000000000000000000000..da15dab2e54429937a90fa05f61025cdc3f77bed GIT binary patch literal 6885 zcmb7|X>c4z6~|lFT2U+?vISX=Kw5@?0B56_*_C#c5WG@!tZuxLb2w%h&5m}*-kn)y zXJsi6?tsG)ZU_lS%ngBX->-S#2?j6Qh8)NJ&WE-)a7$I&ab`UQkb`mcq?jUv%cM^9IcN6yzyNP>=`-nZn zrx2e?yn+}dK8^Tv;xmZPBwk5;7V+7{tB9P~OWaR<4)M9ftBKDeKA+e}d;#%=#1|0{ z5U(M|h}RPPi3(9AYQ#7(K^!0s5|hLfafo;w@p|Hmi8l~mLOe)J6NiZ-#2bmD#4+MH zae|m3P7M3cBoED*(>DMBo2A0r+omWXAd4c-rNAa93MAg_YF0`hl=i>>$uunPVf^dPH{ z8ruH>SFlAzj-CKt1Nju>pJ=Z!)-LwI?VyO|D?!-Q{w~PlXy3tDWFPoy$eSVP8xgti zT2QR%b&xkgUXS+OAZ&$+cm$98);O8M< zfP5A5Al|`$?290Lx$~QN{u1QNcs>upwq1zhu1N^S-X-k(8U(S}EsXsJDApzVeGAXX zuiaQ1`!?h|ct+0b{vTs|kRN-{pM4K<76QANSjP__k3fD1`7z`)^u!zwG1lD){s;np zb`L_9A&65q=IK6%_dkLBl(D^0@MqvS1oQ00oO}O^cRz>x0`e$h`(ofPLE+0^fpIdpD-5mOdLabB2>Qhtt_{!>QSxyEd^<)UA?k=cnm~udpyNZkyf-$2~r+#QI~3 znvC^ds|=p!ZPTW08ctbQb*#dpQ=_tZAY&UbK7{F&q!N$wF+R^1d7c+WCw17!SBEF( z`N=6U)<9egmBdg7dGsJ3I=D`Gq-fbXlt*SsSyP2_s+lr>lCSng_{m8zs4!1epf*?V z%n}(`j_EZXogb$+eBOhvyu371OD6j@J~lpQn7VE0d~tYggcmaDTGey)5?-thrx$5( zVX&&Uv_Y7vYEYXsN=_}mT+&?=ZYslsk>JGv-td&BX$d~oYxBk4RbJ@51M9hqrtBAE z#am8^rEjKohOhEJk{4CE0^y#ET+TP>$!VLfbiJM;-7S_nr@Nl%TKYA}7Jsd`r4KEP z9?GQote-zsq^VnRT-~!An-7g4K@&=<5%UcOCB-3$kWY>yY3g#VOLg5A$g6V|=Lh zT+`H%x|`+1`sFHe;^wj5peJV*v7T7d%QZj9M{dC*UoefL?UbB?Wr`@r4v5`_Dlma6 z5Kko(WnDVV86GB&}hHz;J-PSvT(md>zP{VEsb*oZcufEt47%*_^R!OWjs`S=sLB?&Z zCKGr=8Z{}UYD}xA(bl8MCCD^4Y}>-AYxC&QF8@Hv*eG(SaP^vo8orDZT2{0XoUx`W z&lEMTB%0^E>Q{pe#@0{Bx+k&C)mWmXv52-7HiBBn2x=jVA0`eJX)Ro&wQw~O0Tf;l zvDqqwnqNvc_;@V6>{^C6-P2>dFqt*ijG|d8p#=y(Qf~E^juo zS+s`Hk_Q;d8jh~`po$Yah#b`*L=JJoD9QkAY;hvR!v-%NGI;Tj!NbI%f)@`Lym+|a z#hV;(O|~g2Vm%65#TaX-t4&v}aiNl*m-%Y%iwB> zfWZ|aVB$vG*WSmYeeHvMsIPsj(W9^ZCS-|F$Cev_^JE8c$b{?=mt|fYH6c65n~J=} zhU^fRP7`N%$PQX?aht9Jlu>r%H6P#HgJETO-mp=!>bx?f|g^z(MZ-JK(1J%Dl&nTF0QBH^!@S@s#?hL8;Lq&m8(TlFL^~j z-EPjzWch@Afvb?HtMe6Ty17NEcr-SZPa5Swouscx&XU9CP3jt(B)-^Dhq<;wao$IY#nZWUi3PHnsJ zua@2T>-|$pHLH}*nH9$ Pk6La%SG8_8Px1c)i8mx( literal 0 HcmV?d00001 diff --git a/resources/test/ipinfo_location_sample.mmdb b/resources/test/ipinfo_location_sample.mmdb new file mode 100644 index 0000000000000000000000000000000000000000..aee65ad5d5ff1c53696f8ed323761c7ec7b86010 GIT binary patch literal 8780 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 N>`;HsK4qQZ{{y literal 0 HcmV?d00001 diff --git a/src/mmdb/asn.rs b/src/mmdb/asn.rs index 23e341f9..e120d2ee 100644 --- a/src/mmdb/asn.rs +++ b/src/mmdb/asn.rs @@ -77,19 +77,19 @@ fn test_get_asn_with_custom_ipinfo_single_reader() { assert!(matches!(reader, MmdbReader::Custom(_))); // known IP - let res = get_asn(&IpAddr::from([61, 8, 0, 0]), &reader); - assert_eq!(res.code, "AS1221"); - assert_eq!(res.name, "Telstra Limited"); + let res = get_asn(&IpAddr::from([185, 72, 2, 28]), &reader); + assert_eq!(res.code, "AS202583"); + assert_eq!(res.name, "AVATEL TELECOM, SA"); // another known IP - let res = get_asn(&IpAddr::from([206, 180, 34, 99]), &reader); - assert_eq!(res.code, "AS63344"); - assert_eq!(res.name, "The Reynolds and Reynolds Company"); + let res = get_asn(&IpAddr::from([89, 187, 198, 0]), &reader); + assert_eq!(res.code, "AS210367"); + assert_eq!(res.name, "Krajska zdravotni, a.s."); // known IPv6 - let res = get_asn(&IpAddr::from_str("2806:230:2057::").unwrap(), &reader); - assert_eq!(res.code, "AS11888"); - assert_eq!(res.name, "Television Internacional, S.A. de C.V."); + let res = get_asn(&IpAddr::from_str("2408:8957:6280::").unwrap(), &reader); + assert_eq!(res.code, "AS17622"); + assert_eq!(res.name, "China Unicom Guangzhou network"); // unknown IP let res = get_asn(&IpAddr::from([127, 0, 0, 1]), &reader); @@ -106,31 +106,29 @@ fn test_get_asn_with_custom_ipinfo_single_reader() { #[test] fn test_get_asn_with_custom_ipinfo_combined_reader() { let reader_1 = MmdbReader::from( - &String::from("resources/test/ipinfo_country_asn_sample.mmdb"), + &String::from("resources/test/ipinfo_lite_sample.mmdb"), ASN_MMDB, ); - let reader_2 = MmdbReader::from( - &String::from("resources/test/ipinfo_country_asn_sample.mmdb"), - &[], - ); + let reader_2 = + MmdbReader::from(&String::from("resources/test/ipinfo_lite_sample.mmdb"), &[]); for reader in vec![reader_1, reader_2] { assert!(matches!(reader, MmdbReader::Custom(_))); // known IP - let res = get_asn(&IpAddr::from([31, 171, 144, 141]), &reader); - assert_eq!(res.code, "AS197742"); - assert_eq!(res.name, "IBB Energie AG"); + let res = get_asn(&IpAddr::from([1, 0, 65, 1]), &reader); + assert_eq!(res.code, "AS18144"); + assert_eq!(res.name, "Enecom,Inc."); // another known IP - let res = get_asn(&IpAddr::from([103, 112, 220, 111]), &reader); - assert_eq!(res.code, "AS134077"); - assert_eq!(res.name, "Magik Pivot Company Limited"); + let res = get_asn(&IpAddr::from([1, 6, 230, 0]), &reader); + assert_eq!(res.code, "AS4755"); + assert_eq!(res.name, "TATA Communications formerly VSNL is Leading ISP"); // known IPv6 - let res = get_asn(&IpAddr::from_str("2a02:6ea0:f001::").unwrap(), &reader); - assert_eq!(res.code, "AS60068"); - assert_eq!(res.name, "Datacamp Limited"); + // let res = get_asn(&IpAddr::from_str("2a02:6ea0:f001::").unwrap(), &reader); + // assert_eq!(res.code, "AS60068"); + // assert_eq!(res.name, "Datacamp Limited"); // unknown IP let res = get_asn(&IpAddr::from([127, 0, 0, 1]), &reader); diff --git a/src/mmdb/country.rs b/src/mmdb/country.rs index c23e4d69..9e5cd9a0 100644 --- a/src/mmdb/country.rs +++ b/src/mmdb/country.rs @@ -63,11 +63,11 @@ fn test_get_country_with_default_reader() { #[test] fn test_get_country_with_custom_ipinfo_single_reader() { let reader_1 = MmdbReader::from( - &String::from("resources/test/ipinfo_country_sample.mmdb"), + &String::from("resources/test/ipinfo_location_sample.mmdb"), COUNTRY_MMDB, ); let reader_2 = MmdbReader::from( - &String::from("resources/test/ipinfo_country_sample.mmdb"), + &String::from("resources/test/ipinfo_location_sample.mmdb"), &[], ); @@ -75,16 +75,16 @@ fn test_get_country_with_custom_ipinfo_single_reader() { assert!(matches!(reader, MmdbReader::Custom(_))); // known IP - let res = get_country(&IpAddr::from([2, 2, 146, 0]), &reader); - assert_eq!(res, Country::GB); + let res = get_country(&IpAddr::from([1, 0, 6, 99]), &reader); + assert_eq!(res, Country::AU); // another known IP - let res = get_country(&IpAddr::from([23, 193, 112, 81]), &reader); - assert_eq!(res, Country::US); + let res = get_country(&IpAddr::from([1, 0, 8, 0]), &reader); + assert_eq!(res, Country::CN); // known IPv6 - let res = get_country(&IpAddr::from_str("2a0e:1d80::").unwrap(), &reader); - assert_eq!(res, Country::RO); + // let res = get_country(&IpAddr::from_str("2a0e:1d80::").unwrap(), &reader); + // assert_eq!(res, Country::RO); // unknown IP let res = get_country(&IpAddr::from([127, 0, 0, 1]), &reader); @@ -99,28 +99,26 @@ fn test_get_country_with_custom_ipinfo_single_reader() { #[test] fn test_get_country_with_custom_ipinfo_combined_reader() { let reader_1 = MmdbReader::from( - &String::from("resources/test/ipinfo_country_asn_sample.mmdb"), + &String::from("resources/test/ipinfo_lite_sample.mmdb"), COUNTRY_MMDB, ); - let reader_2 = MmdbReader::from( - &String::from("resources/test/ipinfo_country_asn_sample.mmdb"), - &[], - ); + let reader_2 = + MmdbReader::from(&String::from("resources/test/ipinfo_lite_sample.mmdb"), &[]); for reader in vec![reader_1, reader_2] { assert!(matches!(reader, MmdbReader::Custom(_))); // known IP - let res = get_country(&IpAddr::from([31, 171, 144, 141]), &reader); - assert_eq!(res, Country::IT); + let res = get_country(&IpAddr::from([1, 0, 65, 1]), &reader); + assert_eq!(res, Country::JP); // another known IP - let res = get_country(&IpAddr::from([103, 112, 220, 111]), &reader); - assert_eq!(res, Country::TH); + let res = get_country(&IpAddr::from([1, 6, 230, 0]), &reader); + assert_eq!(res, Country::IN); // known IPv6 - let res = get_country(&IpAddr::from_str("2a02:6ea0:f001::").unwrap(), &reader); - assert_eq!(res, Country::AR); + // let res = get_country(&IpAddr::from_str("2a02:6ea0:f001::").unwrap(), &reader); + // assert_eq!(res, Country::AR); // unknown IP let res = get_country(&IpAddr::from([127, 0, 0, 1]), &reader); diff --git a/src/mmdb/types/mmdb_country_entry.rs b/src/mmdb/types/mmdb_country_entry.rs index fffa5b6e..279e0b2e 100644 --- a/src/mmdb/types/mmdb_country_entry.rs +++ b/src/mmdb/types/mmdb_country_entry.rs @@ -30,7 +30,9 @@ fn get_country(&self) -> Country { Self::Standard(StandardCountryEntry { country: Some(StandardCountryEntryInner { iso_code: Some(c) }), }) - | Self::Ipinfo(IpinfoCountryEntry { country: Some(c) }) => Country::from_str(c), + | Self::Ipinfo(IpinfoCountryEntry { + country_code: Some(c), + }) => Country::from_str(c), _ => Country::ZZ, } } @@ -49,5 +51,5 @@ struct StandardCountryEntryInner<'a> { #[derive(Deserialize)] struct IpinfoCountryEntry<'a> { - country: Option<&'a str>, + country_code: Option<&'a str>, } From bc5bb0ae219e9b4cbc16ea31162739dda6da5936 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 14 Aug 2025 00:23:30 +0200 Subject: [PATCH 34/74] add bits data representation WIP (replace ChartType with DataRepr) --- src/chart/manage_chart_data.rs | 2 +- src/chart/types/chart_type.rs | 21 ---- src/chart/types/donut_chart.rs | 19 ++- src/chart/types/mod.rs | 1 - src/chart/types/traffic_chart.rs | 23 ++-- src/gui/pages/inspect_page.rs | 9 +- src/gui/pages/notifications_page.rs | 60 ++++------ src/gui/pages/overview_page.rs | 84 ++++++------- src/gui/pages/settings_notifications_page.rs | 12 +- src/gui/pages/thumbnail_page.rs | 20 ++-- src/gui/sniffer.rs | 32 ++--- src/gui/types/message.rs | 7 +- src/main.rs | 3 +- src/networking/types/data_info.rs | 10 +- ...yte_multiple.rs => data_representation.rs} | 111 +++++++++++++----- src/networking/types/info_traffic.rs | 6 +- src/networking/types/mod.rs | 2 +- src/notifications/notify_and_log.rs | 23 ++-- .../types/logged_notification.rs | 4 +- src/notifications/types/notifications.rs | 6 +- src/report/get_report_entries.rs | 11 +- src/report/types/report_col.rs | 8 +- 22 files changed, 235 insertions(+), 239 deletions(-) delete mode 100644 src/chart/types/chart_type.rs rename src/networking/types/{byte_multiple.rs => data_representation.rs} (74%) diff --git a/src/chart/manage_chart_data.rs b/src/chart/manage_chart_data.rs index aaaf219f..a2fc5091 100644 --- a/src/chart/manage_chart_data.rs +++ b/src/chart/manage_chart_data.rs @@ -310,7 +310,7 @@ fn test_chart_data_updates() { min_packets: -1000.0, max_packets: 21000.0, language: Language::default(), - chart_type: ChartType::Packets, + data_repr: ChartType::Packets, style: StyleType::default(), thumbnail: false, is_live_capture: true, diff --git a/src/chart/types/chart_type.rs b/src/chart/types/chart_type.rs deleted file mode 100644 index 0e339435..00000000 --- a/src/chart/types/chart_type.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::Language; -use crate::translations::translations::{bytes_translation, packets_translation}; -use serde::{Deserialize, Serialize}; - -/// Enum representing the possible kind of chart displayed. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ChartType { - Packets, - Bytes, -} - -impl ChartType { - pub(crate) const ALL: [ChartType; 2] = [ChartType::Bytes, ChartType::Packets]; - - pub fn get_label(&self, language: Language) -> &str { - match self { - ChartType::Packets => packets_translation(language), - ChartType::Bytes => bytes_translation(language), - } - } -} diff --git a/src/chart/types/donut_chart.rs b/src/chart/types/donut_chart.rs index b2f392b7..13749306 100644 --- a/src/chart/types/donut_chart.rs +++ b/src/chart/types/donut_chart.rs @@ -1,7 +1,6 @@ -use crate::chart::types::chart_type::ChartType; use crate::gui::styles::donut::Catalog; use crate::gui::styles::style_constants::{FONT_SIZE_FOOTER, FONT_SIZE_SUBTITLE}; -use crate::networking::types::byte_multiple::ByteMultiple; +use crate::networking::types::data_representation::DataRepr; use iced::alignment::{Horizontal, Vertical}; use iced::widget::canvas::path::Arc; use iced::widget::canvas::{Frame, Text}; @@ -10,7 +9,7 @@ use std::f32::consts; pub struct DonutChart { - chart_type: ChartType, + data_repr: DataRepr, incoming: u128, outgoing: u128, filtered_out: u128, @@ -21,7 +20,7 @@ pub struct DonutChart { impl DonutChart { fn new( - chart_type: ChartType, + data_repr: DataRepr, incoming: u128, outgoing: u128, filtered_out: u128, @@ -30,7 +29,7 @@ fn new( thumbnail: bool, ) -> Self { Self { - chart_type, + data_repr, incoming, outgoing, filtered_out, @@ -46,11 +45,7 @@ fn total(&self) -> u128 { fn title(&self) -> String { let total = self.total(); - if self.chart_type.eq(&ChartType::Bytes) { - ByteMultiple::formatted_string(total) - } else { - total.to_string() - } + self.data_repr.formatted_string(total) } fn angles(&self) -> [(Radians, Radians); 4] { @@ -150,7 +145,7 @@ fn draw( } pub fn donut_chart( - chart_type: ChartType, + data_repr: DataRepr, incoming: u128, outgoing: u128, filtered_out: u128, @@ -164,7 +159,7 @@ pub fn donut_chart( Length::Fixed(110.0) }; iced::widget::canvas(DonutChart::new( - chart_type, + data_repr, incoming, outgoing, filtered_out, diff --git a/src/chart/types/mod.rs b/src/chart/types/mod.rs index ca3f7e1b..c84d0fef 100644 --- a/src/chart/types/mod.rs +++ b/src/chart/types/mod.rs @@ -1,3 +1,2 @@ -pub mod chart_type; pub mod donut_chart; pub mod traffic_chart; diff --git a/src/chart/types/traffic_chart.rs b/src/chart/types/traffic_chart.rs index d9562e48..19dd47bb 100644 --- a/src/chart/types/traffic_chart.rs +++ b/src/chart/types/traffic_chart.rs @@ -15,12 +15,13 @@ use crate::gui::styles::style_constants::CHARTS_LINE_BORDER; use crate::gui::styles::types::palette::to_rgb_color; use crate::gui::types::message::Message; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::traffic_direction::TrafficDirection; use crate::translations::translations::{incoming_translation, outgoing_translation}; use crate::utils::error_logger::{ErrorLogger, Location}; use crate::utils::formatted_strings::{get_formatted_num_seconds, get_formatted_timestamp}; use crate::utils::types::timestamp::Timestamp; -use crate::{ByteMultiple, ChartType, Language, StyleType, location}; +use crate::{Language, StyleType, location}; /// Struct defining the chart to be displayed in gui run page pub struct TrafficChart { @@ -45,7 +46,7 @@ pub struct TrafficChart { /// Language used for the chart legend pub language: Language, /// Packets or bytes - pub chart_type: ChartType, + pub data_repr: DataRepr, /// Style of the chart pub style: StyleType, /// Whether the chart is for the thumbnail page @@ -71,7 +72,7 @@ pub fn new(style: StyleType, language: Language) -> Self { min_packets: 0.0, max_packets: 0.0, language, - chart_type: ChartType::Bytes, + data_repr: DataRepr::Bytes, style, thumbnail: false, is_live_capture: true, @@ -115,8 +116,8 @@ pub fn view(&self) -> Element<'_, Message, StyleType> { .into() } - pub fn change_kind(&mut self, kind: ChartType) { - self.chart_type = kind; + pub fn change_kind(&mut self, kind: DataRepr) { + self.data_repr = kind; } pub fn change_language(&mut self, language: Language) { @@ -169,7 +170,7 @@ fn x_axis_range(&self) -> Range { } fn y_axis_range(&self) -> Range { - let (min, max) = match self.chart_type { + let (min, max) = match self.data_repr { ChartType::Packets => (self.min_packets, self.max_packets), ChartType::Bytes => (self.min_bytes, self.max_bytes), }; @@ -186,7 +187,7 @@ fn font<'a>(&self, size: f64) -> TextStyle<'a> { } fn spline_to_plot(&self, direction: TrafficDirection) -> &Spline { - match self.chart_type { + match self.data_repr { ChartType::Packets => match direction { TrafficDirection::Incoming => &self.in_packets.spline, TrafficDirection::Outgoing => &self.out_packets.spline, @@ -283,12 +284,10 @@ fn build_chart( .max_light_lines(0) .label_style(self.font(12.5)) .y_labels(min(5, y_labels)) - .y_label_formatter(if self.chart_type.eq(&ChartType::Packets) { - &|packets| packets.abs().to_string() - } else { + .y_label_formatter( #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - &|bytes| ByteMultiple::formatted_string(bytes.abs() as u128) - }) + &|amount| self.data_repr.formatted_string(amount.abs() as u128), + ) .x_labels(min(6, x_labels)) .x_label_formatter( #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index 0f3b8bf2..f995de7f 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -11,7 +11,6 @@ }; use iced::{Alignment, Font, Length, Padding, Pixels, alignment}; -use crate::chart::types::chart_type::ChartType; use crate::gui::components::tab::get_pages_tabs; use crate::gui::components::types::my_modal::MyModal; use crate::gui::pages::overview_page::{get_bars, get_bars_length}; @@ -23,8 +22,8 @@ use crate::gui::styles::text_input::TextInputType; use crate::gui::types::message::Message; use crate::networking::types::address_port_pair::AddressPortPair; -use crate::networking::types::byte_multiple::ByteMultiple; use crate::networking::types::data_info::DataInfo; +use crate::networking::types::data_representation::{ByteMultiple, DataRepr}; use crate::networking::types::host_data_states::HostStates; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::networking::types::traffic_direction::TrafficDirection; @@ -144,7 +143,7 @@ fn report<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { .push(get_agglomerates_row( font, agglomerate, - sniffer.traffic_chart.chart_type, + sniffer.traffic_chart.data_repr, )) .push(Rule::horizontal(5)) .push(get_change_page_row( @@ -550,12 +549,12 @@ fn get_button_change_page<'a>(increment: bool) -> Button<'a, Message, StyleType> fn get_agglomerates_row<'a>( font: Font, tot: DataInfo, - chart_type: ChartType, + data_repr: DataRepr, ) -> Row<'a, Message, StyleType> { let tot_packets = tot.tot_packets(); let tot_bytes = tot.tot_bytes(); - let (in_length, out_length) = get_bars_length(chart_type, &tot, &tot); + let (in_length, out_length) = get_bars_length(data_repr, &tot, &tot); let bars = get_bars(in_length, out_length).width(ReportCol::FILTER_COLUMNS_WIDTH); let bytes_col = Column::new() diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 662b0159..4c8f2728 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -1,4 +1,3 @@ -use crate::chart::types::chart_type::ChartType; use crate::countries::country_utils::get_computer_tooltip; use crate::countries::flags_pictures::FLAGS_HEIGHT_BIG; use crate::gui::components::header::get_button_settings; @@ -13,6 +12,7 @@ use crate::gui::types::message::Message; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; use crate::networking::types::service::Service; use crate::networking::types::traffic_type::TrafficType; @@ -21,13 +21,12 @@ }; use crate::report::types::sort_type::SortType; use crate::translations::translations::{ - bytes_exceeded_translation, clear_all_translation, favorite_transmitted_translation, - no_notifications_received_translation, no_notifications_set_translation, - only_last_30_translation, packets_exceeded_translation, per_second_translation, + clear_all_translation, favorite_transmitted_translation, no_notifications_received_translation, + no_notifications_set_translation, only_last_30_translation, per_second_translation, threshold_translation, }; use crate::utils::types::icon::Icon; -use crate::{ByteMultiple, ConfigSettings, Language, RunningPage, Sniffer, StyleType}; +use crate::{ConfigSettings, Language, RunningPage, Sniffer, StyleType}; use iced::Length::FillPortion; use iced::widget::scrollable::Direction; use iced::widget::text::LineHeight; @@ -150,13 +149,9 @@ fn data_notification_log<'a>( language: Language, font: Font, ) -> Container<'a, Message, StyleType> { - let chart_type = logged_notification.chart_type; - let data_string = if chart_type == ChartType::Bytes { - ByteMultiple::formatted_string(logged_notification.threshold.into()) - } else { - logged_notification.threshold.to_string() - }; - let icon = if chart_type == ChartType::Packets { + let data_repr = logged_notification.data_repr; + let data_string = data_repr.formatted_string(logged_notification.threshold.into()); + let icon = if data_repr == DataRepr::Packets { Icon::PacketsThreshold } else { Icon::BytesThreshold @@ -184,13 +179,9 @@ fn data_notification_log<'a>( .push(Text::new(logged_notification.timestamp.clone()).font(font)), ) .push( - Text::new(if chart_type == ChartType::Bytes { - bytes_exceeded_translation(language) - } else { - packets_exceeded_translation(language) - }) - .class(TextType::Title) - .font(font), + Text::new(data_repr.data_exceeded_translation(language)) + .class(TextType::Title) + .font(font), ) .push( Text::new(threshold_str) @@ -222,14 +213,14 @@ fn data_notification_log<'a>( fn favorite_notification_log<'a>( logged_notification: &FavoriteTransmitted, first_entry_data_info: DataInfo, - chart_type: ChartType, + data_repr: DataRepr, language: Language, font: Font, ) -> Container<'a, Message, StyleType> { let host_bar = host_bar( &logged_notification.host, &logged_notification.data_info_host, - chart_type, + data_repr, first_entry_data_info, font, language, @@ -294,7 +285,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> let ConfigSettings { style, language, .. } = sniffer.configs.settings; - let chart_type = sniffer.traffic_chart.chart_type; + let data_repr = sniffer.traffic_chart.data_repr; let font = style.get_extension().font; let mut ret_val = Column::new() .padding(Padding::ZERO.right(15)) @@ -306,7 +297,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> .0 .iter() .map(LoggedNotification::data_info) - .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, chart_type)) + .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, data_repr)) .unwrap_or_default(); for logged_notification in &sniffer.logged_notifications.0 { @@ -323,7 +314,7 @@ fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> favorite_notification_log( favorite_transmitted, first_entry_data_info, - chart_type, + data_repr, language, font, ) @@ -339,10 +330,10 @@ fn threshold_bar<'a>( language: Language, font: Font, ) -> Row<'a, Message, StyleType> { - let chart_type = logged_notification.chart_type; + let data_repr = logged_notification.data_repr; let data_info = logged_notification.data_info; let (incoming_bar_len, outgoing_bar_len) = - get_bars_length(chart_type, &first_entry_data_info, &data_info); + get_bars_length(data_repr, &first_entry_data_info, &data_info); Row::new() .align_y(Alignment::Center) @@ -358,16 +349,9 @@ fn threshold_bar<'a>( .push( Column::new() .spacing(1) - .push( - Row::new().push(horizontal_space()).push( - Text::new(if chart_type.eq(&ChartType::Packets) { - data_info.tot_packets().to_string() - } else { - ByteMultiple::formatted_string(data_info.tot_bytes()) - }) - .font(font), - ), - ) + .push(Row::new().push(horizontal_space()).push( + Text::new(data_repr.formatted_string_from_data_info(&data_info)).font(font), + )) .push(get_bars(incoming_bar_len, outgoing_bar_len)), ) } @@ -424,7 +408,7 @@ fn data_notification_extra<'a>( let host_bar = host_bar( host, data_info_host, - logged_notification.chart_type, + logged_notification.data_repr, first_data_info_host, font, language, @@ -442,7 +426,7 @@ fn data_notification_extra<'a>( let service_bar = service_bar( service, data_info, - logged_notification.chart_type, + logged_notification.data_repr, first_data_info_service, font, ); diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 2959d7d2..1ff1fbc2 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -19,6 +19,7 @@ use crate::networking::types::capture_context::CaptureSource; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::filters::Filters; use crate::networking::types::host::Host; use crate::networking::types::service::Service; @@ -38,7 +39,7 @@ use crate::translations::translations_4::{excluded_translation, reading_from_pcap_translation}; use crate::utils::formatted_strings::get_active_filters_string; use crate::utils::types::icon::Icon; -use crate::{ByteMultiple, ChartType, ConfigSettings, Language, RunningPage, StyleType}; +use crate::{ConfigSettings, Language, RunningPage, StyleType}; use iced::Length::{Fill, FillPortion}; use iced::alignment::{Horizontal, Vertical}; use iced::widget::scrollable::Direction; @@ -245,16 +246,16 @@ fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { style, language, .. } = sniffer.configs.settings; let font = style.get_extension().font; - let chart_type = sniffer.traffic_chart.chart_type; + let data_repr = sniffer.traffic_chart.data_repr; let mut scroll_host = Column::new() .padding(Padding::ZERO.right(11.0)) .align_x(Alignment::Center); - let entries = get_host_entries(&sniffer.info_traffic, chart_type, sniffer.host_sort_type); + let entries = get_host_entries(&sniffer.info_traffic, data_repr, sniffer.host_sort_type); let first_entry_data_info = entries .iter() .map(|(_, d)| d.data_info) - .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, chart_type)) + .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, data_repr)) .unwrap_or_default(); for (host, data_info_host) in &entries { @@ -263,7 +264,7 @@ fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { let host_bar = host_bar( host, data_info_host, - chart_type, + data_repr, first_entry_data_info, font, language, @@ -322,20 +323,20 @@ fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { style, language, .. } = sniffer.configs.settings; let font = style.get_extension().font; - let chart_type = sniffer.traffic_chart.chart_type; + let data_repr = sniffer.traffic_chart.data_repr; let mut scroll_service = Column::new() .padding(Padding::ZERO.right(11.0)) .align_x(Alignment::Center); - let entries = get_service_entries(&sniffer.info_traffic, chart_type, sniffer.service_sort_type); + let entries = get_service_entries(&sniffer.info_traffic, data_repr, sniffer.service_sort_type); let first_entry_data_info = entries .iter() .map(|&(_, d)| d) - .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, chart_type)) + .max_by(|d1, d2| d1.compare(d2, SortType::Ascending, data_repr)) .unwrap_or_default(); for (service, data_info) in &entries { - let content = service_bar(service, data_info, chart_type, first_entry_data_info, font); + let content = service_bar(service, data_info, data_repr, first_entry_data_info, font); scroll_service = scroll_service.push( button(content) @@ -384,16 +385,13 @@ fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { pub fn host_bar<'a>( host: &Host, data_info_host: &DataInfoHost, - chart_type: ChartType, + data_repr: DataRepr, first_entry_data_info: DataInfo, font: Font, language: Language, ) -> Row<'a, Message, StyleType> { - let (incoming_bar_len, outgoing_bar_len) = get_bars_length( - chart_type, - &first_entry_data_info, - &data_info_host.data_info, - ); + let (incoming_bar_len, outgoing_bar_len) = + get_bars_length(data_repr, &first_entry_data_info, &data_info_host.data_info); Row::new() .height(FLAGS_HEIGHT_BIG) @@ -422,11 +420,10 @@ pub fn host_bar<'a>( ) .push(horizontal_space()) .push( - Text::new(if chart_type.eq(&ChartType::Packets) { - data_info_host.data_info.tot_packets().to_string() - } else { - ByteMultiple::formatted_string(data_info_host.data_info.tot_bytes()) - }) + Text::new( + data_repr + .formatted_string_from_data_info(&data_info_host.data_info), + ) .font(font), ), ) @@ -437,12 +434,12 @@ pub fn host_bar<'a>( pub fn service_bar<'a>( service: &Service, data_info: &DataInfo, - chart_type: ChartType, + data_repr: DataRepr, first_entry_data_info: DataInfo, font: Font, ) -> Row<'a, Message, StyleType> { let (incoming_bar_len, outgoing_bar_len) = - get_bars_length(chart_type, &first_entry_data_info, data_info); + get_bars_length(data_repr, &first_entry_data_info, data_info); Row::new() .height(FLAGS_HEIGHT_BIG) @@ -456,12 +453,8 @@ pub fn service_bar<'a>( .push(Text::new(service.to_string()).font(font)) .push(horizontal_space()) .push( - Text::new(if chart_type.eq(&ChartType::Packets) { - data_info.tot_packets().to_string() - } else { - ByteMultiple::formatted_string(data_info.tot_bytes()) - }) - .font(font), + Text::new(data_repr.formatted_string_from_data_info(&data_info)) + .font(font), ), ) .push(get_bars(incoming_bar_len, outgoing_bar_len)), @@ -477,7 +470,7 @@ fn col_info<'a>(sniffer: &Sniffer) -> Container<'a, Message, StyleType> { let col_device = col_device(language, font, &sniffer.capture_source); let col_data_representation = - col_data_representation(language, font, sniffer.traffic_chart.chart_type); + col_data_representation(language, font, sniffer.traffic_chart.data_repr); let donut_row = donut_row(language, font, sniffer); @@ -555,7 +548,7 @@ fn col_device<'a>( fn col_data_representation<'a>( language: Language, font: Font, - chart_type: ChartType, + data_repr: DataRepr, ) -> Column<'a, Message, StyleType> { let mut ret_val = Column::new().spacing(5).push( Text::new(format!("{}:", data_representation_translation(language))) @@ -564,7 +557,7 @@ fn col_data_representation<'a>( ); for option in ChartType::ALL { - let is_active = chart_type.eq(&option); + let is_active = data_repr.eq(&option); ret_val = ret_val.push( Button::new( Text::new(option.get_label(language).to_owned()) @@ -580,7 +573,7 @@ fn col_data_representation<'a>( } else { ButtonType::BorderedRound }) - .on_press(Message::ChartSelection(option)), + .on_press(Message::DataReprSelection(option)), ); } ret_val @@ -591,18 +584,18 @@ fn donut_row<'a>( font: Font, sniffer: &Sniffer, ) -> Container<'a, Message, StyleType> { - let chart_type = sniffer.traffic_chart.chart_type; + let data_repr = sniffer.traffic_chart.data_repr; let filters = &sniffer.filters; let (in_data, out_data, filtered_out, dropped) = - sniffer.info_traffic.get_thumbnail_data(chart_type); + sniffer.info_traffic.get_thumbnail_data(data_repr); let legend_entry_filtered = if filters.none_active() { None } else { Some(donut_legend_entry( filtered_out, - chart_type, + data_repr, RuleType::FilteredOut, filters, font, @@ -614,7 +607,7 @@ fn donut_row<'a>( .spacing(5) .push(donut_legend_entry( in_data, - chart_type, + data_repr, RuleType::Incoming, filters, font, @@ -622,7 +615,7 @@ fn donut_row<'a>( )) .push(donut_legend_entry( out_data, - chart_type, + data_repr, RuleType::Outgoing, filters, font, @@ -631,7 +624,7 @@ fn donut_row<'a>( .push_maybe(legend_entry_filtered) .push(donut_legend_entry( dropped, - chart_type, + data_repr, RuleType::Dropped, filters, font, @@ -642,7 +635,7 @@ fn donut_row<'a>( .align_y(Vertical::Center) .spacing(20) .push(donut_chart( - chart_type, + data_repr, in_data, out_data, filtered_out, @@ -661,17 +654,13 @@ fn donut_row<'a>( fn donut_legend_entry<'a>( value: u128, - chart_type: ChartType, + data_repr: DataRepr, rule_type: RuleType, filters: &Filters, font: Font, language: Language, ) -> Row<'a, Message, StyleType> { - let value_text = if chart_type.eq(&ChartType::Bytes) { - ByteMultiple::formatted_string(value) - } else { - value.to_string() - }; + let value_text = data_repr.formatted_string(value); let label = match rule_type { RuleType::Incoming => incoming_translation(language), @@ -702,11 +691,11 @@ fn donut_legend_entry<'a>( const MIN_BARS_LENGTH: f32 = 4.0; pub fn get_bars_length( - chart_type: ChartType, + data_repr: DataRepr, first_entry: &DataInfo, data_info: &DataInfo, ) -> (u16, u16) { - let (in_val, out_val, first_entry_tot_val) = match chart_type { + let (in_val, out_val, first_entry_tot_val) = match data_repr { ChartType::Packets => ( data_info.incoming_packets(), data_info.outgoing_packets(), @@ -881,7 +870,6 @@ fn sort_arrows<'a>( #[cfg(test)] mod tests { - use crate::chart::types::chart_type::ChartType; use crate::gui::pages::overview_page::{MIN_BARS_LENGTH, get_bars_length}; use crate::networking::types::data_info::DataInfo; diff --git a/src/gui/pages/settings_notifications_page.rs b/src/gui/pages/settings_notifications_page.rs index 6c10c1c8..2d168e2a 100644 --- a/src/gui/pages/settings_notifications_page.rs +++ b/src/gui/pages/settings_notifications_page.rs @@ -3,7 +3,6 @@ use iced::widget::{Checkbox, Column, Container, Row, Scrollable, Space, Text, TextInput}; use iced::{Alignment, Font, Length, Padding}; -use crate::chart::types::chart_type::ChartType; use crate::gui::components::button::button_hide; use crate::gui::components::tab::get_settings_tabs; use crate::gui::pages::types::settings_page::SettingsPage; @@ -14,6 +13,7 @@ use crate::gui::styles::text::TextType; use crate::gui::styles::types::gradient_type::GradientType; use crate::gui::types::message::Message; +use crate::networking::types::data_representation::DataRepr; use crate::notifications::types::notifications::{ DataNotification, FavoriteNotification, Notification, }; @@ -144,7 +144,7 @@ fn get_data_notify<'a>( data_notification, language, font, - data_notification.chart_type, + data_notification.data_repr, ); let input_row = input_group_bytes(data_notification, font, language); let sound_row = sound_buttons(Notification::Data(data_notification), font, language); @@ -372,7 +372,7 @@ fn row_data_representation<'a>( data_notification: DataNotification, language: Language, font: Font, - chart_type: ChartType, + data_repr: DataRepr, ) -> Row<'a, Message, StyleType> { let mut ret_val = Row::new() .width(Length::Shrink) @@ -381,8 +381,8 @@ fn row_data_representation<'a>( .push(Space::with_width(45)) .push(Text::new(format!("{}:", data_representation_translation(language))).font(font)); - for option in ChartType::ALL { - let is_active = chart_type.eq(&option); + for option in DataRepr::ALL { + let is_active = data_repr.eq(&option); ret_val = ret_val.push( Button::new( Text::new(option.get_label(language).to_owned()) @@ -400,7 +400,7 @@ fn row_data_representation<'a>( }) .on_press(Message::UpdateNotificationSettings( Notification::Data(DataNotification { - chart_type: option, + data_repr: option, ..data_notification }), false, diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs index 47bbec31..5d5bd96d 100644 --- a/src/gui/pages/thumbnail_page.rs +++ b/src/gui/pages/thumbnail_page.rs @@ -4,7 +4,6 @@ use iced::widget::{Column, Container, Row, Rule, Space, Text, vertical_space}; use iced::{Alignment, Font, Length}; -use crate::chart::types::chart_type::ChartType; use crate::chart::types::donut_chart::donut_chart; use crate::configs::types::config_settings::ConfigSettings; use crate::countries::country_utils::get_flag_tooltip; @@ -12,6 +11,7 @@ use crate::gui::styles::style_constants::FONT_SIZE_FOOTER; use crate::gui::styles::types::style_type::StyleType; use crate::gui::types::message::Message; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::{Host, ThumbnailHost}; use crate::networking::types::info_traffic::InfoTraffic; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; @@ -41,16 +41,16 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { } let info_traffic = &sniffer.info_traffic; - let chart_type = sniffer.traffic_chart.chart_type; + let data_repr = sniffer.traffic_chart.data_repr; - let (in_data, out_data, filtered_out, dropped) = info_traffic.get_thumbnail_data(chart_type); + let (in_data, out_data, filtered_out, dropped) = info_traffic.get_thumbnail_data(data_repr); let charts = Row::new() .padding(5) .height(Length::Fill) .align_y(Alignment::Center) .push(donut_chart( - chart_type, + data_repr, in_data, out_data, filtered_out, @@ -71,14 +71,14 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { .align_y(Alignment::Start) .push(host_col( info_traffic, - chart_type, + data_repr, font, sniffer.host_sort_type, )) .push(Rule::vertical(10)) .push(service_col( info_traffic, - chart_type, + data_repr, font, sniffer.service_sort_type, )); @@ -93,7 +93,7 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { fn host_col<'a>( info_traffic: &InfoTraffic, - chart_type: ChartType, + data_repr: DataRepr, font: Font, sort_type: SortType, ) -> Column<'a, Message, StyleType> { @@ -101,7 +101,7 @@ fn host_col<'a>( .padding([0, 5]) .spacing(3) .width(Length::FillPortion(2)); - let hosts = get_host_entries(info_traffic, chart_type, sort_type); + let hosts = get_host_entries(info_traffic, data_repr, sort_type); let mut thumbnail_hosts = Vec::new(); for (host, data_info_host) in &hosts { @@ -136,12 +136,12 @@ fn host_col<'a>( fn service_col<'a>( info_traffic: &InfoTraffic, - chart_type: ChartType, + data_repr: DataRepr, font: Font, sort_type: SortType, ) -> Column<'a, Message, StyleType> { let mut service_col = Column::new().padding([0, 5]).spacing(3).width(Length::Fill); - let services = get_service_entries(info_traffic, chart_type, sort_type); + let services = get_service_entries(info_traffic, data_repr, sort_type); let n_entry = min(services.len(), MAX_ENTRIES); for (service, _) in services.get(..n_entry).unwrap_or_default() { service_col = service_col.push( diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index c8251247..afbceedf 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -304,7 +304,7 @@ pub fn update(&mut self, message: Message) -> Task { } self.filters.port_str = value; } - Message::ChartSelection(unit) => self.traffic_chart.change_kind(unit), + Message::DataReprSelection(unit) => self.traffic_chart.change_kind(unit), Message::ReportSortSelection(sort) => { self.page_number = 1; self.report_sort_type = sort; @@ -868,7 +868,7 @@ fn update_notifications_settings(&mut self, notification: Notification, emit_sou let notifications = self.configs.settings.notifications; let sound = match notification { Notification::Data(DataNotification { - chart_type, + data_repr, threshold, byte_multiple, sound, @@ -880,7 +880,7 @@ fn update_notifications_settings(&mut self, notification: Notification, emit_sou || temp_threshold.previous_threshold != previous_threshold { temp_threshold = DataNotification { - chart_type, + data_repr, threshold, byte_multiple, sound, @@ -910,7 +910,7 @@ fn update_notifications_settings(&mut self, notification: Notification, emit_sou .settings .notifications .data_notification - .chart_type = chart_type; + .data_repr = data_repr; sound } Notification::Favorite(favorite_notification) => { @@ -1200,13 +1200,13 @@ fn test_correctly_update_protocol() { fn test_correctly_update_chart_kind() { let mut sniffer = Sniffer::new(Configs::default()); - assert_eq!(sniffer.traffic_chart.chart_type, ChartType::Bytes); - sniffer.update(Message::ChartSelection(ChartType::Packets)); - assert_eq!(sniffer.traffic_chart.chart_type, ChartType::Packets); - sniffer.update(Message::ChartSelection(ChartType::Packets)); - assert_eq!(sniffer.traffic_chart.chart_type, ChartType::Packets); - sniffer.update(Message::ChartSelection(ChartType::Bytes)); - assert_eq!(sniffer.traffic_chart.chart_type, ChartType::Bytes); + assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Bytes); + sniffer.update(Message::DataReprSelection(ChartType::Packets)); + assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Packets); + sniffer.update(Message::DataReprSelection(ChartType::Packets)); + assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Packets); + sniffer.update(Message::DataReprSelection(ChartType::Bytes)); + assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Bytes); } #[test] @@ -1662,7 +1662,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { let mut sniffer = Sniffer::new(Configs::default()); let bytes_notification_init = DataNotification { - chart_type: ChartType::Bytes, + data_repr: ChartType::Bytes, threshold: None, byte_multiple: ByteMultiple::KB, sound: Sound::Pop, @@ -1670,7 +1670,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { }; let bytes_notification_toggled_on = DataNotification { - chart_type: ChartType::Bytes, + data_repr: ChartType::Bytes, threshold: Some(800_000), byte_multiple: ByteMultiple::GB, sound: Sound::Pop, @@ -1678,7 +1678,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { }; let bytes_notification_adjusted_threshold_sound_off = DataNotification { - chart_type: ChartType::Bytes, + data_repr: ChartType::Bytes, threshold: Some(3), byte_multiple: ByteMultiple::KB, sound: Sound::None, @@ -1686,7 +1686,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { }; let bytes_notification_sound_off_only = DataNotification { - chart_type: ChartType::Bytes, + data_repr: ChartType::Bytes, threshold: Some(800_000), byte_multiple: ByteMultiple::GB, sound: Sound::None, @@ -1806,7 +1806,7 @@ fn test_clear_all_notifications() { VecDeque::from([LoggedNotification::DataThresholdExceeded( DataThresholdExceeded { id: 1, - chart_type: ChartType::Packets, + data_repr: ChartType::Packets, threshold: 0, data_info: DataInfo::default(), timestamp: "".to_string(), diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index 847c2cb4..d2a39194 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -5,6 +5,7 @@ use crate::gui::pages::types::running_page::RunningPage; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::types::gradient_type::GradientType; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::info_traffic::InfoTraffic; use crate::notifications::types::notifications::Notification; @@ -12,7 +13,7 @@ use crate::report::types::sort_type::SortType; use crate::utils::types::file_info::FileInfo; use crate::utils::types::web_page::WebPage; -use crate::{ChartType, IpVersion, Language, Protocol, ReportSortType, StyleType}; +use crate::{IpVersion, Language, Protocol, ReportSortType, StyleType}; #[derive(Debug, Clone)] /// Messages types that permit reacting to application interactions/subscriptions @@ -31,8 +32,8 @@ pub enum Message { AddressFilter(String), /// Changed port filter PortFilter(String), - /// Select chart type to be displayed - ChartSelection(ChartType), + /// Select data representation to use + DataReprSelection(DataRepr), /// Select report sort type to be displayed (inspect page) ReportSortSelection(ReportSortType), /// Select host sort type to be displayed (overview page) diff --git a/src/main.rs b/src/main.rs index 04588a65..230d5785 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ use iced::window::settings::PlatformSpecific; use iced::{Font, Pixels, Settings, application, window}; -use chart::types::chart_type::ChartType; use chart::types::traffic_chart::TrafficChart; use cli::handle_cli_args; use configs::types::config_device::ConfigDevice; @@ -18,7 +17,7 @@ use gui::sniffer::Sniffer; use gui::styles::style_constants::FONT_SIZE_BODY; use gui::styles::types::style_type::StyleType; -use networking::types::byte_multiple::ByteMultiple; +use networking::types::data_representation::ByteMultiple; use networking::types::info_traffic::InfoTraffic; use networking::types::ip_version::IpVersion; use networking::types::protocol::Protocol; diff --git a/src/networking/types/data_info.rs b/src/networking/types/data_info.rs index 5c5d99af..752126fe 100644 --- a/src/networking/types/data_info.rs +++ b/src/networking/types/data_info.rs @@ -1,6 +1,6 @@ //! Module defining the `DataInfo` struct, which represents incoming and outgoing packets and bytes. -use crate::chart::types::chart_type::ChartType; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::traffic_direction::TrafficDirection; use crate::report::types::sort_type::SortType; use std::cmp::Ordering; @@ -47,8 +47,8 @@ pub fn tot_bytes(&self) -> u128 { self.incoming_bytes + self.outgoing_bytes } - pub fn tot_data(&self, chart_type: ChartType) -> u128 { - match chart_type { + pub fn tot_data(&self, data_repr: DataRepr) -> u128 { + match data_repr { ChartType::Packets => self.tot_packets(), ChartType::Bytes => self.tot_bytes(), } @@ -103,8 +103,8 @@ pub fn refresh(&mut self, rhs: Self) { self.final_instant = rhs.final_instant; } - pub fn compare(&self, other: &Self, sort_type: SortType, chart_type: ChartType) -> Ordering { - match chart_type { + pub fn compare(&self, other: &Self, sort_type: SortType, data_repr: DataRepr) -> Ordering { + match data_repr { ChartType::Packets => match sort_type { SortType::Ascending => self.tot_packets().cmp(&other.tot_packets()), SortType::Descending => other.tot_packets().cmp(&self.tot_packets()), diff --git a/src/networking/types/byte_multiple.rs b/src/networking/types/data_representation.rs similarity index 74% rename from src/networking/types/byte_multiple.rs rename to src/networking/types/data_representation.rs index 670d090a..b8c9daa7 100644 --- a/src/networking/types/byte_multiple.rs +++ b/src/networking/types/data_representation.rs @@ -1,8 +1,73 @@ -use std::fmt; - +use crate::networking::types::data_info::DataInfo; +use crate::translations::translations::{ + bytes_exceeded_translation, bytes_translation, packets_exceeded_translation, + packets_translation, +}; +use crate::translations::translations_4::{bits_exceeded_translation, bits_translation}; +use crate::translations::types::language::Language; use serde::{Deserialize, Serialize}; -/// Enum representing the possible observed values of IP protocol version. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum DataRepr { + Packets, + Bytes, + Bits, +} + +impl DataRepr { + pub(crate) const ALL: [DataRepr; 3] = [DataRepr::Bits, DataRepr::Bytes, DataRepr::Packets]; + + pub fn get_label(&self, language: Language) -> &str { + match self { + DataRepr::Packets => packets_translation(language), + DataRepr::Bytes => bytes_translation(language), + DataRepr::Bits => bits_translation(language), + } + } + + /// Returns a String representing a quantity of traffic (packets / bytes / bits) with the proper multiple if applicable + pub fn formatted_string(&self, amount: u128) -> String { + if self == &DataRepr::Packets { + return amount.to_string(); + } + + #[allow(clippy::cast_precision_loss)] + let mut n = amount as f32; + + let byte_multiple = ByteMultiple::from_amount(amount); + + #[allow(clippy::cast_precision_loss)] + let multiplier = byte_multiple.multiplier() as f32; + n /= multiplier; + if n > 999.0 && byte_multiple != ByteMultiple::PB { + // this allows representing e.g. 999_999 as 999 KB instead of 1000 KB + n = 999.0; + } + let precision = usize::from(byte_multiple != ByteMultiple::B && n <= 9.95); + format!("{n:.precision$} {}", byte_multiple.pretty_print(*self)) + .trim() + .to_string() + } + + pub fn formatted_string_from_data_info(&self, data_info: &DataInfo) -> String { + let amount = match self { + DataRepr::Packets => data_info.tot_packets(), + DataRepr::Bytes => data_info.tot_bytes(), + DataRepr::Bits => data_info.tot_bytes() * 8, + }; + self.formatted_string(amount) + } + + pub fn data_exceeded_translation(&self, language: Language) -> &str { + match self { + DataRepr::Packets => packets_exceeded_translation(language), + DataRepr::Bytes => bytes_exceeded_translation(language), + DataRepr::Bits => bits_exceeded_translation(language), + } + } +} + +/// Represents a Byte or bit multiple for displaying values in a human-readable format. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ByteMultiple { /// A Byte @@ -19,12 +84,6 @@ pub enum ByteMultiple { PB, } -impl fmt::Display for ByteMultiple { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{self:?}") - } -} - impl ByteMultiple { pub fn multiplier(self) -> u64 { match self { @@ -37,7 +96,7 @@ pub fn multiplier(self) -> u64 { } } - fn from_num_bytes(bytes: u128) -> Self { + fn from_amount(bytes: u128) -> Self { match bytes { x if (u128::MIN..u128::from(ByteMultiple::KB.multiplier())).contains(&x) => { ByteMultiple::B @@ -71,10 +130,14 @@ fn from_num_bytes(bytes: u128) -> Self { } pub fn get_char(self) -> String { - self.to_string() - .strip_suffix('B') - .unwrap_or_default() - .to_owned() + match self { + Self::B => String::new(), + Self::KB => "K".to_string(), + Self::MB => "M".to_string(), + Self::GB => "G".to_string(), + Self::TB => "T".to_string(), + Self::PB => "P".to_string(), + } } pub fn from_char(ch: char) -> Self { @@ -88,22 +151,12 @@ pub fn from_char(ch: char) -> Self { } } - /// Returns a String representing a quantity of bytes with its proper multiple (B, KB, MB, GB, TB) - pub fn formatted_string(bytes: u128) -> String { - #[allow(clippy::cast_precision_loss)] - let mut n = bytes as f32; - - let byte_multiple = ByteMultiple::from_num_bytes(bytes); - - #[allow(clippy::cast_precision_loss)] - let multiplier = byte_multiple.multiplier() as f32; - n /= multiplier; - if n > 999.0 && byte_multiple != ByteMultiple::PB { - // this allows representing e.g. 999_999 as 999 KB instead of 1000 KB - n = 999.0; + pub fn pretty_print(&self, repr: DataRepr) -> String { + match repr { + DataRepr::Packets => String::new(), + DataRepr::Bytes => format!("{}B", self.get_char()), + DataRepr::Bits => format!("{}b", self.get_char()), } - let precision = usize::from(byte_multiple != ByteMultiple::B && n <= 9.95); - format!("{n:.precision$} {byte_multiple}") } } diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index 976e78c7..73229df4 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -1,8 +1,8 @@ use crate::Service; -use crate::chart::types::chart_type::ChartType; use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::utils::types::timestamp::Timestamp; @@ -65,8 +65,8 @@ pub fn refresh(&mut self, msg: &mut InfoTraffic) { } } - pub fn get_thumbnail_data(&self, chart_type: ChartType) -> (u128, u128, u128, u128) { - if chart_type.eq(&ChartType::Bytes) { + pub fn get_thumbnail_data(&self, data_repr: DataRepr) -> (u128, u128, u128, u128) { + if data_repr.eq(&ChartType::Bytes) { ( self.tot_data_info.incoming_bytes(), self.tot_data_info.outgoing_bytes(), diff --git a/src/networking/types/mod.rs b/src/networking/types/mod.rs index 8553faaf..2dcc2ab2 100644 --- a/src/networking/types/mod.rs +++ b/src/networking/types/mod.rs @@ -2,10 +2,10 @@ pub mod arp_type; pub mod asn; pub mod bogon; -pub mod byte_multiple; pub mod capture_context; pub mod data_info; pub mod data_info_host; +pub mod data_representation; pub mod filters; pub mod host; pub mod host_data_states; diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index 93acddd9..67a27a8c 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -1,8 +1,8 @@ use crate::InfoTraffic; -use crate::chart::types::chart_type::ChartType; use crate::networking::types::capture_context::CaptureSource; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; use crate::networking::types::service::Service; use crate::notifications::types::logged_notification::{ @@ -31,8 +31,8 @@ pub fn notify_and_log( let data_info = info_traffic_msg.tot_data_info; // data threshold if let Some(threshold) = notifications.data_notification.threshold { - let chart_type = notifications.data_notification.chart_type; - if data_info.tot_data(chart_type) > u128::from(threshold) { + let data_repr = notifications.data_notification.data_repr; + if data_info.tot_data(data_repr) > u128::from(threshold) { //log this notification logged_notifications.1 += 1; if logged_notifications.0.len() >= 30 { @@ -43,13 +43,13 @@ pub fn notify_and_log( .push_front(LoggedNotification::DataThresholdExceeded( DataThresholdExceeded { id: logged_notifications.1, - chart_type, + data_repr, threshold: notifications.data_notification.previous_threshold, data_info, timestamp: get_formatted_timestamp(timestamp), is_expanded: false, - hosts: hosts_list(info_traffic_msg, chart_type), - services: services_list(info_traffic_msg, chart_type), + hosts: hosts_list(info_traffic_msg, data_repr), + services: services_list(info_traffic_msg, data_repr), }, )); if sound_to_play.eq(&Sound::None) { @@ -98,7 +98,7 @@ pub fn notify_and_log( logged_notifications.1 - emitted_notifications_prev } -fn hosts_list(info_traffic_msg: &InfoTraffic, chart_type: ChartType) -> Vec<(Host, DataInfoHost)> { +fn hosts_list(info_traffic_msg: &InfoTraffic, data_repr: DataRepr) -> Vec<(Host, DataInfoHost)> { let mut hosts: Vec<(Host, DataInfoHost)> = info_traffic_msg .hosts .iter() @@ -106,7 +106,7 @@ fn hosts_list(info_traffic_msg: &InfoTraffic, chart_type: ChartType) -> Vec<(Hos .collect(); hosts.sort_by(|(_, a), (_, b)| { a.data_info - .compare(&b.data_info, SortType::Descending, chart_type) + .compare(&b.data_info, SortType::Descending, data_repr) }); let n_entry = min(hosts.len(), 4); hosts @@ -117,17 +117,14 @@ fn hosts_list(info_traffic_msg: &InfoTraffic, chart_type: ChartType) -> Vec<(Hos .collect() } -fn services_list( - info_traffic_msg: &InfoTraffic, - chart_type: ChartType, -) -> Vec<(Service, DataInfo)> { +fn services_list(info_traffic_msg: &InfoTraffic, data_repr: DataRepr) -> Vec<(Service, DataInfo)> { let mut services: Vec<(Service, DataInfo)> = info_traffic_msg .services .iter() .filter(|(service, _)| service != &&Service::NotApplicable) .map(|(s, data)| (*s, *data)) .collect(); - services.sort_by(|(_, a), (_, b)| a.compare(b, SortType::Descending, chart_type)); + services.sort_by(|(_, a), (_, b)| a.compare(b, SortType::Descending, data_repr)); let n_entry = min(services.len(), 4); services .get(..n_entry) diff --git a/src/notifications/types/logged_notification.rs b/src/notifications/types/logged_notification.rs index 6a75d2cd..1b354370 100644 --- a/src/notifications/types/logged_notification.rs +++ b/src/notifications/types/logged_notification.rs @@ -1,6 +1,6 @@ -use crate::chart::types::chart_type::ChartType; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; use crate::networking::types::service::Service; @@ -38,7 +38,7 @@ pub fn expand(&mut self, expand: bool) { #[derive(Clone)] pub struct DataThresholdExceeded { pub(crate) id: usize, - pub(crate) chart_type: ChartType, + pub(crate) data_repr: DataRepr, pub(crate) threshold: u64, pub(crate) data_info: DataInfo, pub(crate) timestamp: String, diff --git a/src/notifications/types/notifications.rs b/src/notifications/types/notifications.rs index 8935ad07..f7d6b5fe 100644 --- a/src/notifications/types/notifications.rs +++ b/src/notifications/types/notifications.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::ByteMultiple; -use crate::chart::types::chart_type::ChartType; +use crate::networking::types::data_representation::DataRepr; use crate::notifications::types::sound::Sound; /// Used to contain the notifications configuration set by the user @@ -34,7 +34,7 @@ pub enum Notification { #[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy)] pub struct DataNotification { /// Data representation - pub chart_type: ChartType, + pub data_repr: DataRepr, /// Threshold of received + sent bytes; if exceeded a notification is emitted pub threshold: Option, /// B, KB, MB or GB @@ -48,7 +48,7 @@ pub struct DataNotification { impl Default for DataNotification { fn default() -> Self { DataNotification { - chart_type: ChartType::Bytes, + data_repr: DataRepr::Bytes, threshold: None, byte_multiple: ByteMultiple::KB, sound: Sound::Pop, diff --git a/src/report/get_report_entries.rs b/src/report/get_report_entries.rs index f7742a7d..b6f7979b 100644 --- a/src/report/get_report_entries.rs +++ b/src/report/get_report_entries.rs @@ -4,10 +4,11 @@ use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::report::types::sort_type::SortType; -use crate::{ChartType, InfoTraffic, ReportSortType, Service, Sniffer}; +use crate::{InfoTraffic, ReportSortType, Service, Sniffer}; /// 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, @@ -82,12 +83,12 @@ pub fn get_searched_entries( pub fn get_host_entries( info_traffic: &InfoTraffic, - chart_type: ChartType, + data_repr: DataRepr, sort_type: SortType, ) -> Vec<(Host, DataInfoHost)> { let mut sorted_vec: Vec<(&Host, &DataInfoHost)> = info_traffic.hosts.iter().collect(); - sorted_vec.sort_by(|&(_, a), &(_, b)| a.data_info.compare(&b.data_info, sort_type, chart_type)); + 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] @@ -98,7 +99,7 @@ pub fn get_host_entries( pub fn get_service_entries( info_traffic: &InfoTraffic, - chart_type: ChartType, + data_repr: DataRepr, sort_type: SortType, ) -> Vec<(Service, DataInfo)> { let mut sorted_vec: Vec<(&Service, &DataInfo)> = info_traffic @@ -107,7 +108,7 @@ pub fn get_service_entries( .filter(|(service, _)| service != &&Service::NotApplicable) .collect(); - sorted_vec.sort_by(|&(_, a), &(_, b)| a.compare(b, sort_type, chart_type)); + 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] diff --git a/src/report/types/report_col.rs b/src/report/types/report_col.rs index 1ec1877f..71795c55 100644 --- a/src/report/types/report_col.rs +++ b/src/report/types/report_col.rs @@ -1,5 +1,5 @@ -use crate::ByteMultiple; use crate::networking::types::address_port_pair::AddressPortPair; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::report::types::search_parameters::FilterInputType; use crate::translations::translations::{ @@ -25,6 +25,7 @@ pub enum ReportCol { DstPort, Proto, Service, + Bits, Bytes, Packets, } @@ -92,8 +93,9 @@ pub(crate) fn get_value(&self, key: &AddressPortPair, val: &InfoAddressPortPair) } ReportCol::Proto => key.protocol.to_string(), ReportCol::Service => val.service.to_string(), - ReportCol::Bytes => ByteMultiple::formatted_string(val.transmitted_bytes), - ReportCol::Packets => val.transmitted_packets.to_string(), + ReportCol::Bits => DataRepr::Bits.formatted_string(val.transmitted_bytes * 8), + ReportCol::Bytes => DataRepr::Bytes.formatted_string(val.transmitted_bytes), + ReportCol::Packets => DataRepr::Packets.formatted_string(val.transmitted_packets), } } From 4a34e7559785e2b3fda8f1945d73c66b41687b62 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 14 Aug 2025 12:17:12 +0200 Subject: [PATCH 35/74] added bits data representation in charts, notifications, bars, etc. --- src/chart/manage_chart_data.rs | 20 ++++-- src/chart/types/traffic_chart.rs | 22 +++--- src/gui/pages/connection_details_page.rs | 20 +++--- src/gui/pages/inspect_page.rs | 68 +++++++++---------- src/gui/pages/notifications_page.rs | 4 +- src/gui/pages/overview_page.rs | 65 +++++++++--------- src/gui/pages/thumbnail_page.rs | 5 +- src/gui/sniffer.rs | 5 +- src/networking/types/data_info.rs | 52 +++++--------- src/networking/types/data_representation.rs | 18 ++--- .../types/info_address_port_pair.rs | 23 +++++++ src/networking/types/info_traffic.rs | 35 +++++----- src/report/get_report_entries.rs | 26 ++----- src/report/types/report_col.rs | 36 ++++------ src/report/types/report_sort_type.rs | 35 +++------- src/translations/translations_4.rs | 6 +- 16 files changed, 205 insertions(+), 235 deletions(-) diff --git a/src/chart/manage_chart_data.rs b/src/chart/manage_chart_data.rs index a2fc5091..7d361fb7 100644 --- a/src/chart/manage_chart_data.rs +++ b/src/chart/manage_chart_data.rs @@ -1,7 +1,7 @@ -use splines::{Interpolation, Key, Spline}; - use crate::TrafficChart; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::info_traffic::InfoTraffic; +use splines::{Interpolation, Key, Spline}; impl TrafficChart { pub fn update_charts_data(&mut self, info_traffic_msg: &InfoTraffic, no_more_packets: bool) { @@ -16,13 +16,21 @@ pub fn update_charts_data(&mut self, info_traffic_msg: &InfoTraffic, no_more_pac self.ticks += 1; #[allow(clippy::cast_precision_loss)] - let out_bytes_entry = -(info_traffic_msg.tot_data_info.outgoing_bytes() as f32); + let out_bytes_entry = -(info_traffic_msg + .tot_data_info + .outgoing_data(DataRepr::Bytes) as f32); #[allow(clippy::cast_precision_loss)] - let in_bytes_entry = info_traffic_msg.tot_data_info.incoming_bytes() as f32; + let in_bytes_entry = info_traffic_msg + .tot_data_info + .incoming_data(DataRepr::Bytes) as f32; #[allow(clippy::cast_precision_loss)] - let out_packets_entry = -(info_traffic_msg.tot_data_info.outgoing_packets() as f32); + let out_packets_entry = -(info_traffic_msg + .tot_data_info + .outgoing_data(DataRepr::Packets) as f32); #[allow(clippy::cast_precision_loss)] - let in_packets_entry = info_traffic_msg.tot_data_info.incoming_packets() as f32; + let in_packets_entry = info_traffic_msg + .tot_data_info + .incoming_data(DataRepr::Packets) as f32; let out_bytes_point = (tot_seconds, out_bytes_entry); let in_bytes_point = (tot_seconds, in_bytes_entry); diff --git a/src/chart/types/traffic_chart.rs b/src/chart/types/traffic_chart.rs index 19dd47bb..35983e8c 100644 --- a/src/chart/types/traffic_chart.rs +++ b/src/chart/types/traffic_chart.rs @@ -171,8 +171,9 @@ fn x_axis_range(&self) -> Range { fn y_axis_range(&self) -> Range { let (min, max) = match self.data_repr { - ChartType::Packets => (self.min_packets, self.max_packets), - ChartType::Bytes => (self.min_bytes, self.max_bytes), + DataRepr::Packets => (self.min_packets, self.max_packets), + DataRepr::Bytes => (self.min_bytes, self.max_bytes), + DataRepr::Bits => (self.min_bytes * 8.0, self.max_bytes * 8.0), }; let fs = max - min; let gap = fs * 0.05; @@ -188,11 +189,11 @@ fn font<'a>(&self, size: f64) -> TextStyle<'a> { fn spline_to_plot(&self, direction: TrafficDirection) -> &Spline { match self.data_repr { - ChartType::Packets => match direction { + DataRepr::Packets => match direction { TrafficDirection::Incoming => &self.in_packets.spline, TrafficDirection::Outgoing => &self.out_packets.spline, }, - ChartType::Bytes => match direction { + DataRepr::Bytes | DataRepr::Bits => match direction { TrafficDirection::Incoming => &self.in_bytes.spline, TrafficDirection::Outgoing => &self.out_bytes.spline, }, @@ -220,11 +221,16 @@ fn area_series( let color = self.series_color(direction); let alpha = self.style.get_extension().alpha_chart_badge; let spline = self.spline_to_plot(direction); + let multiplier = if self.data_repr == DataRepr::Bits { + 8.0 + } else { + 1.0 + }; let data = match spline.keys() { // if we have only one tick, we need to add a second point to draw the area - [k] => vec![(0.0, k.value), (0.1, k.value)], - _ => sample_spline(spline), + [k] => vec![(0.0, k.value * multiplier), (0.1, k.value * multiplier)], + _ => sample_spline(spline, multiplier), }; AreaSeries::new(data, 0.0, color.mix(alpha.into())) @@ -330,7 +336,7 @@ fn build_chart( } } -fn sample_spline(spline: &Spline) -> Vec<(f32, f32)> { +fn sample_spline(spline: &Spline, multiplier: f32) -> Vec<(f32, f32)> { let pts = spline.len() * 10; // 10 samples per key let mut ret_val = Vec::new(); let len = spline.len(); @@ -347,7 +353,7 @@ fn sample_spline(spline: &Spline) -> Vec<(f32, f32)> { for i in 0..pts { #[allow(clippy::cast_precision_loss)] let x = first_x + delta * i as f32; - let p = spline.clamped_sample(x).unwrap_or_default(); + let p = spline.clamped_sample(x).unwrap_or_default() * multiplier; ret_val.push((x, p)); } ret_val diff --git a/src/gui/pages/connection_details_page.rs b/src/gui/pages/connection_details_page.rs index 9ac47694..1bd0a2f6 100644 --- a/src/gui/pages/connection_details_page.rs +++ b/src/gui/pages/connection_details_page.rs @@ -15,6 +15,7 @@ use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::arp_type::ArpType; use crate::networking::types::bogon::is_bogon; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; use crate::networking::types::icmp_type::IcmpType; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; @@ -33,7 +34,7 @@ }; use crate::utils::formatted_strings::{get_formatted_timestamp, get_socket_address}; use crate::utils::types::icon::Icon; -use crate::{ByteMultiple, ConfigSettings, Language, Protocol, Sniffer, StyleType}; +use crate::{ConfigSettings, Language, Protocol, Sniffer, StyleType}; use iced::alignment::Vertical; use iced::widget::scrollable::Direction; use iced::widget::tooltip::Position; @@ -55,6 +56,7 @@ fn page_content<'a>(sniffer: &Sniffer, key: &AddressPortPair) -> Container<'a, M color_gradient, .. } = sniffer.configs.settings; + let data_repr = sniffer.traffic_chart.data_repr; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -130,7 +132,7 @@ fn page_content<'a>(sniffer: &Sniffer, key: &AddressPortPair) -> Container<'a, M dest_col = dest_col.push(host_info_col); } - let col_info = col_info(key, &val, font, language); + let col_info = col_info(key, &val, data_repr, font, language); let content = assemble_widgets(col_info, source_col, dest_col); @@ -172,6 +174,7 @@ fn page_header<'a>( fn col_info<'a>( key: &AddressPortPair, val: &InfoAddressPortPair, + data_repr: DataRepr, font: Font, language: Language, ) -> Column<'a, Message, StyleType> { @@ -221,12 +224,13 @@ fn col_info<'a>( incoming_translation(language).to_lowercase() } ), - &format!( - "{}\n {} {}", - ByteMultiple::formatted_string(val.transmitted_bytes), - val.transmitted_packets, - packets_translation(language) - ), + &(data_repr.formatted_string(val.transmitted_data(data_repr)) + + if data_repr == DataRepr::Packets { + format!(" {}", packets_translation(language)) + } else { + String::new() + } + .as_ref()), font, )); diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index f995de7f..b5c91a2a 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -23,7 +23,7 @@ use crate::gui::types::message::Message; use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::data_info::DataInfo; -use crate::networking::types::data_representation::{ByteMultiple, DataRepr}; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host_data_states::HostStates; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::networking::types::traffic_direction::TrafficDirection; @@ -75,6 +75,7 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { &sniffer.search, font, sniffer.report_sort_type, + sniffer.traffic_chart.data_repr, )) .push(Space::with_height(4)) .push(Rule::horizontal(5)) @@ -96,7 +97,7 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { .align_y(Alignment::Center) .align_x(Alignment::Center) .padding(Padding::new(7.0).top(10).bottom(3)) - .width(1042) + .width(947) .class(ContainerType::BorderedRound), ); @@ -107,6 +108,7 @@ fn report<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { let ConfigSettings { style, language, .. } = sniffer.configs.settings; + let data_repr = sniffer.traffic_chart.data_repr; let font = style.get_extension().font; let (search_results, results_number, agglomerate) = get_searched_entries(sniffer); @@ -121,12 +123,17 @@ fn report<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { let end_entry_num = start_entry_num + search_results.len() - 1; for report_entry in search_results { scroll_report = scroll_report.push( - button(row_report_entry(&report_entry.0, &report_entry.1, font)) - .padding(2) - .on_press(Message::ShowModal(MyModal::ConnectionDetails( - report_entry.0, - ))) - .class(ButtonType::Neutral), + button(row_report_entry( + &report_entry.0, + &report_entry.1, + data_repr, + font, + )) + .padding(2) + .on_press(Message::ShowModal(MyModal::ConnectionDetails( + report_entry.0, + ))) + .class(ButtonType::Neutral), ); } if results_number > 0 { @@ -177,11 +184,12 @@ fn report_header_row( search_params: &SearchParameters, font: Font, sort_type: ReportSortType, + data_repr: DataRepr, ) -> Row<'_, Message, StyleType> { let mut ret_val = Row::new().padding([0, 2]).align_y(Alignment::Center); for report_col in ReportCol::ALL { let (title_display, title_small_display, tooltip_val) = - title_report_col_display(&report_col, language); + title_report_col_display(&report_col, data_repr, language); let title_row = Row::new() .align_y(Alignment::End) .push(Text::new(title_display).font(font)) @@ -207,7 +215,9 @@ fn report_header_row( .width(report_col.get_width()) .height(56) .push(title_tooltip); - if report_col != ReportCol::Packets && report_col != ReportCol::Bytes { + if report_col == ReportCol::Data { + col_header = col_header.push(sort_arrows(sort_type)); + } else { col_header = col_header.push( Container::new(filter_input( report_col.get_filter_input_type(), @@ -217,8 +227,6 @@ fn report_header_row( .height(Length::Fill) .align_y(Alignment::Center), ); - } else { - col_header = col_header.push(sort_arrows(sort_type, &report_col)); } ret_val = ret_val.push(col_header); } @@ -227,10 +235,11 @@ fn report_header_row( fn title_report_col_display( report_col: &ReportCol, + data_repr: DataRepr, language: Language, ) -> (String, String, String) { let max_chars = report_col.get_max_chars(Some(language)); - let title = report_col.get_title(language); + let title = report_col.get_title(language, data_repr); let title_direction_info = report_col.get_title_direction_info(language); let chars_title = title.chars().collect::>(); let chars_title_direction_info = title_direction_info.chars().collect::>(); @@ -260,21 +269,16 @@ fn title_report_col_display( } } -fn sort_arrows<'a>( - active_sort_type: ReportSortType, - report_col: &ReportCol, -) -> Container<'a, Message, StyleType> { +fn sort_arrows<'a>(active_sort_type: ReportSortType) -> Container<'a, Message, StyleType> { Container::new( button( active_sort_type - .icon(report_col) + .icon() .align_x(Alignment::Center) .align_y(Alignment::Center), ) - .class(active_sort_type.button_type(report_col)) - .on_press(Message::ReportSortSelection( - active_sort_type.next_sort(report_col), - )), + .class(active_sort_type.button_type()) + .on_press(Message::ReportSortSelection(active_sort_type.next_sort())), ) .align_y(Alignment::Center) .height(Length::Fill) @@ -283,6 +287,7 @@ fn sort_arrows<'a>( fn row_report_entry<'a>( key: &AddressPortPair, val: &InfoAddressPortPair, + data_repr: DataRepr, font: Font, ) -> Row<'a, Message, StyleType> { let text_type = if val.traffic_direction == TrafficDirection::Outgoing { @@ -295,7 +300,7 @@ fn row_report_entry<'a>( for report_col in ReportCol::ALL { let max_chars = report_col.get_max_chars(None); - let col_value = report_col.get_value(key, val); + let col_value = report_col.get_value(key, val, data_repr); ret_val = ret_val.push( Container::new( Text::new(if col_value.len() <= max_chars { @@ -551,29 +556,20 @@ fn get_agglomerates_row<'a>( tot: DataInfo, data_repr: DataRepr, ) -> Row<'a, Message, StyleType> { - let tot_packets = tot.tot_packets(); - let tot_bytes = tot.tot_bytes(); - let (in_length, out_length) = get_bars_length(data_repr, &tot, &tot); let bars = get_bars(in_length, out_length).width(ReportCol::FILTER_COLUMNS_WIDTH); - let bytes_col = Column::new() + let data_col = Column::new() .align_x(Alignment::Center) - .width(ReportCol::Bytes.get_width()) - .push(Text::new(ByteMultiple::formatted_string(tot_bytes)).font(font)); - - let packets_col = Column::new() - .align_x(Alignment::Center) - .width(ReportCol::Packets.get_width()) - .push(Text::new(tot_packets.to_string()).font(font)); + .width(ReportCol::Data.get_width()) + .push(Text::new(data_repr.formatted_string(tot.tot_data(data_repr))).font(font)); Row::new() .padding([0, 2]) .height(40) .align_y(Alignment::Center) .push(bars) - .push(bytes_col) - .push(packets_col) + .push(data_col) } fn get_change_page_row<'a>( diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 4c8f2728..3e9914c8 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -179,7 +179,7 @@ fn data_notification_log<'a>( .push(Text::new(logged_notification.timestamp.clone()).font(font)), ) .push( - Text::new(data_repr.data_exceeded_translation(language)) + Text::new(data_repr.data_exceeded_translation(language).to_string()) .class(TextType::Title) .font(font), ) @@ -350,7 +350,7 @@ fn threshold_bar<'a>( Column::new() .spacing(1) .push(Row::new().push(horizontal_space()).push( - Text::new(data_repr.formatted_string_from_data_info(&data_info)).font(font), + Text::new(data_repr.formatted_string(data_info.tot_data(data_repr))).font(font), )) .push(get_bars(incoming_bar_len, outgoing_bar_len)), ) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 1ff1fbc2..aeb74995 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -71,7 +71,10 @@ pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { } else { // NO pcap error detected let observed = sniffer.info_traffic.all_packets; - let filtered = sniffer.info_traffic.tot_data_info.tot_packets(); + let filtered = sniffer + .info_traffic + .tot_data_info + .tot_data(DataRepr::Packets); match (observed, filtered) { (0, 0) => { @@ -422,7 +425,7 @@ pub fn host_bar<'a>( .push( Text::new( data_repr - .formatted_string_from_data_info(&data_info_host.data_info), + .formatted_string(data_info_host.data_info.tot_data(data_repr)), ) .font(font), ), @@ -453,7 +456,7 @@ pub fn service_bar<'a>( .push(Text::new(service.to_string()).font(font)) .push(horizontal_space()) .push( - Text::new(data_repr.formatted_string_from_data_info(&data_info)) + Text::new(data_repr.formatted_string(data_info.tot_data(data_repr))) .font(font), ), ) @@ -556,26 +559,29 @@ fn col_data_representation<'a>( .font(font), ); - for option in ChartType::ALL { + let [bits, bytes, packets] = DataRepr::ALL.map(|option| { let is_active = data_repr.eq(&option); - ret_val = ret_val.push( - Button::new( - Text::new(option.get_label(language).to_owned()) - .width(Length::Fill) - .align_x(Alignment::Center) - .align_y(Alignment::Center) - .font(font), - ) - .width(Length::Fill) - .height(33) - .class(if is_active { - ButtonType::BorderedRoundSelected - } else { - ButtonType::BorderedRound - }) - .on_press(Message::DataReprSelection(option)), - ); - } + Button::new( + Text::new(option.get_label(language).to_owned()) + .width(Length::Fill) + .align_x(Alignment::Center) + .align_y(Alignment::Center) + .font(font), + ) + .width(Length::Fill) + .height(33) + .class(if is_active { + ButtonType::BorderedRoundSelected + } else { + ButtonType::BorderedRound + }) + .on_press(Message::DataReprSelection(option)) + }); + + ret_val = ret_val + .push(Row::new().spacing(5).push(bits).push(bytes)) + .push(packets); + ret_val } @@ -695,18 +701,9 @@ pub fn get_bars_length( first_entry: &DataInfo, data_info: &DataInfo, ) -> (u16, u16) { - let (in_val, out_val, first_entry_tot_val) = match data_repr { - ChartType::Packets => ( - data_info.incoming_packets(), - data_info.outgoing_packets(), - first_entry.tot_packets(), - ), - ChartType::Bytes => ( - data_info.incoming_bytes(), - data_info.outgoing_bytes(), - first_entry.tot_bytes(), - ), - }; + let in_val = data_info.incoming_data(data_repr); + let out_val = data_info.outgoing_data(data_repr); + let first_entry_tot_val = first_entry.tot_data(data_repr); let tot_val = in_val + out_val; if tot_val == 0 { diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs index 5d5bd96d..68c4aa43 100644 --- a/src/gui/pages/thumbnail_page.rs +++ b/src/gui/pages/thumbnail_page.rs @@ -27,7 +27,10 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, .. } = sniffer.configs.settings; let font = style.get_extension().font; - let filtered = sniffer.info_traffic.tot_data_info.tot_packets(); + let filtered = sniffer + .info_traffic + .tot_data_info + .tot_data(DataRepr::Packets); if filtered == 0 { return Container::new( diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index afbceedf..3ffc8f58 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -46,6 +46,7 @@ use crate::networking::parse_packets::BackendTrafficMessage; use crate::networking::parse_packets::parse_packets; use crate::networking::types::capture_context::{CaptureContext, CaptureSource, MyPcapImport}; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::filters::Filters; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::host_data_states::HostDataStates; @@ -686,7 +687,7 @@ fn update_threshold(&mut self) { fn refresh_data(&mut self, mut msg: InfoTraffic, no_more_packets: bool) { self.info_traffic.refresh(&mut msg); - if self.info_traffic.tot_data_info.tot_packets() == 0 { + if self.info_traffic.tot_data_info.tot_data(DataRepr::Packets) == 0 { return; } let emitted_notifications = notify_and_log( @@ -949,7 +950,7 @@ fn switch_page(&mut self, next: bool) { true, ) => { // Running with no overlays - if self.info_traffic.tot_data_info.tot_packets() > 0 { + if self.info_traffic.tot_data_info.tot_data(DataRepr::Packets) > 0 { // Running with no overlays and some packets filtered self.running_page = if next { self.running_page.next() diff --git a/src/networking/types/data_info.rs b/src/networking/types/data_info.rs index 752126fe..35f55893 100644 --- a/src/networking/types/data_info.rs +++ b/src/networking/types/data_info.rs @@ -23,35 +23,24 @@ pub struct DataInfo { } impl DataInfo { - pub fn incoming_packets(&self) -> u128 { - self.incoming_packets + pub fn incoming_data(&self, data_repr: DataRepr) -> u128 { + match data_repr { + DataRepr::Packets => self.incoming_packets, + DataRepr::Bytes => self.incoming_bytes, + DataRepr::Bits => self.incoming_bytes * 8, + } } - pub fn outgoing_packets(&self) -> u128 { - self.outgoing_packets - } - - pub fn incoming_bytes(&self) -> u128 { - self.incoming_bytes - } - - pub fn outgoing_bytes(&self) -> u128 { - self.outgoing_bytes - } - - pub fn tot_packets(&self) -> u128 { - self.incoming_packets + self.outgoing_packets - } - - pub fn tot_bytes(&self) -> u128 { - self.incoming_bytes + self.outgoing_bytes + pub fn outgoing_data(&self, data_repr: DataRepr) -> u128 { + match data_repr { + DataRepr::Packets => self.outgoing_packets, + DataRepr::Bytes => self.outgoing_bytes, + DataRepr::Bits => self.outgoing_bytes * 8, + } } pub fn tot_data(&self, data_repr: DataRepr) -> u128 { - match data_repr { - ChartType::Packets => self.tot_packets(), - ChartType::Bytes => self.tot_bytes(), - } + self.incoming_data(data_repr) + self.outgoing_data(data_repr) } pub fn add_packet(&mut self, bytes: u128, traffic_direction: TrafficDirection) { @@ -104,17 +93,10 @@ pub fn refresh(&mut self, rhs: Self) { } pub fn compare(&self, other: &Self, sort_type: SortType, data_repr: DataRepr) -> Ordering { - match data_repr { - ChartType::Packets => match sort_type { - SortType::Ascending => self.tot_packets().cmp(&other.tot_packets()), - SortType::Descending => other.tot_packets().cmp(&self.tot_packets()), - SortType::Neutral => other.final_instant.cmp(&self.final_instant), - }, - ChartType::Bytes => match sort_type { - SortType::Ascending => self.tot_bytes().cmp(&other.tot_bytes()), - SortType::Descending => other.tot_bytes().cmp(&self.tot_bytes()), - SortType::Neutral => other.final_instant.cmp(&self.final_instant), - }, + match sort_type { + SortType::Ascending => self.tot_data(data_repr).cmp(&other.tot_data(data_repr)), + SortType::Descending => other.tot_data(data_repr).cmp(&self.tot_data(data_repr)), + SortType::Neutral => other.final_instant.cmp(&self.final_instant), } } diff --git a/src/networking/types/data_representation.rs b/src/networking/types/data_representation.rs index b8c9daa7..7f0d52c7 100644 --- a/src/networking/types/data_representation.rs +++ b/src/networking/types/data_representation.rs @@ -1,4 +1,3 @@ -use crate::networking::types::data_info::DataInfo; use crate::translations::translations::{ bytes_exceeded_translation, bytes_translation, packets_exceeded_translation, packets_translation, @@ -26,8 +25,8 @@ pub fn get_label(&self, language: Language) -> &str { } /// Returns a String representing a quantity of traffic (packets / bytes / bits) with the proper multiple if applicable - pub fn formatted_string(&self, amount: u128) -> String { - if self == &DataRepr::Packets { + pub fn formatted_string(self, amount: u128) -> String { + if self == DataRepr::Packets { return amount.to_string(); } @@ -44,20 +43,11 @@ pub fn formatted_string(&self, amount: u128) -> String { n = 999.0; } let precision = usize::from(byte_multiple != ByteMultiple::B && n <= 9.95); - format!("{n:.precision$} {}", byte_multiple.pretty_print(*self)) + format!("{n:.precision$} {}", byte_multiple.pretty_print(self)) .trim() .to_string() } - pub fn formatted_string_from_data_info(&self, data_info: &DataInfo) -> String { - let amount = match self { - DataRepr::Packets => data_info.tot_packets(), - DataRepr::Bytes => data_info.tot_bytes(), - DataRepr::Bits => data_info.tot_bytes() * 8, - }; - self.formatted_string(amount) - } - pub fn data_exceeded_translation(&self, language: Language) -> &str { match self { DataRepr::Packets => packets_exceeded_translation(language), @@ -151,7 +141,7 @@ pub fn from_char(ch: char) -> Self { } } - pub fn pretty_print(&self, repr: DataRepr) -> String { + pub fn pretty_print(self, repr: DataRepr) -> String { match repr { DataRepr::Packets => String::new(), DataRepr::Bytes => format!("{}B", self.get_char()), diff --git a/src/networking/types/info_address_port_pair.rs b/src/networking/types/info_address_port_pair.rs index 3e9682d6..e6636323 100644 --- a/src/networking/types/info_address_port_pair.rs +++ b/src/networking/types/info_address_port_pair.rs @@ -1,12 +1,15 @@ //! Module defining the `InfoAddressPortPair` struct, useful to format the output report file and //! to keep track of statistics about the sniffed traffic. +use std::cmp::Ordering; use std::collections::HashMap; use crate::Service; use crate::networking::types::arp_type::ArpType; +use crate::networking::types::data_representation::DataRepr; use crate::networking::types::icmp_type::IcmpType; use crate::networking::types::traffic_direction::TrafficDirection; +use crate::report::types::sort_type::SortType; use crate::utils::types::timestamp::Timestamp; /// Struct useful to format the output report file and to keep track of statistics about the sniffed traffic. @@ -56,4 +59,24 @@ pub fn refresh(&mut self, other: &Self) { .or_insert(*count); } } + + pub fn transmitted_data(&self, data_repr: DataRepr) -> u128 { + match data_repr { + DataRepr::Packets => self.transmitted_packets, + DataRepr::Bytes => self.transmitted_bytes, + DataRepr::Bits => self.transmitted_bytes * 8, + } + } + + pub fn compare(&self, other: &Self, sort_type: SortType, data_repr: DataRepr) -> Ordering { + match sort_type { + SortType::Ascending => self + .transmitted_data(data_repr) + .cmp(&other.transmitted_data(data_repr)), + SortType::Descending => other + .transmitted_data(data_repr) + .cmp(&self.transmitted_data(data_repr)), + SortType::Neutral => other.final_timestamp.cmp(&self.final_timestamp), + } + } } diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index 73229df4..7fcde9e7 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -66,26 +66,23 @@ pub fn refresh(&mut self, msg: &mut InfoTraffic) { } pub fn get_thumbnail_data(&self, data_repr: DataRepr) -> (u128, u128, u128, u128) { - if data_repr.eq(&ChartType::Bytes) { - ( - self.tot_data_info.incoming_bytes(), - self.tot_data_info.outgoing_bytes(), - self.all_bytes - - self.tot_data_info.outgoing_bytes() - - self.tot_data_info.incoming_bytes(), + let incoming = self.tot_data_info.incoming_data(data_repr); + let outgoing = self.tot_data_info.outgoing_data(data_repr); + let all = match data_repr { + DataRepr::Packets => self.all_packets, + DataRepr::Bytes => self.all_bytes, + DataRepr::Bits => self.all_bytes * 8, + }; + let filtered = all - incoming - outgoing; + let dropped = match data_repr { + DataRepr::Packets => u128::from(self.dropped_packets), + DataRepr::Bytes | DataRepr::Bits => { // assume that the dropped packets have the same size as the average packet - u128::from(self.dropped_packets) * self.all_bytes / self.all_packets, - ) - } else { - ( - self.tot_data_info.incoming_packets(), - self.tot_data_info.outgoing_packets(), - self.all_packets - - self.tot_data_info.outgoing_packets() - - self.tot_data_info.incoming_packets(), - u128::from(self.dropped_packets), - ) - } + u128::from(self.dropped_packets) * all / self.all_packets + } + }; + + (incoming, outgoing, filtered, dropped) } pub fn take_but_leave_something(&mut self) -> Self { diff --git a/src/report/get_report_entries.rs b/src/report/get_report_entries.rs index b6f7979b..5e67c271 100644 --- a/src/report/get_report_entries.rs +++ b/src/report/get_report_entries.rs @@ -8,7 +8,7 @@ use crate::networking::types::host::Host; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::report::types::sort_type::SortType; -use crate::{InfoTraffic, ReportSortType, Service, Sniffer}; +use crate::{InfoTraffic, Service, Sniffer}; /// 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, @@ -47,24 +47,12 @@ pub fn get_searched_entries( }) .collect(); - all_results.sort_by(|&(_, a), &(_, b)| match sniffer.report_sort_type { - ReportSortType { - byte_sort, - packet_sort: SortType::Neutral, - } => match byte_sort { - SortType::Ascending => a.transmitted_bytes.cmp(&b.transmitted_bytes), - SortType::Descending => b.transmitted_bytes.cmp(&a.transmitted_bytes), - SortType::Neutral => b.final_timestamp.cmp(&a.final_timestamp), - }, - ReportSortType { - byte_sort: SortType::Neutral, - packet_sort, - } => match packet_sort { - SortType::Ascending => a.transmitted_packets.cmp(&b.transmitted_packets), - SortType::Descending => b.transmitted_packets.cmp(&a.transmitted_packets), - SortType::Neutral => b.final_timestamp.cmp(&a.final_timestamp), - }, - _ => b.final_timestamp.cmp(&a.final_timestamp), + all_results.sort_by(|&(_, a), &(_, b)| { + a.compare( + b, + sniffer.report_sort_type.data_sort, + sniffer.traffic_chart.data_repr, + ) }); let upper_bound = min(sniffer.page_number * 20, all_results.len()); diff --git a/src/report/types/report_col.rs b/src/report/types/report_col.rs index 71795c55..2e5db7b1 100644 --- a/src/report/types/report_col.rs +++ b/src/report/types/report_col.rs @@ -2,9 +2,7 @@ use crate::networking::types::data_representation::DataRepr; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::report::types::search_parameters::FilterInputType; -use crate::translations::translations::{ - address_translation, bytes_translation, packets_translation, protocol_translation, -}; +use crate::translations::translations::{address_translation, protocol_translation}; use crate::translations::translations_2::{destination_translation, source_translation}; use crate::translations::translations_3::{port_translation, service_translation}; use crate::translations::types::language::Language; @@ -25,37 +23,30 @@ pub enum ReportCol { DstPort, Proto, Service, - Bits, - Bytes, - Packets, + Data, } impl ReportCol { - pub(crate) const ALL: [ReportCol; 8] = [ + pub(crate) const ALL: [ReportCol; 7] = [ ReportCol::SrcIp, ReportCol::SrcPort, ReportCol::DstIp, ReportCol::DstPort, ReportCol::Proto, ReportCol::Service, - ReportCol::Bytes, - ReportCol::Packets, + ReportCol::Data, ]; pub(crate) const FILTER_COLUMNS_WIDTH: f32 = 4.0 * SMALL_COL_WIDTH + 2.0 * LARGE_COL_WIDTH; - pub(crate) fn get_title(&self, language: Language) -> String { + pub(crate) fn get_title(&self, language: Language, data_repr: DataRepr) -> String { match self { ReportCol::SrcIp | ReportCol::DstIp => address_translation(language).to_string(), ReportCol::SrcPort | ReportCol::DstPort => port_translation(language).to_string(), ReportCol::Proto => protocol_translation(language).to_string(), ReportCol::Service => service_translation(language).to_string(), - ReportCol::Bytes => { - let mut str = bytes_translation(language).to_string(); - str.remove(0).to_uppercase().to_string() + &str - } - ReportCol::Packets => { - let mut str = packets_translation(language).to_string(); + ReportCol::Data => { + let mut str = data_repr.get_label(language).to_string(); str.remove(0).to_uppercase().to_string() + &str } } @@ -73,7 +64,12 @@ pub(crate) fn get_title_direction_info(&self, language: Language) -> String { } } - pub(crate) fn get_value(&self, key: &AddressPortPair, val: &InfoAddressPortPair) -> String { + pub(crate) fn get_value( + &self, + key: &AddressPortPair, + val: &InfoAddressPortPair, + data_repr: DataRepr, + ) -> String { match self { ReportCol::SrcIp => key.address1.to_string(), ReportCol::SrcPort => { @@ -93,9 +89,7 @@ pub(crate) fn get_value(&self, key: &AddressPortPair, val: &InfoAddressPortPair) } ReportCol::Proto => key.protocol.to_string(), ReportCol::Service => val.service.to_string(), - ReportCol::Bits => DataRepr::Bits.formatted_string(val.transmitted_bytes * 8), - ReportCol::Bytes => DataRepr::Bytes.formatted_string(val.transmitted_bytes), - ReportCol::Packets => DataRepr::Packets.formatted_string(val.transmitted_packets), + ReportCol::Data => data_repr.formatted_string(val.transmitted_data(data_repr)), } } @@ -128,7 +122,7 @@ pub(crate) fn get_filter_input_type(&self) -> FilterInputType { ReportCol::DstPort => FilterInputType::PortDst, ReportCol::Proto => FilterInputType::Proto, ReportCol::Service => FilterInputType::Service, - ReportCol::Bytes | ReportCol::Packets => FilterInputType::Country, // just to not panic... + ReportCol::Data => FilterInputType::Country, // just to not panic... } } } diff --git a/src/report/types/report_sort_type.rs b/src/report/types/report_sort_type.rs index 161d79de..f40dc485 100644 --- a/src/report/types/report_sort_type.rs +++ b/src/report/types/report_sort_type.rs @@ -4,46 +4,27 @@ use crate::gui::styles::button::ButtonType; use crate::gui::styles::types::style_type::StyleType; -use crate::report::types::report_col::ReportCol; use crate::report::types::sort_type::SortType; -use crate::utils::types::icon::Icon; /// Struct representing the possible kinds of sort for displayed relevant connections. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct ReportSortType { - pub byte_sort: SortType, - pub packet_sort: SortType, + pub data_sort: SortType, } impl ReportSortType { - pub fn next_sort(self, report_col: &ReportCol) -> Self { - match report_col { - ReportCol::Bytes => Self { - byte_sort: self.byte_sort.next_sort(), - packet_sort: SortType::Neutral, - }, - ReportCol::Packets => Self { - byte_sort: SortType::Neutral, - packet_sort: self.packet_sort.next_sort(), - }, - _ => Self::default(), + pub fn next_sort(self) -> Self { + Self { + data_sort: self.data_sort.next_sort(), } } - pub fn icon<'a>(self, report_col: &ReportCol) -> Text<'a, StyleType> { - match report_col { - ReportCol::Bytes => self.byte_sort.icon(), - ReportCol::Packets => self.packet_sort.icon(), - _ => Icon::SortNeutral.to_text(), - } + pub fn icon<'a>(self) -> Text<'a, StyleType> { + self.data_sort.icon() } - pub fn button_type(self, report_col: &ReportCol) -> ButtonType { - match report_col { - ReportCol::Bytes => self.byte_sort.button_type(), - ReportCol::Packets => self.packet_sort.button_type(), - _ => ButtonType::SortArrows, - } + pub fn button_type(self) -> ButtonType { + self.data_sort.button_type() } } diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 46319c56..a7cb5b49 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -164,11 +164,11 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { #[allow(dead_code)] pub fn bits_translation(language: Language) -> &'static str { match language { - Language::EN | Language::IT | Language::NL | Language::DE | Language::FR => "Bits", + Language::EN | Language::IT | Language::NL | Language::DE | Language::FR => "bits", Language::JA => "ビット", Language::ZH => "比特", - Language::UZ => "Bitlar", - _ => "Bits", + Language::UZ => "bitlar", + _ => "bits", } } From 371ab3b93055851e7121e178dc6d3a6dcdc37993 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 14 Aug 2025 13:00:26 +0200 Subject: [PATCH 36/74] fix old tests --- src/chart/manage_chart_data.rs | 5 +- src/chart/types/traffic_chart.rs | 2 +- src/gui/pages/inspect_page.rs | 140 ++++---- src/gui/pages/overview_page.rs | 67 ++-- src/gui/sniffer.rs | 78 ++--- src/networking/types/data_representation.rs | 351 +++++++++++++++++--- src/report/types/report_sort_type.rs | 96 +----- 7 files changed, 453 insertions(+), 286 deletions(-) diff --git a/src/chart/manage_chart_data.rs b/src/chart/manage_chart_data.rs index 7d361fb7..78a5fa7e 100644 --- a/src/chart/manage_chart_data.rs +++ b/src/chart/manage_chart_data.rs @@ -221,8 +221,9 @@ mod tests { use crate::chart::manage_chart_data::{ChartSeries, get_max, get_min}; use crate::networking::types::data_info::DataInfo; + use crate::networking::types::data_representation::DataRepr; use crate::utils::types::timestamp::Timestamp; - use crate::{ChartType, InfoTraffic, Language, StyleType, TrafficChart}; + use crate::{InfoTraffic, Language, StyleType, TrafficChart}; fn spline_from_vec(vec: Vec<(i32, i32)>) -> Spline { Spline::from_vec( @@ -318,7 +319,7 @@ fn test_chart_data_updates() { min_packets: -1000.0, max_packets: 21000.0, language: Language::default(), - data_repr: ChartType::Packets, + data_repr: DataRepr::Packets, style: StyleType::default(), thumbnail: false, is_live_capture: true, diff --git a/src/chart/types/traffic_chart.rs b/src/chart/types/traffic_chart.rs index 35983e8c..3b41d2be 100644 --- a/src/chart/types/traffic_chart.rs +++ b/src/chart/types/traffic_chart.rs @@ -407,7 +407,7 @@ fn test_spline_samples() { let eps = 0.001; let pts = spline.len() * 10; - let samples = sample_spline(&spline); + let samples = sample_spline(&spline, 1.0); assert_eq!(samples.len(), pts); let delta = samples[1].0 - samples[0].0; diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index b5c91a2a..961880da 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -628,6 +628,7 @@ fn button_clear_filter<'a>( #[cfg(test)] mod tests { use crate::gui::pages::inspect_page::title_report_col_display; + use crate::networking::types::data_representation::DataRepr; use crate::report::types::report_col::ReportCol; use crate::translations::types::language::Language; @@ -636,78 +637,81 @@ fn test_table_titles_display_and_tooltip_values_for_each_language() { // check glyph len when adding new language... assert_eq!(Language::ALL.len(), 22); for report_col in ReportCol::ALL { - for language in Language::ALL { - let (title, title_small, tooltip_val) = - title_report_col_display(&report_col, language); - let title_chars = title.chars().collect::>(); - let title_small_chars = title_small.chars().collect::>(); - let max_chars = report_col.get_max_chars(Some(language)); - if tooltip_val.is_empty() { - // all is entirely displayed - assert!(title_chars.len() + title_small_chars.len() <= max_chars); - assert_eq!(title, report_col.get_title(language)); - assert_eq!(title_small, report_col.get_title_direction_info(language)); - } else { - // tooltip is the full concatenation - assert_eq!( - tooltip_val, - [ - report_col.get_title(language), - report_col.get_title_direction_info(language) - ] - .concat() - ); - if report_col.get_title_direction_info(language).len() == 0 { - // displayed values have max len -1 (they include "…" that counts for 2 units) - assert_eq!(title_chars.len() + title_small_chars.len(), max_chars - 1); + for data_repr in DataRepr::ALL { + for language in Language::ALL { + let (title, title_small, tooltip_val) = + title_report_col_display(&report_col, data_repr, language); + let title_chars = title.chars().collect::>(); + let title_small_chars = title_small.chars().collect::>(); + let max_chars = report_col.get_max_chars(Some(language)); + if tooltip_val.is_empty() { + // all is entirely displayed + assert!(title_chars.len() + title_small_chars.len() <= max_chars); + assert_eq!(title, report_col.get_title(language, data_repr)); + assert_eq!(title_small, report_col.get_title_direction_info(language)); } else { - match title_chars.len() { - x if x == max_chars - 4 || x == max_chars - 3 => { - assert_eq!(title_small_chars.len(), 1) - } - _ => assert_eq!( - title_chars.len() + title_small_chars.len(), - max_chars - 1 - ), - } - } - if title != report_col.get_title(language) { - // first title part is not full, so second one is suspensions - assert_eq!(title_small, "…"); - // check len wrt max - assert!(title_chars.len() >= max_chars - 4); - // first title part is max - 2 chars of full self + // tooltip is the full concatenation assert_eq!( - title, - report_col - .get_title(language) - .chars() - .collect::>()[..max_chars - 2] - .iter() - .collect::() + tooltip_val, + [ + report_col.get_title(language, data_repr), + report_col.get_title_direction_info(language) + ] + .concat() ); - } else { - // first part is untouched - // second title part is max - title.len - 2 chars of full self, plus suspensions - let mut second_part = [ - &report_col - .get_title_direction_info(language) - .chars() - .collect::>()[..max_chars - 2 - title_chars.len()] - .iter() - .collect::(), - "…", - ] - .concat(); - if second_part == String::from(" (…") || second_part == String::from(" …") - { - second_part = String::from("…"); + if report_col.get_title_direction_info(language).len() == 0 { + // displayed values have max len -1 (they include "…" that counts for 2 units) + assert_eq!(title_chars.len() + title_small_chars.len(), max_chars - 1); + } else { + match title_chars.len() { + x if x == max_chars - 4 || x == max_chars - 3 => { + assert_eq!(title_small_chars.len(), 1) + } + _ => assert_eq!( + title_chars.len() + title_small_chars.len(), + max_chars - 1 + ), + } + } + if title != report_col.get_title(language, data_repr) { + // first title part is not full, so second one is suspensions + assert_eq!(title_small, "…"); + // check len wrt max + assert!(title_chars.len() >= max_chars - 4); + // first title part is max - 2 chars of full self + assert_eq!( + title, + report_col + .get_title(language, data_repr) + .chars() + .collect::>()[..max_chars - 2] + .iter() + .collect::() + ); + } else { + // first part is untouched + // second title part is max - title.len - 2 chars of full self, plus suspensions + let mut second_part = [ + &report_col + .get_title_direction_info(language) + .chars() + .collect::>()[..max_chars - 2 - title_chars.len()] + .iter() + .collect::(), + "…", + ] + .concat(); + if second_part == String::from(" (…") + || second_part == String::from(" …") + { + second_part = String::from("…"); + } + assert_eq!(title_small, second_part); + // second part never terminates with "(…" + assert!(!title_small.ends_with("(…")); + // second part never terminates with " …" + assert!(!title_small.ends_with(" …")); } - assert_eq!(title_small, second_part); - // second part never terminates with "(…" - assert!(!title_small.ends_with("(…")); - // second part never terminates with " …" - assert!(!title_small.ends_with(" …")); } } } diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index aeb74995..d797a57b 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -869,17 +869,22 @@ fn sort_arrows<'a>( mod tests { use crate::gui::pages::overview_page::{MIN_BARS_LENGTH, get_bars_length}; use crate::networking::types::data_info::DataInfo; + use crate::networking::types::data_representation::DataRepr; #[test] fn test_get_bars_length_simple() { let first_entry = DataInfo::new_for_tests(50, 50, 150, 50); let data_info = DataInfo::new_for_tests(25, 55, 165, 30); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (25, 55) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), + (83, 15) + ); + assert_eq!( + get_bars_length(DataRepr::Bits, &first_entry, &data_info), (83, 15) ); } @@ -889,21 +894,21 @@ fn test_get_bars_length_normalize_small_values() { let first_entry = DataInfo::new_for_tests(50, 50, 150, 50); let mut data_info = DataInfo::new_for_tests(2, 1, 1, 0); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (MIN_BARS_LENGTH as u16 / 2, MIN_BARS_LENGTH as u16 / 2) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (MIN_BARS_LENGTH as u16, 0) ); data_info = DataInfo::new_for_tests(0, 3, 0, 2); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (0, MIN_BARS_LENGTH as u16) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (0, MIN_BARS_LENGTH as u16) ); } @@ -914,31 +919,31 @@ fn test_get_bars_length_normalize_very_small_values() { DataInfo::new_for_tests(u128::MAX / 2, u128::MAX / 2, u128::MAX / 2, u128::MAX / 2); let mut data_info = DataInfo::new_for_tests(1, 1, 1, 1); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (MIN_BARS_LENGTH as u16 / 2, MIN_BARS_LENGTH as u16 / 2) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (MIN_BARS_LENGTH as u16 / 2, MIN_BARS_LENGTH as u16 / 2) ); data_info = DataInfo::new_for_tests(0, 1, 0, 1); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (0, MIN_BARS_LENGTH as u16) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (0, MIN_BARS_LENGTH as u16) ); data_info = DataInfo::new_for_tests(1, 0, 1, 0); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (MIN_BARS_LENGTH as u16, 0) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (MIN_BARS_LENGTH as u16, 0) ); } @@ -949,93 +954,93 @@ fn test_get_bars_length_complex() { let mut data_info = DataInfo::new_for_tests(0, 9, 0, 10); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (0, 16) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (0, 71) ); data_info = DataInfo::new_for_tests(9, 0, 13, 0); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (16, 0) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (93, 0) ); data_info = DataInfo::new_for_tests(4, 5, 6, 7); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (7, 9) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (43, 50) ); data_info = DataInfo::new_for_tests(5, 4, 7, 6); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (9, 7) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (50, 43) ); data_info = DataInfo::new_for_tests(1, 8, 1, 12); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (MIN_BARS_LENGTH as u16 / 2, 14) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (7, 86) ); data_info = DataInfo::new_for_tests(8, 1, 12, 1); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (14, MIN_BARS_LENGTH as u16 / 2) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (86, 7) ); data_info = DataInfo::new_for_tests(6, 1, 10, 1); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (11, MIN_BARS_LENGTH as u16 / 2) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (71, 7) ); data_info = DataInfo::new_for_tests(1, 6, 1, 9); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (MIN_BARS_LENGTH as u16 / 2, 11,) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (7, 64) ); data_info = DataInfo::new_for_tests(1, 6, 5, 5); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (36, 36) ); data_info = DataInfo::new_for_tests(0, 0, 0, 0); assert_eq!( - get_bars_length(ChartType::Packets, &first_entry, &data_info), + get_bars_length(DataRepr::Packets, &first_entry, &data_info), (0, 0) ); assert_eq!( - get_bars_length(ChartType::Bytes, &first_entry, &data_info), + get_bars_length(DataRepr::Bytes, &first_entry, &data_info), (0, 0) ); } diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 3ffc8f58..aa7fea6c 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -1114,6 +1114,7 @@ mod tests { use crate::gui::types::message::Message; use crate::gui::types::timing_events::TimingEvents; use crate::networking::types::data_info::DataInfo; + use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; use crate::networking::types::traffic_direction::TrafficDirection; use crate::notifications::types::logged_notification::{ @@ -1123,11 +1124,10 @@ mod tests { DataNotification, FavoriteNotification, Notification, Notifications, }; use crate::notifications::types::sound::Sound; - use crate::report::types::report_col::ReportCol; use crate::report::types::sort_type::SortType; use crate::{ - ByteMultiple, ChartType, ConfigDevice, ConfigSettings, ConfigWindow, Configs, IpVersion, - Language, Protocol, ReportSortType, RunningPage, Sniffer, StyleType, + ByteMultiple, ConfigDevice, ConfigSettings, ConfigWindow, Configs, IpVersion, Language, + Protocol, ReportSortType, RunningPage, Sniffer, StyleType, }; // helpful to clean up files generated from tests @@ -1201,13 +1201,15 @@ fn test_correctly_update_protocol() { fn test_correctly_update_chart_kind() { let mut sniffer = Sniffer::new(Configs::default()); - assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Bytes); - sniffer.update(Message::DataReprSelection(ChartType::Packets)); - assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Packets); - sniffer.update(Message::DataReprSelection(ChartType::Packets)); - assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Packets); - sniffer.update(Message::DataReprSelection(ChartType::Bytes)); - assert_eq!(sniffer.traffic_chart.data_repr, ChartType::Bytes); + assert_eq!(sniffer.traffic_chart.data_repr, DataRepr::Bytes); + sniffer.update(Message::DataReprSelection(DataRepr::Packets)); + assert_eq!(sniffer.traffic_chart.data_repr, DataRepr::Packets); + sniffer.update(Message::DataReprSelection(DataRepr::Packets)); + assert_eq!(sniffer.traffic_chart.data_repr, DataRepr::Packets); + sniffer.update(Message::DataReprSelection(DataRepr::Bytes)); + assert_eq!(sniffer.traffic_chart.data_repr, DataRepr::Bytes); + sniffer.update(Message::DataReprSelection(DataRepr::Bits)); + assert_eq!(sniffer.traffic_chart.data_repr, DataRepr::Bits); } #[test] @@ -1216,53 +1218,31 @@ fn test_correctly_update_report_sort_kind() { let mut sniffer = Sniffer::new(Configs::default()); let sort = ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Neutral, + data_sort: SortType::Neutral, }; assert_eq!(sniffer.report_sort_type, sort); - sniffer.update(Message::ReportSortSelection( - sort.next_sort(&ReportCol::Bytes), - )); + sniffer.update(Message::ReportSortSelection(sort.next_sort())); assert_eq!( sniffer.report_sort_type, ReportSortType { - byte_sort: SortType::Descending, - packet_sort: SortType::Neutral + data_sort: SortType::Descending, + } + ); + sniffer.update(Message::ReportSortSelection(sort.next_sort().next_sort())); + assert_eq!( + sniffer.report_sort_type, + ReportSortType { + data_sort: SortType::Ascending, } ); sniffer.update(Message::ReportSortSelection( - sort.next_sort(&ReportCol::Bytes) - .next_sort(&ReportCol::Bytes), + sort.next_sort().next_sort().next_sort(), )); assert_eq!( sniffer.report_sort_type, ReportSortType { - byte_sort: SortType::Ascending, - packet_sort: SortType::Neutral - } - ); - sniffer.update(Message::ReportSortSelection( - sort.next_sort(&ReportCol::Bytes) - .next_sort(&ReportCol::Packets), - )); - assert_eq!( - sniffer.report_sort_type, - ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Descending - } - ); - sniffer.update(Message::ReportSortSelection( - sort.next_sort(&ReportCol::Bytes) - .next_sort(&ReportCol::Bytes) - .next_sort(&ReportCol::Bytes), - )); - assert_eq!( - sniffer.report_sort_type, - ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Neutral + data_sort: SortType::Neutral, } ); } @@ -1663,7 +1643,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { let mut sniffer = Sniffer::new(Configs::default()); let bytes_notification_init = DataNotification { - data_repr: ChartType::Bytes, + data_repr: DataRepr::Bytes, threshold: None, byte_multiple: ByteMultiple::KB, sound: Sound::Pop, @@ -1671,7 +1651,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { }; let bytes_notification_toggled_on = DataNotification { - data_repr: ChartType::Bytes, + data_repr: DataRepr::Bytes, threshold: Some(800_000), byte_multiple: ByteMultiple::GB, sound: Sound::Pop, @@ -1679,7 +1659,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { }; let bytes_notification_adjusted_threshold_sound_off = DataNotification { - data_repr: ChartType::Bytes, + data_repr: DataRepr::Bytes, threshold: Some(3), byte_multiple: ByteMultiple::KB, sound: Sound::None, @@ -1687,7 +1667,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { }; let bytes_notification_sound_off_only = DataNotification { - data_repr: ChartType::Bytes, + data_repr: DataRepr::Bytes, threshold: Some(800_000), byte_multiple: ByteMultiple::GB, sound: Sound::None, @@ -1807,7 +1787,7 @@ fn test_clear_all_notifications() { VecDeque::from([LoggedNotification::DataThresholdExceeded( DataThresholdExceeded { id: 1, - data_repr: ChartType::Packets, + data_repr: DataRepr::Packets, threshold: 0, data_info: DataInfo::default(), timestamp: "".to_string(), diff --git a/src/networking/types/data_representation.rs b/src/networking/types/data_representation.rs index 7f0d52c7..e98a8161 100644 --- a/src/networking/types/data_representation.rs +++ b/src/networking/types/data_representation.rs @@ -172,12 +172,78 @@ fn test_interpret_unknown_suffix_correctly() { #[test] fn test_byte_multiple_display() { - assert_eq!(format!("{}", ByteMultiple::B), "B"); - assert_eq!(format!("{}", ByteMultiple::KB), "KB"); - assert_eq!(format!("{}", ByteMultiple::MB), "MB"); - assert_eq!(format!("{}", ByteMultiple::GB), "GB"); - assert_eq!(format!("{}", ByteMultiple::TB), "TB"); - assert_eq!(format!("{}", ByteMultiple::PB), "PB"); + assert_eq!( + format!("{}", ByteMultiple::B.pretty_print(DataRepr::Packets)), + "" + ); + assert_eq!( + format!("{}", ByteMultiple::B.pretty_print(DataRepr::Bytes)), + "B" + ); + assert_eq!( + format!("{}", ByteMultiple::B.pretty_print(DataRepr::Bits)), + "b" + ); + assert_eq!( + format!("{}", ByteMultiple::KB.pretty_print(DataRepr::Packets)), + "" + ); + assert_eq!( + format!("{}", ByteMultiple::KB.pretty_print(DataRepr::Bytes)), + "KB" + ); + assert_eq!( + format!("{}", ByteMultiple::KB.pretty_print(DataRepr::Bits)), + "Kb" + ); + assert_eq!( + format!("{}", ByteMultiple::MB.pretty_print(DataRepr::Packets)), + "" + ); + assert_eq!( + format!("{}", ByteMultiple::MB.pretty_print(DataRepr::Bytes)), + "MB" + ); + assert_eq!( + format!("{}", ByteMultiple::MB.pretty_print(DataRepr::Bits)), + "Mb" + ); + assert_eq!( + format!("{}", ByteMultiple::GB.pretty_print(DataRepr::Packets)), + "" + ); + assert_eq!( + format!("{}", ByteMultiple::GB.pretty_print(DataRepr::Bytes)), + "GB" + ); + assert_eq!( + format!("{}", ByteMultiple::GB.pretty_print(DataRepr::Bits)), + "Gb" + ); + assert_eq!( + format!("{}", ByteMultiple::TB.pretty_print(DataRepr::Packets)), + "" + ); + assert_eq!( + format!("{}", ByteMultiple::TB.pretty_print(DataRepr::Bytes)), + "TB" + ); + assert_eq!( + format!("{}", ByteMultiple::TB.pretty_print(DataRepr::Bits)), + "Tb" + ); + assert_eq!( + format!("{}", ByteMultiple::PB.pretty_print(DataRepr::Packets)), + "" + ); + assert_eq!( + format!("{}", ByteMultiple::PB.pretty_print(DataRepr::Bytes)), + "PB" + ); + assert_eq!( + format!("{}", ByteMultiple::PB.pretty_print(DataRepr::Bits)), + "Pb" + ); } #[test] @@ -202,67 +268,256 @@ fn test_byte_multiple_multiplier() { #[test] fn test_byte_multiple_formatted_string() { - assert_eq!(ByteMultiple::formatted_string(u128::MIN), "0 B"); - assert_eq!(ByteMultiple::formatted_string(1), "1 B"); - assert_eq!(ByteMultiple::formatted_string(82), "82 B"); - assert_eq!(ByteMultiple::formatted_string(999), "999 B"); - assert_eq!(ByteMultiple::formatted_string(1_000), "1.0 KB"); - assert_eq!(ByteMultiple::formatted_string(1_090), "1.1 KB"); - assert_eq!(ByteMultiple::formatted_string(1_990), "2.0 KB"); - assert_eq!(ByteMultiple::formatted_string(9_090), "9.1 KB"); - assert_eq!(ByteMultiple::formatted_string(9_950), "9.9 KB"); - assert_eq!(ByteMultiple::formatted_string(9_951), "10 KB"); - assert_eq!(ByteMultiple::formatted_string(71_324), "71 KB"); - assert_eq!(ByteMultiple::formatted_string(821_789), "822 KB"); - assert_eq!(ByteMultiple::formatted_string(999_499), "999 KB"); - assert_eq!(ByteMultiple::formatted_string(999_999), "999 KB"); - assert_eq!(ByteMultiple::formatted_string(1_000_000), "1.0 MB"); - assert_eq!(ByteMultiple::formatted_string(3_790_000), "3.8 MB"); - assert_eq!(ByteMultiple::formatted_string(9_950_000), "9.9 MB"); - assert_eq!(ByteMultiple::formatted_string(9_951_000), "10 MB"); - assert_eq!(ByteMultiple::formatted_string(49_499_000), "49 MB"); - assert_eq!(ByteMultiple::formatted_string(49_500_000), "50 MB"); - assert_eq!(ByteMultiple::formatted_string(670_900_000), "671 MB"); - assert_eq!(ByteMultiple::formatted_string(998_199_999), "998 MB"); - assert_eq!(ByteMultiple::formatted_string(999_999_999), "999 MB"); - assert_eq!(ByteMultiple::formatted_string(1_000_000_000), "1.0 GB"); - assert_eq!(ByteMultiple::formatted_string(7_770_000_000), "7.8 GB"); - assert_eq!(ByteMultiple::formatted_string(9_950_000_000), "9.9 GB"); - assert_eq!(ByteMultiple::formatted_string(9_951_000_000), "10 GB"); - assert_eq!(ByteMultiple::formatted_string(19_951_000_000), "20 GB"); - assert_eq!(ByteMultiple::formatted_string(399_951_000_000), "400 GB"); - assert_eq!(ByteMultiple::formatted_string(999_999_999_999), "999 GB"); - assert_eq!(ByteMultiple::formatted_string(1_000_000_000_000), "1.0 TB"); - assert_eq!(ByteMultiple::formatted_string(9_950_000_000_000), "9.9 TB"); - assert_eq!(ByteMultiple::formatted_string(9_951_000_000_000), "10 TB"); + assert_eq!(DataRepr::Packets.formatted_string(u128::MIN), "0"); + assert_eq!(DataRepr::Bytes.formatted_string(u128::MIN), "0 B"); + assert_eq!(DataRepr::Bits.formatted_string(u128::MIN), "0 b"); + + assert_eq!(DataRepr::Packets.formatted_string(1), "1"); + assert_eq!(DataRepr::Bytes.formatted_string(1), "1 B"); + assert_eq!(DataRepr::Bits.formatted_string(1), "1 b"); + + assert_eq!(DataRepr::Packets.formatted_string(82), "82"); + assert_eq!(DataRepr::Bytes.formatted_string(82), "82 B"); + assert_eq!(DataRepr::Bits.formatted_string(82), "82 b"); + + assert_eq!(DataRepr::Packets.formatted_string(999), "999"); + assert_eq!(DataRepr::Bytes.formatted_string(999), "999 B"); + assert_eq!(DataRepr::Bits.formatted_string(999), "999 b"); + + assert_eq!(DataRepr::Packets.formatted_string(1_000), "1000"); + assert_eq!(DataRepr::Bytes.formatted_string(1_000), "1.0 KB"); + assert_eq!(DataRepr::Bits.formatted_string(1_000), "1.0 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(1_090), "1090"); + assert_eq!(DataRepr::Bytes.formatted_string(1_090), "1.1 KB"); + assert_eq!(DataRepr::Bits.formatted_string(1_090), "1.1 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(1_990), "1990"); + assert_eq!(DataRepr::Bytes.formatted_string(1_990), "2.0 KB"); + assert_eq!(DataRepr::Bits.formatted_string(1_990), "2.0 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(9_090), "9090"); + assert_eq!(DataRepr::Bytes.formatted_string(9_090), "9.1 KB"); + assert_eq!(DataRepr::Bits.formatted_string(9_090), "9.1 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(9_950), "9950"); + assert_eq!(DataRepr::Bytes.formatted_string(9_950), "9.9 KB"); + assert_eq!(DataRepr::Bits.formatted_string(9_950), "9.9 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(9_951), "9951"); + assert_eq!(DataRepr::Bytes.formatted_string(9_951), "10 KB"); + assert_eq!(DataRepr::Bits.formatted_string(9_951), "10 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(71_324), "71324"); + assert_eq!(DataRepr::Bytes.formatted_string(71_324), "71 KB"); + assert_eq!(DataRepr::Bits.formatted_string(71_324), "71 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(821_789), "821789"); + assert_eq!(DataRepr::Bytes.formatted_string(821_789), "822 KB"); + assert_eq!(DataRepr::Bits.formatted_string(821_789), "822 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(999_499), "999499"); + assert_eq!(DataRepr::Bytes.formatted_string(999_499), "999 KB"); + assert_eq!(DataRepr::Bits.formatted_string(999_499), "999 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(999_999), "999999"); + assert_eq!(DataRepr::Bytes.formatted_string(999_999), "999 KB"); + assert_eq!(DataRepr::Bits.formatted_string(999_999), "999 Kb"); + + assert_eq!(DataRepr::Packets.formatted_string(1_000_000), "1000000"); + assert_eq!(DataRepr::Bytes.formatted_string(1_000_000), "1.0 MB"); + assert_eq!(DataRepr::Bits.formatted_string(1_000_000), "1.0 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(3_790_000), "3790000"); + assert_eq!(DataRepr::Bytes.formatted_string(3_790_000), "3.8 MB"); + assert_eq!(DataRepr::Bits.formatted_string(3_790_000), "3.8 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(9_950_000), "9950000"); + assert_eq!(DataRepr::Bytes.formatted_string(9_950_000), "9.9 MB"); + assert_eq!(DataRepr::Bits.formatted_string(9_950_000), "9.9 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(9_951_000), "9951000"); + assert_eq!(DataRepr::Bytes.formatted_string(9_951_000), "10 MB"); + assert_eq!(DataRepr::Bits.formatted_string(9_951_000), "10 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(49_499_000), "49499000"); + assert_eq!(DataRepr::Bytes.formatted_string(49_499_000), "49 MB"); + assert_eq!(DataRepr::Bits.formatted_string(49_499_000), "49 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(49_500_000), "49500000"); + assert_eq!(DataRepr::Bytes.formatted_string(49_500_000), "50 MB"); + assert_eq!(DataRepr::Bits.formatted_string(49_500_000), "50 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(670_900_000), "670900000"); + assert_eq!(DataRepr::Bytes.formatted_string(670_900_000), "671 MB"); + assert_eq!(DataRepr::Bits.formatted_string(670_900_000), "671 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(998_199_999), "998199999"); + assert_eq!(DataRepr::Bytes.formatted_string(998_199_999), "998 MB"); + assert_eq!(DataRepr::Bits.formatted_string(998_199_999), "998 Mb"); + + assert_eq!(DataRepr::Packets.formatted_string(999_999_999), "999999999"); + assert_eq!(DataRepr::Bytes.formatted_string(999_999_999), "999 MB"); + assert_eq!(DataRepr::Bits.formatted_string(999_999_999), "999 Mb"); + assert_eq!( - ByteMultiple::formatted_string(999_950_000_000_000), + DataRepr::Packets.formatted_string(1_000_000_000), + "1000000000" + ); + assert_eq!(DataRepr::Bytes.formatted_string(1_000_000_000), "1.0 GB"); + assert_eq!(DataRepr::Bits.formatted_string(1_000_000_000), "1.0 Gb"); + + assert_eq!( + DataRepr::Packets.formatted_string(7_770_000_000), + "7770000000" + ); + assert_eq!(DataRepr::Bytes.formatted_string(7_770_000_000), "7.8 GB"); + assert_eq!(DataRepr::Bits.formatted_string(7_770_000_000), "7.8 Gb"); + + assert_eq!( + DataRepr::Packets.formatted_string(9_950_000_000), + "9950000000" + ); + assert_eq!(DataRepr::Bytes.formatted_string(9_950_000_000), "9.9 GB"); + assert_eq!(DataRepr::Bits.formatted_string(9_950_000_000), "9.9 Gb"); + + assert_eq!( + DataRepr::Packets.formatted_string(9_951_000_000), + "9951000000" + ); + assert_eq!(DataRepr::Bytes.formatted_string(9_951_000_000), "10 GB"); + assert_eq!(DataRepr::Bits.formatted_string(9_951_000_000), "10 Gb"); + + assert_eq!( + DataRepr::Packets.formatted_string(19_951_000_000), + "19951000000" + ); + assert_eq!(DataRepr::Bytes.formatted_string(19_951_000_000), "20 GB"); + assert_eq!(DataRepr::Bits.formatted_string(19_951_000_000), "20 Gb"); + + assert_eq!( + DataRepr::Packets.formatted_string(399_951_000_000), + "399951000000" + ); + assert_eq!(DataRepr::Bytes.formatted_string(399_951_000_000), "400 GB"); + assert_eq!(DataRepr::Bits.formatted_string(399_951_000_000), "400 Gb"); + + assert_eq!( + DataRepr::Packets.formatted_string(999_999_999_999), + "999999999999" + ); + assert_eq!(DataRepr::Bytes.formatted_string(999_999_999_999), "999 GB"); + assert_eq!(DataRepr::Bits.formatted_string(999_999_999_999), "999 Gb"); + + assert_eq!( + DataRepr::Packets.formatted_string(1_000_000_000_000), + "1000000000000" + ); + assert_eq!( + DataRepr::Bytes.formatted_string(1_000_000_000_000), + "1.0 TB" + ); + assert_eq!(DataRepr::Bits.formatted_string(1_000_000_000_000), "1.0 Tb"); + + assert_eq!( + DataRepr::Packets.formatted_string(9_950_000_000_000), + "9950000000000" + ); + assert_eq!( + DataRepr::Bytes.formatted_string(9_950_000_000_000), + "9.9 TB" + ); + assert_eq!(DataRepr::Bits.formatted_string(9_950_000_000_000), "9.9 Tb"); + + assert_eq!( + DataRepr::Packets.formatted_string(9_951_000_000_000), + "9951000000000" + ); + assert_eq!(DataRepr::Bytes.formatted_string(9_951_000_000_000), "10 TB"); + assert_eq!(DataRepr::Bits.formatted_string(9_951_000_000_000), "10 Tb"); + + assert_eq!( + DataRepr::Packets.formatted_string(999_950_000_000_000), + "999950000000000" + ); + assert_eq!( + DataRepr::Bytes.formatted_string(999_950_000_000_000), "999 TB" ); assert_eq!( - ByteMultiple::formatted_string(999_999_999_999_999), + DataRepr::Bits.formatted_string(999_950_000_000_000), + "999 Tb" + ); + + assert_eq!( + DataRepr::Packets.formatted_string(999_999_999_999_999), + "999999999999999" + ); + assert_eq!( + DataRepr::Bytes.formatted_string(999_999_999_999_999), "999 TB" ); assert_eq!( - ByteMultiple::formatted_string(1_000_000_000_000_000), + DataRepr::Bits.formatted_string(999_999_999_999_999), + "999 Tb" + ); + + assert_eq!( + DataRepr::Packets.formatted_string(1_000_000_000_000_000), + "1000000000000000" + ); + assert_eq!( + DataRepr::Bytes.formatted_string(1_000_000_000_000_000), "1.0 PB" ); assert_eq!( - ByteMultiple::formatted_string(1_000_000_000_000_000_0), + DataRepr::Bits.formatted_string(1_000_000_000_000_000), + "1.0 Pb" + ); + + assert_eq!( + DataRepr::Packets.formatted_string(1_000_000_000_000_000_0), + "10000000000000000" + ); + assert_eq!( + DataRepr::Bytes.formatted_string(1_000_000_000_000_000_0), "10 PB" ); assert_eq!( - ByteMultiple::formatted_string(999_999_999_000_000_000), + DataRepr::Bits.formatted_string(1_000_000_000_000_000_0), + "10 Pb" + ); + assert_eq!( + DataRepr::Packets.formatted_string(999_999_999_000_000_000), + "999999999000000000" + ); + assert_eq!( + DataRepr::Bytes.formatted_string(999_999_999_000_000_000), "1000 PB" ); assert_eq!( - ByteMultiple::formatted_string(1_000_000_000_000_000_000_000), - "1000000 PB" + DataRepr::Bits.formatted_string(999_999_999_000_000_000), + "1000 Pb" + ); + + assert_eq!( + DataRepr::Packets.formatted_string(u128::MAX / 2), + "170141183460469231731687303715884105727" ); assert_eq!( - ByteMultiple::formatted_string(u128::MAX / 2), + DataRepr::Bytes.formatted_string(u128::MAX / 2), "170141184077655307190272 PB" ); - assert_eq!(ByteMultiple::formatted_string(u128::MAX), "inf PB"); + assert_eq!( + DataRepr::Bits.formatted_string(u128::MAX / 2), + "170141184077655307190272 Pb" + ); + + assert_eq!( + DataRepr::Packets.formatted_string(u128::MAX), + "340282366920938463463374607431768211455" + ); + assert_eq!(DataRepr::Bytes.formatted_string(u128::MAX), "inf PB"); + assert_eq!(DataRepr::Bits.formatted_string(u128::MAX), "inf Pb"); } } diff --git a/src/report/types/report_sort_type.rs b/src/report/types/report_sort_type.rs index f40dc485..ab0679bf 100644 --- a/src/report/types/report_sort_type.rs +++ b/src/report/types/report_sort_type.rs @@ -30,7 +30,6 @@ pub fn button_type(self) -> ButtonType { #[cfg(test)] mod tests { - use crate::report::types::report_col::ReportCol; use crate::report::types::report_sort_type::ReportSortType; use crate::report::types::sort_type::SortType; @@ -40,116 +39,39 @@ fn test_next_report_sort() { assert_eq!( sort, ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Neutral + data_sort: SortType::Neutral, } ); - sort = sort.next_sort(&ReportCol::Packets); + sort = sort.next_sort(); assert_eq!( sort, ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Descending + data_sort: SortType::Descending, } ); - sort = sort.next_sort(&ReportCol::Packets); + sort = sort.next_sort(); assert_eq!( sort, ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Ascending + data_sort: SortType::Ascending, } ); - sort = sort.next_sort(&ReportCol::Packets); + sort = sort.next_sort(); assert_eq!( sort, ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Neutral + data_sort: SortType::Neutral, } ); - sort = sort.next_sort(&ReportCol::Packets); + sort = sort.next_sort(); assert_eq!( sort, ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Descending - } - ); - - sort = sort.next_sort(&ReportCol::Bytes); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Descending, - packet_sort: SortType::Neutral - } - ); - - sort = sort.next_sort(&ReportCol::Packets); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Descending - } - ); - - sort = sort.next_sort(&ReportCol::Bytes); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Descending, - packet_sort: SortType::Neutral - } - ); - - sort = sort.next_sort(&ReportCol::Bytes); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Ascending, - packet_sort: SortType::Neutral - } - ); - - sort = sort.next_sort(&ReportCol::Packets); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Descending - } - ); - - sort = sort.next_sort(&ReportCol::Bytes); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Descending, - packet_sort: SortType::Neutral - } - ); - - sort = sort.next_sort(&ReportCol::Bytes); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Ascending, - packet_sort: SortType::Neutral - } - ); - - sort = sort.next_sort(&ReportCol::Bytes); - assert_eq!( - sort, - ReportSortType { - byte_sort: SortType::Neutral, - packet_sort: SortType::Neutral + data_sort: SortType::Descending, } ); } From 79114bdd55832cbeaa90ddb0bc4408a3916395ba Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 14 Aug 2025 17:45:33 +0200 Subject: [PATCH 37/74] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ab69aa..817b798d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # Changelog All Sniffnet releases with the relative changes are documented in this file. ## [UNRELEASED] +- Added _bits_ data representation ([#936](https://github.com/GyulyVGC/sniffnet/pull/936) — fixes [#506](https://github.com/GyulyVGC/sniffnet/issues/506)) - An AppImage of Sniffnet is now available ([#859](https://github.com/GyulyVGC/sniffnet/pull/859) — fixes [#900](https://github.com/GyulyVGC/sniffnet/issues/900)) - Added Dutch translation 🇳🇱 ([#854](https://github.com/GyulyVGC/sniffnet/pull/854)) - The Windows Installer is now signed with a code signing certificate provided by the [SignPath Foundation](https://signpath.org/) ([#897](https://github.com/GyulyVGC/sniffnet/pull/897) — fixes [#894](https://github.com/GyulyVGC/sniffnet/issues/894)) From 5755724ce9aac6c3d50e2cc163dd8c6de3a85e1c Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 14 Aug 2025 22:13:33 +0200 Subject: [PATCH 38/74] add test on DataInfo --- src/networking/types/data_info.rs | 112 ++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/networking/types/data_info.rs b/src/networking/types/data_info.rs index 35f55893..975e93e1 100644 --- a/src/networking/types/data_info.rs +++ b/src/networking/types/data_info.rs @@ -128,3 +128,115 @@ fn default() -> Self { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::networking::types::traffic_direction::TrafficDirection; + + #[test] + fn test_data_info() { + // in_packets: 0, out_packets: 0, in_bytes: 0, out_bytes: 0 + let mut data_info_1 = DataInfo::new_with_first_packet(123, TrafficDirection::Incoming); + // 1, 0, 123, 0 + data_info_1.add_packet(100, TrafficDirection::Incoming); + // 2, 0, 223, 0 + data_info_1.add_packet(200, TrafficDirection::Outgoing); + // 2, 1, 223, 200 + data_info_1.add_packets(11, 1200, TrafficDirection::Outgoing); + // 2, 12, 223, 1400 + data_info_1.add_packets(5, 500, TrafficDirection::Incoming); + // 7, 12, 723, 1400 + + assert_eq!(data_info_1.incoming_packets, 7); + assert_eq!(data_info_1.outgoing_packets, 12); + assert_eq!(data_info_1.incoming_bytes, 723); + assert_eq!(data_info_1.outgoing_bytes, 1400); + + assert_eq!(data_info_1.tot_data(DataRepr::Packets), 19); + assert_eq!(data_info_1.tot_data(DataRepr::Bytes), 2123); + assert_eq!(data_info_1.tot_data(DataRepr::Bits), 16984); + + assert_eq!(data_info_1.incoming_data(DataRepr::Packets), 7); + assert_eq!(data_info_1.incoming_data(DataRepr::Bytes), 723); + assert_eq!(data_info_1.incoming_data(DataRepr::Bits), 5784); + + assert_eq!(data_info_1.outgoing_data(DataRepr::Packets), 12); + assert_eq!(data_info_1.outgoing_data(DataRepr::Bytes), 1400); + assert_eq!(data_info_1.outgoing_data(DataRepr::Bits), 11200); + + let mut data_info_2 = DataInfo::new_with_first_packet(100, TrafficDirection::Outgoing); + // 0, 1, 0, 100 + data_info_2.add_packets(19, 300, TrafficDirection::Outgoing); + // 0, 20, 0, 400 + + assert_eq!(data_info_2.incoming_packets, 0); + assert_eq!(data_info_2.outgoing_packets, 20); + assert_eq!(data_info_2.incoming_bytes, 0); + assert_eq!(data_info_2.outgoing_bytes, 400); + + assert_eq!(data_info_2.tot_data(DataRepr::Packets), 20); + assert_eq!(data_info_2.tot_data(DataRepr::Bytes), 400); + assert_eq!(data_info_2.tot_data(DataRepr::Bits), 3200); + + assert_eq!(data_info_2.incoming_data(DataRepr::Packets), 0); + assert_eq!(data_info_2.incoming_data(DataRepr::Bytes), 0); + assert_eq!(data_info_2.incoming_data(DataRepr::Bits), 0); + + assert_eq!(data_info_2.outgoing_data(DataRepr::Packets), 20); + assert_eq!(data_info_2.outgoing_data(DataRepr::Bytes), 400); + assert_eq!(data_info_2.outgoing_data(DataRepr::Bits), 3200); + + // compare data_info_1 and data_info_2 + + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Ascending, DataRepr::Packets), + Ordering::Less + ); + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Descending, DataRepr::Packets), + Ordering::Greater + ); + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Neutral, DataRepr::Packets), + Ordering::Greater + ); + + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Ascending, DataRepr::Bytes), + Ordering::Greater + ); + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Descending, DataRepr::Bytes), + Ordering::Less + ); + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Neutral, DataRepr::Bytes), + Ordering::Greater + ); + + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Ascending, DataRepr::Bits), + Ordering::Greater + ); + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Descending, DataRepr::Bits), + Ordering::Less + ); + assert_eq!( + data_info_1.compare(&data_info_2, SortType::Neutral, DataRepr::Bits), + Ordering::Greater + ); + + // refresh data_info_1 with data_info_2 + assert!(data_info_1.final_instant < data_info_2.final_instant); + data_info_1.refresh(data_info_2); + + // data_info_1 should now contain the sum of both data_info_1 and data_info_2 + assert_eq!(data_info_1.incoming_packets, 7); + assert_eq!(data_info_1.outgoing_packets, 32); + assert_eq!(data_info_1.incoming_bytes, 723); + assert_eq!(data_info_1.outgoing_bytes, 1800); + assert_eq!(data_info_1.final_instant, data_info_2.final_instant); + } +} From 6bcd91767ef119c66e5f473c9eebcb06b945243e Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 14 Aug 2025 22:30:02 +0200 Subject: [PATCH 39/74] add test on InfoAddressPortPair --- .../types/info_address_port_pair.rs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/networking/types/info_address_port_pair.rs b/src/networking/types/info_address_port_pair.rs index e6636323..a872f85a 100644 --- a/src/networking/types/info_address_port_pair.rs +++ b/src/networking/types/info_address_port_pair.rs @@ -80,3 +80,73 @@ pub fn compare(&self, other: &Self, sort_type: SortType, data_repr: DataRepr) -> } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::networking::types::data_representation::DataRepr; + use crate::report::types::sort_type::SortType; + + #[test] + fn test_info_address_port_pair_data() { + let pair1 = InfoAddressPortPair { + transmitted_bytes: 1000, + transmitted_packets: 10, + final_timestamp: Timestamp::new(8, 1300), + ..Default::default() + }; + let pair2 = InfoAddressPortPair { + transmitted_bytes: 1100, + transmitted_packets: 8, + final_timestamp: Timestamp::new(15, 0), + ..Default::default() + }; + + assert_eq!(pair1.transmitted_data(DataRepr::Bytes), 1000); + assert_eq!(pair1.transmitted_data(DataRepr::Packets), 10); + assert_eq!(pair1.transmitted_data(DataRepr::Bits), 8000); + + assert_eq!(pair2.transmitted_data(DataRepr::Bytes), 1100); + assert_eq!(pair2.transmitted_data(DataRepr::Packets), 8); + assert_eq!(pair2.transmitted_data(DataRepr::Bits), 8800); + + assert_eq!( + pair1.compare(&pair2, SortType::Ascending, DataRepr::Bytes), + Ordering::Less + ); + assert_eq!( + pair1.compare(&pair2, SortType::Descending, DataRepr::Bytes), + Ordering::Greater + ); + assert_eq!( + pair1.compare(&pair2, SortType::Neutral, DataRepr::Bytes), + Ordering::Greater + ); + + assert_eq!( + pair1.compare(&pair2, SortType::Ascending, DataRepr::Packets), + Ordering::Greater + ); + assert_eq!( + pair1.compare(&pair2, SortType::Descending, DataRepr::Packets), + Ordering::Less + ); + assert_eq!( + pair1.compare(&pair2, SortType::Neutral, DataRepr::Packets), + Ordering::Greater + ); + + assert_eq!( + pair1.compare(&pair2, SortType::Ascending, DataRepr::Bits), + Ordering::Less + ); + assert_eq!( + pair1.compare(&pair2, SortType::Descending, DataRepr::Bits), + Ordering::Greater + ); + assert_eq!( + pair1.compare(&pair2, SortType::Neutral, DataRepr::Bits), + Ordering::Greater + ); + } +} From ffc94134730390049c2631595b5ae57292a4166d Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 14 Aug 2025 22:45:51 +0200 Subject: [PATCH 40/74] minor improvements --- src/networking/types/data_representation.rs | 2 +- src/translations/translations_2.rs | 1 - src/translations/translations_4.rs | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/networking/types/data_representation.rs b/src/networking/types/data_representation.rs index e98a8161..8e289b27 100644 --- a/src/networking/types/data_representation.rs +++ b/src/networking/types/data_representation.rs @@ -141,7 +141,7 @@ pub fn from_char(ch: char) -> Self { } } - pub fn pretty_print(self, repr: DataRepr) -> String { + fn pretty_print(self, repr: DataRepr) -> String { match repr { DataRepr::Packets => String::new(), DataRepr::Bytes => format!("{}B", self.get_char()), diff --git a/src/translations/translations_2.rs b/src/translations/translations_2.rs index e193426a..376b7d63 100644 --- a/src/translations/translations_2.rs +++ b/src/translations/translations_2.rs @@ -674,7 +674,6 @@ pub fn showing_results_translation( } } -#[allow(dead_code)] pub fn color_gradients_translation(language: Language) -> &'static str { match language { Language::EN => "Apply color gradients", diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index a7cb5b49..9028d663 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -146,7 +146,6 @@ pub fn data_exceeded_translation(language: Language) -> &'static str { } } -#[allow(dead_code)] pub fn bits_exceeded_translation(language: Language) -> &'static str { match language { Language::EN => "Bits threshold exceeded", @@ -161,7 +160,6 @@ pub fn bits_exceeded_translation(language: Language) -> &'static str { } } -#[allow(dead_code)] pub fn bits_translation(language: Language) -> &'static str { match language { Language::EN | Language::IT | Language::NL | Language::DE | Language::FR => "bits", From c7364b9b7195ad6de4b877b4246a8436cd03e29b Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 15 Aug 2025 16:32:32 +0200 Subject: [PATCH 41/74] replace old filters with BPF filter --- src/gui/pages/initial_page.rs | 206 ++---------------- src/gui/pages/overview_page.rs | 55 +++-- src/gui/sniffer.rs | 45 +--- src/gui/styles/text_input.rs | 39 ++-- src/gui/types/message.rs | 12 +- src/networking/parse_packets.rs | 270 ++++++++++++------------ src/networking/types/capture_context.rs | 16 +- src/networking/types/filters.rs | 112 ---------- src/networking/types/ip_collection.rs | 3 - src/networking/types/ip_version.rs | 4 - src/networking/types/mod.rs | 2 - src/networking/types/port_collection.rs | 180 ---------------- src/networking/types/protocol.rs | 4 - src/translations/translations.rs | 54 ++--- src/translations/translations_3.rs | 50 ++--- src/utils/formatted_strings.rs | 62 ------ 16 files changed, 277 insertions(+), 837 deletions(-) delete mode 100644 src/networking/types/filters.rs delete mode 100644 src/networking/types/port_collection.rs diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index cae46f1e..ea6b903e 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -2,7 +2,6 @@ //! //! It contains elements to select network adapter and traffic filters. -use std::collections::HashSet; use std::fmt::Write; use iced::Length::FillPortion; @@ -26,21 +25,18 @@ use crate::gui::types::export_pcap::ExportPcap; use crate::gui::types::message::Message; use crate::networking::types::capture_context::CaptureSource; -use crate::networking::types::filters::Filters; -use crate::networking::types::ip_collection::AddressCollection; -use crate::networking::types::port_collection::PortCollection; use crate::translations::translations::{ address_translation, addresses_translation, choose_adapters_translation, - ip_version_translation, protocol_translation, select_filters_translation, start_translation, + select_filters_translation, start_translation, }; use crate::translations::translations_3::{ - directory_translation, export_capture_translation, file_name_translation, port_translation, + directory_translation, export_capture_translation, file_name_translation, }; use crate::translations::translations_4::import_capture_translation; -use crate::utils::formatted_strings::{get_invalid_filters_string, get_path_termination_string}; +use crate::utils::formatted_strings::get_path_termination_string; use crate::utils::types::file_info::FileInfo; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, IpVersion, Language, Protocol, StyleType}; +use crate::{ConfigSettings, Language, StyleType}; /// Computes the body of gui initial page pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { @@ -61,17 +57,7 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { ); let col_capture_source = Column::new().push(col_adapter).push(col_import_pcap); - let ip_active = &sniffer.filters.ip_versions; - let col_ip_buttons = col_ip_buttons(ip_active, font, language); - - let protocol_active = &sniffer.filters.protocols; - let col_protocol_buttons = col_protocol_buttons(protocol_active, font, language); - - let address_active = &sniffer.filters.address_str; - let col_address_filter = col_address_input(address_active, font, language); - - let port_active = &sniffer.filters.port_str; - let col_port_filter = col_port_input(port_active, font, language); + let col_bpf_filter = col_bpf_input(&sniffer.bpf_filter, font); let filters_pane = Column::new() .width(FillPortion(6)) @@ -83,18 +69,7 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { .class(TextType::Title) .size(FONT_SIZE_TITLE), ) - .push( - Row::new() - .spacing(20) - .push(col_ip_buttons) - .push(col_protocol_buttons), - ) - .push( - Row::new() - .spacing(20) - .push(col_address_filter) - .push(col_port_filter), - ) + .push(col_bpf_filter) .push(Rule::horizontal(40)) .push(get_export_pcap_group( &sniffer.capture_source, @@ -103,16 +78,11 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { font, )) .push( - Container::new(button_start( - font, - language, - color_gradient, - &sniffer.filters, - )) - .width(Length::Fill) - .height(Length::Fill) - .align_y(Alignment::Start) - .align_x(Alignment::Center), + Container::new(button_start(font, language, color_gradient)) + .width(Length::Fill) + .height(Length::Fill) + .align_y(Alignment::Start) + .align_x(Alignment::Center), ); let body = Column::new().push(Space::with_height(5)).push( @@ -125,112 +95,20 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { Container::new(body).height(Length::Fill) } -fn col_ip_buttons( - active_ip_filters: &HashSet, - font: Font, - language: Language, -) -> Column<'_, Message, StyleType> { - let mut buttons_row = Row::new().spacing(5).padding(Padding::ZERO.left(5)); - for option in IpVersion::ALL { - let is_active = active_ip_filters.contains(&option); - let check_symbol = if is_active { "✔" } else { "✘" }; - buttons_row = buttons_row.push( - Button::new( - Text::new(format!("{option} {check_symbol}")) - .align_x(Alignment::Center) - .align_y(Alignment::Center) - .font(font), - ) - .width(80) - .height(35) - .class(if is_active { - ButtonType::BorderedRoundSelected - } else { - ButtonType::BorderedRound - }) - .on_press(Message::IpVersionSelection(option, !is_active)), - ); - } - - Column::new() - .width(Length::Fill) - .spacing(7) - .push( - Text::new(ip_version_translation(language)) - .font(font) - .class(TextType::Subtitle) - .size(FONT_SIZE_SUBTITLE), - ) - .push(buttons_row) -} - -fn col_protocol_buttons( - active_protocol_filters: &HashSet, - font: Font, - language: Language, -) -> Column<'_, Message, StyleType> { - let mut buttons_row = Row::new().spacing(5).padding(Padding::ZERO.left(5)); - for option in Protocol::ALL { - let is_active = active_protocol_filters.contains(&option); - let check_symbol = if is_active { "✔" } else { "✘" }; - buttons_row = buttons_row.push( - Button::new( - Text::new(format!("{option} {check_symbol}")) - .align_x(Alignment::Center) - .align_y(Alignment::Center) - .font(font), - ) - .width(80) - .height(35) - .class(if is_active { - ButtonType::BorderedRoundSelected - } else { - ButtonType::BorderedRound - }) - .on_press(Message::ProtocolSelection(option, !is_active)), - ); - } - - Column::new() - .width(Length::Fill) - .spacing(7) - .push( - Text::new(protocol_translation(language)) - .font(font) - .class(TextType::Subtitle) - .size(FONT_SIZE_SUBTITLE), - ) - .push(buttons_row) -} - -fn col_address_input( - value: &str, - font: Font, - language: Language, -) -> Column<'_, Message, StyleType> { - let is_error = if value.is_empty() { - false - } else { - AddressCollection::new(value).is_none() - }; +fn col_bpf_input(value: &str, font: Font) -> Column<'_, Message, StyleType> { let input_row = Row::new().padding(Padding::ZERO.left(5)).push( - TextInput::new(AddressCollection::PLACEHOLDER_STR, value) + TextInput::new("Berkeley Packet Filter (BPF)", value) .padding([3, 5]) - .on_input(Message::AddressFilter) + .on_input(Message::BpfFilter) .font(font) - .width(310) - .class(if is_error { - TextInputType::Error - } else { - TextInputType::Standard - }), + .class(TextInputType::Standard), ); Column::new() .width(Length::Fill) .spacing(7) .push( - Text::new(address_translation(language)) + Text::new("Berkeley Packet Filter (BPF)") .font(font) .class(TextType::Subtitle) .size(FONT_SIZE_SUBTITLE), @@ -238,44 +116,12 @@ fn col_address_input( .push(input_row) } -fn col_port_input(value: &str, font: Font, language: Language) -> Column<'_, Message, StyleType> { - let is_error = if value.is_empty() { - false - } else { - PortCollection::new(value).is_none() - }; - let input_row = Row::new().padding(Padding::ZERO.left(5)).push( - TextInput::new(PortCollection::PLACEHOLDER_STR, value) - .padding([3, 5]) - .on_input(Message::PortFilter) - .font(font) - .width(180) - .class(if is_error { - TextInputType::Error - } else { - TextInputType::Standard - }), - ); - - Column::new() - .width(Length::Fill) - .spacing(7) - .push( - Text::new(port_translation(language)) - .font(font) - .class(TextType::Subtitle) - .size(FONT_SIZE_SUBTITLE), - ) - .push(input_row) -} - -fn button_start( +fn button_start<'a>( font: Font, language: Language, color_gradient: GradientType, - filters: &Filters, -) -> Tooltip<'_, Message, StyleType> { - let mut content = button( +) -> Tooltip<'a, Message, StyleType> { + let content = button( Icon::Rocket .to_text() .size(25) @@ -285,18 +131,12 @@ fn button_start( .padding(10) .height(80) .width(160) - .class(ButtonType::Gradient(color_gradient)); + .class(ButtonType::Gradient(color_gradient)) + .on_press(Message::Start); - let mut tooltip = start_translation(language).to_string(); + let tooltip = start_translation(language).to_string(); //tooltip.push_str(" [⏎]"); - let mut position = Position::Top; - - if filters.are_valid() { - content = content.on_press(Message::Start); - } else { - tooltip = get_invalid_filters_string(filters, language); - position = Position::FollowCursor; - } + let position = Position::Top; Tooltip::new(content, Text::new(tooltip).font(font), position) .gap(5) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index d797a57b..d25020b4 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -20,7 +20,6 @@ use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::data_representation::DataRepr; -use crate::networking::types::filters::Filters; use crate::networking::types::host::Host; use crate::networking::types::service::Service; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; @@ -37,7 +36,6 @@ }; use crate::translations::translations_3::{service_translation, unsupported_link_type_translation}; use crate::translations::translations_4::{excluded_translation, reading_from_pcap_translation}; -use crate::utils::formatted_strings::get_active_filters_string; use crate::utils::types::icon::Icon; use crate::{ConfigSettings, Language, RunningPage, StyleType}; use iced::Length::{Fill, FillPortion}; @@ -83,7 +81,7 @@ pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { } (observed, 0) => { //no packets have been filtered but some have been observed - body = body_no_observed(&sniffer.filters, observed, font, language, dots); + body = body_no_observed(&sniffer.bpf_filter, observed, font, language, dots); } (_observed, _filtered) => { //observed > filtered > 0 || observed = filtered > 0 @@ -175,7 +173,7 @@ fn body_no_packets<'a>( } fn body_no_observed<'a>( - filters: &Filters, + bpf: &'a str, observed: u128, font: Font, language: Language, @@ -192,7 +190,7 @@ fn body_no_observed<'a>( .align_x(Alignment::Center) .push(vertical_space()) .push(Icon::Funnel.to_text().size(60)) - .push(get_active_filters_col(filters, language, font)) + .push(get_active_filters_col(bpf, language, font)) .push(Rule::horizontal(20)) .push(tot_packets_text) .push(Text::new(dots.to_owned()).font(font).size(50)) @@ -464,7 +462,7 @@ pub fn service_bar<'a>( ) } -fn col_info<'a>(sniffer: &Sniffer) -> Container<'a, Message, StyleType> { +fn col_info(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, language, .. } = sniffer.configs.settings; @@ -585,25 +583,25 @@ fn col_data_representation<'a>( ret_val } -fn donut_row<'a>( +fn donut_row( language: Language, font: Font, sniffer: &Sniffer, -) -> Container<'a, Message, StyleType> { +) -> Container<'_, Message, StyleType> { let data_repr = sniffer.traffic_chart.data_repr; - let filters = &sniffer.filters; + let bpf = &sniffer.bpf_filter; let (in_data, out_data, filtered_out, dropped) = sniffer.info_traffic.get_thumbnail_data(data_repr); - let legend_entry_filtered = if filters.none_active() { + let legend_entry_filtered = if bpf.trim().is_empty() { None } else { Some(donut_legend_entry( filtered_out, data_repr, RuleType::FilteredOut, - filters, + bpf, font, language, )) @@ -615,7 +613,7 @@ fn donut_row<'a>( in_data, data_repr, RuleType::Incoming, - filters, + bpf, font, language, )) @@ -623,7 +621,7 @@ fn donut_row<'a>( out_data, data_repr, RuleType::Outgoing, - filters, + bpf, font, language, )) @@ -632,7 +630,7 @@ fn donut_row<'a>( dropped, data_repr, RuleType::Dropped, - filters, + bpf, font, language, )); @@ -658,14 +656,14 @@ fn donut_row<'a>( .align_y(Vertical::Center) } -fn donut_legend_entry<'a>( +fn donut_legend_entry( value: u128, data_repr: DataRepr, rule_type: RuleType, - filters: &Filters, + bpf: &str, font: Font, language: Language, -) -> Row<'a, Message, StyleType> { +) -> Row<'_, Message, StyleType> { let value_text = data_repr.formatted_string(value); let label = match rule_type { @@ -677,7 +675,7 @@ fn donut_legend_entry<'a>( }; let tooltip = if matches!(rule_type, RuleType::FilteredOut) { - Some(get_active_filters_tooltip(filters, language, font)) + Some(get_active_filters_tooltip(bpf, language, font)) } else { None }; @@ -794,40 +792,37 @@ fn get_star_button<'a>(is_favorite: bool, host: Host) -> Button<'a, Message, Sty .on_press(Message::AddOrRemoveFavorite(host, !is_favorite)) } -fn get_active_filters_col<'a>( - filters: &Filters, +fn get_active_filters_col( + bpf: &str, language: Language, font: Font, -) -> Column<'a, Message, StyleType> { +) -> Column<'_, Message, StyleType> { let mut ret_val = Column::new().push( Text::new(active_filters_translation(language)) .font(font) .class(TextType::Subtitle), ); - if filters.none_active() { + if bpf.trim().is_empty() { ret_val = ret_val.push(Text::new(format!(" {}", none_translation(language))).font(font)); } else { - let filters_string = get_active_filters_string(filters, language); - ret_val = ret_val.push(Row::new().push(Text::new(filters_string).font(font))); + ret_val = ret_val.push(Row::new().push(Text::new(bpf).font(font))); } ret_val } -fn get_active_filters_tooltip<'a>( - filters: &Filters, +fn get_active_filters_tooltip( + bpf: &str, language: Language, font: Font, -) -> Tooltip<'a, Message, StyleType> { - let filters_string = get_active_filters_string(filters, language); - +) -> Tooltip<'_, Message, StyleType> { let mut ret_val = Column::new().push( Text::new(active_filters_translation(language)) .font(font) .class(TextType::Subtitle), ); - ret_val = ret_val.push(Row::new().push(Text::new(filters_string).font(font))); + ret_val = ret_val.push(Row::new().push(Text::new(bpf).font(font))); Tooltip::new( Container::new( diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index aa7fea6c..74c70293 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -47,13 +47,10 @@ use crate::networking::parse_packets::parse_packets; use crate::networking::types::capture_context::{CaptureContext, CaptureSource, MyPcapImport}; use crate::networking::types::data_representation::DataRepr; -use crate::networking::types::filters::Filters; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::host_data_states::HostDataStates; use crate::networking::types::info_traffic::InfoTraffic; -use crate::networking::types::ip_collection::AddressCollection; use crate::networking::types::my_device::MyDevice; -use crate::networking::types::port_collection::PortCollection; use crate::notifications::notify_and_log::notify_and_log; use crate::notifications::types::logged_notification::LoggedNotification; use crate::notifications::types::notifications::{DataNotification, Notification}; @@ -94,8 +91,8 @@ pub struct Sniffer { pub capture_source: CaptureSource, /// List of network devices pub my_devices: Vec, - /// Active filters on the observed traffic - pub filters: Filters, + /// BPF filter program to be applied to the capture + pub bpf_filter: String, /// Signals if a pcap error occurred pub pcap_error: Option, /// Messages status @@ -158,7 +155,7 @@ pub fn new(configs: Configs) -> Self { newer_release_available: None, capture_source: CaptureSource::Device(device), my_devices: Vec::new(), - filters: Filters::default(), + bpf_filter: String::new(), pcap_error: None, dots_pulse: (".".to_string(), 0), traffic_chart: TrafficChart::new(style, language), @@ -279,31 +276,8 @@ pub fn update(&mut self, message: Message) -> Task { } } Message::DeviceSelection(name) => self.set_device(&name), - Message::IpVersionSelection(version, insert) => { - if insert { - self.filters.ip_versions.insert(version); - } else { - self.filters.ip_versions.remove(&version); - } - } - Message::ProtocolSelection(protocol, insert) => { - if insert { - self.filters.protocols.insert(protocol); - } else { - self.filters.protocols.remove(&protocol); - } - } - Message::AddressFilter(value) => { - if let Some(collection) = AddressCollection::new(&value) { - self.filters.address_collection = collection; - } - self.filters.address_str = value; - } - Message::PortFilter(value) => { - if let Some(collection) = PortCollection::new(&value) { - self.filters.port_collection = collection; - } - self.filters.port_str = value; + Message::BpfFilter(value) => { + self.bpf_filter = value; } Message::DataReprSelection(unit) => self.traffic_chart.change_kind(unit), Message::ReportSortSelection(sort) => { @@ -741,14 +715,14 @@ fn start(&mut self) -> Task { self.set_device(current_device_name); } let pcap_path = self.export_pcap.full_path(); - let capture_context = CaptureContext::new(&self.capture_source, pcap_path.as_ref()); + let capture_context = + CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.bpf_filter); self.pcap_error = capture_context.error().map(ToString::to_string); self.running_page = RunningPage::Overview; if capture_context.error().is_none() { // no pcap error let curr_cap_id = self.current_capture_rx.0; - let filters = self.filters.clone(); let mmdb_readers = self.mmdb_readers.clone(); self.capture_source .set_link_type(capture_context.my_link_type()); @@ -762,7 +736,6 @@ fn start(&mut self) -> Task { parse_packets( curr_cap_id, capture_source, - &filters, &mmdb_readers, capture_context, &tx, @@ -971,9 +944,7 @@ fn shortcut_return(&mut self) -> Task { && self.settings_page.is_none() && self.modal.is_none() { - if self.filters.are_valid() { - return Task::done(Message::Start); - } + return Task::done(Message::Start); } else if self.modal.eq(&Some(MyModal::Reset)) { return Task::done(Message::Reset); } else if self.modal.eq(&Some(MyModal::Quit)) { diff --git a/src/gui/styles/text_input.rs b/src/gui/styles/text_input.rs index 0f12efc4..63d0d5b8 100644 --- a/src/gui/styles/text_input.rs +++ b/src/gui/styles/text_input.rs @@ -13,7 +13,7 @@ pub enum TextInputType { #[default] Standard, Badge, - Error, + // Error, } const TEXT_INPUT_BORDER_RADIUS: f32 = 5.0; @@ -25,7 +25,8 @@ fn active(&self, style: &StyleType) -> Style { Style { background: Background::Color(match self { TextInputType::Badge => Color::TRANSPARENT, - _ => Color { + // TextInputType::Error | + TextInputType::Standard => Color { a: ext.alpha_round_borders, ..ext.buttons_color }, @@ -36,7 +37,7 @@ fn active(&self, style: &StyleType) -> Style { color: match self { TextInputType::Badge => Color::TRANSPARENT, TextInputType::Standard => ext.buttons_color, - TextInputType::Error => ext.red_alert_color, + // TextInputType::Error => ext.red_alert_color, }, }, icon: Color { @@ -51,17 +52,17 @@ fn active(&self, style: &StyleType) -> Style { fn focused(&self, style: &StyleType) -> Style { let colors = style.get_palette(); - let ext = style.get_extension(); + // let ext = style.get_extension(); let is_nightly = style.get_extension().is_nightly; Style { background: Background::Color(colors.primary), border: Border { radius: TEXT_INPUT_BORDER_RADIUS.into(), width: BORDER_WIDTH, - color: match self { - TextInputType::Error => ext.red_alert_color, - _ => colors.secondary, - }, + color: colors.secondary, // match self { + // TextInputType::Error => ext.red_alert_color, + // _ => colors.secondary, + // }, }, icon: Color { a: if is_nightly { 0.2 } else { 0.7 }, @@ -114,15 +115,16 @@ fn hovered(&self, style: &StyleType) -> Style { Style { background: Background::Color(match self { TextInputType::Badge => Color::TRANSPARENT, - _ => ext.buttons_color, + // TextInputType::Error | + TextInputType::Standard => ext.buttons_color, }), border: Border { radius: TEXT_INPUT_BORDER_RADIUS.into(), width: BORDER_WIDTH, - color: match self { - TextInputType::Error => ext.red_alert_color, - _ => colors.secondary, - }, + color: colors.secondary, // match self { + // TextInputType::Error => ext.red_alert_color, + // _ => colors.secondary, + // }, }, icon: Color { a: if ext.is_nightly { 0.2 } else { 0.7 }, @@ -140,7 +142,8 @@ fn disabled(&self, style: &StyleType) -> Style { Style { background: Background::Color(match self { TextInputType::Badge => Color::TRANSPARENT, - _ => Color { + // TextInputType::Error | + TextInputType::Standard => Color { a: ext.alpha_round_containers, ..ext.buttons_color }, @@ -154,10 +157,10 @@ fn disabled(&self, style: &StyleType) -> Style { a: ext.alpha_round_borders, ..ext.buttons_color }, - TextInputType::Error => Color { - a: ext.alpha_round_borders, - ..ext.red_alert_color - }, + // TextInputType::Error => Color { + // a: ext.alpha_round_borders, + // ..ext.red_alert_color + // }, }, }, icon: Color { diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index d2a39194..491e7b61 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -13,7 +13,7 @@ use crate::report::types::sort_type::SortType; use crate::utils::types::file_info::FileInfo; use crate::utils::types::web_page::WebPage; -use crate::{IpVersion, Language, Protocol, ReportSortType, StyleType}; +use crate::{Language, ReportSortType, StyleType}; #[derive(Debug, Clone)] /// Messages types that permit reacting to application interactions/subscriptions @@ -24,14 +24,8 @@ pub enum Message { TickRun(usize, InfoTraffic, Vec, bool), /// Select network device DeviceSelection(String), - /// Select IP filter - IpVersionSelection(IpVersion, bool), - /// Select protocol filter - ProtocolSelection(Protocol, bool), - /// Changed address filter - AddressFilter(String), - /// Changed port filter - PortFilter(String), + /// Changed BPF filter + BpfFilter(String), /// Select data representation to use DataReprSelection(DataRepr), /// Select report sort type to be displayed (inspect page) diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index 14762bba..5201f75c 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -14,7 +14,6 @@ use crate::networking::types::capture_context::{CaptureContext, CaptureSource}; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; -use crate::networking::types::filters::Filters; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::icmp_type::IcmpType; use crate::networking::types::info_traffic::InfoTraffic; @@ -40,7 +39,6 @@ pub fn parse_packets( cap_id: usize, mut cs: CaptureSource, - filters: &Filters, mmdb_readers: &MmdbReaders, capture_context: CaptureContext, tx: &Sender, @@ -137,144 +135,138 @@ pub fn parse_packets( continue; }; - let passed_filters = filters.matches(&packet_filters_fields); - if passed_filters { - // save this packet to PCAP file - if let Some(file) = savefile.as_mut() { - file.write(&packet); - } - // update the map - let (traffic_direction, service) = modify_or_insert_in_map( - &mut info_traffic_msg, - &key, - &cs, - mac_addresses, - icmp_type, - arp_type, - exchanged_bytes, - ); - - info_traffic_msg - .tot_data_info - .add_packet(exchanged_bytes, traffic_direction); - - // check the rDNS status of this address and act accordingly - let address_to_lookup = get_address_to_lookup(&key, traffic_direction); - let mut r_dns_waiting_resolution = false; - let mut resolutions_lock = resolutions_state.lock().unwrap(); - let r_dns_already_resolved = resolutions_lock - .addresses_resolved - .contains_key(&address_to_lookup); - if !r_dns_already_resolved { - r_dns_waiting_resolution = resolutions_lock - .addresses_waiting_resolution - .contains_key(&address_to_lookup); - } - - match (r_dns_waiting_resolution, r_dns_already_resolved) { - (false, false) => { - // rDNS not requested yet (first occurrence of this address to lookup) - - // Add this address to the map of addresses waiting for a resolution - // Useful to NOT perform again a rDNS lookup for this entry - resolutions_lock.addresses_waiting_resolution.insert( - address_to_lookup, - DataInfo::new_with_first_packet( - exchanged_bytes, - traffic_direction, - ), - ); - drop(resolutions_lock); - - // launch new thread to resolve host name - let key2 = key; - let resolutions_state2 = resolutions_state.clone(); - let new_hosts_to_send2 = new_hosts_to_send.clone(); - let interface_addresses = cs.get_addresses().clone(); - let mmdb_readers_2 = mmdb_readers.clone(); - let tx2 = tx.clone(); - let _ = thread::Builder::new() - .name("thread_reverse_dns_lookup".to_string()) - .spawn(move || { - reverse_dns_lookup( - &resolutions_state2, - &new_hosts_to_send2, - &key2, - traffic_direction, - &interface_addresses, - &mmdb_readers_2, - &tx2, - ); - }) - .log_err(location!()); - } - (true, false) => { - // waiting for a previously requested rDNS resolution - // update the corresponding waiting address data - resolutions_lock - .addresses_waiting_resolution - .entry(address_to_lookup) - .and_modify(|data_info| { - data_info.add_packet(exchanged_bytes, traffic_direction); - }); - drop(resolutions_lock); - } - (_, true) => { - // rDNS already resolved - // update the corresponding host's data info - let host = resolutions_lock - .addresses_resolved - .get(&address_to_lookup) - .unwrap_or(&Host::default()) - .clone(); - drop(resolutions_lock); - info_traffic_msg - .hosts - .entry(host) - .and_modify(|data_info_host| { - data_info_host - .data_info - .add_packet(exchanged_bytes, traffic_direction); - }) - .or_insert_with(|| { - let my_interface_addresses = cs.get_addresses(); - let traffic_type = get_traffic_type( - &address_to_lookup, - my_interface_addresses, - traffic_direction, - ); - let is_loopback = address_to_lookup.is_loopback(); - let is_local = is_local_connection( - &address_to_lookup, - my_interface_addresses, - ); - let is_bogon = is_bogon(&address_to_lookup); - DataInfoHost { - data_info: DataInfo::new_with_first_packet( - exchanged_bytes, - traffic_direction, - ), - is_favorite: false, - is_loopback, - is_local, - is_bogon, - traffic_type, - } - }); - } - } - - //increment the packet count for the sniffed service - info_traffic_msg - .services - .entry(service) - .and_modify(|data_info| { - data_info.add_packet(exchanged_bytes, traffic_direction); - }) - .or_insert_with(|| { - DataInfo::new_with_first_packet(exchanged_bytes, traffic_direction) - }); + // save this packet to PCAP file + if let Some(file) = savefile.as_mut() { + file.write(&packet); } + // update the map + let (traffic_direction, service) = modify_or_insert_in_map( + &mut info_traffic_msg, + &key, + &cs, + mac_addresses, + icmp_type, + arp_type, + exchanged_bytes, + ); + + info_traffic_msg + .tot_data_info + .add_packet(exchanged_bytes, traffic_direction); + + // check the rDNS status of this address and act accordingly + let address_to_lookup = get_address_to_lookup(&key, traffic_direction); + let mut r_dns_waiting_resolution = false; + let mut resolutions_lock = resolutions_state.lock().unwrap(); + let r_dns_already_resolved = resolutions_lock + .addresses_resolved + .contains_key(&address_to_lookup); + if !r_dns_already_resolved { + r_dns_waiting_resolution = resolutions_lock + .addresses_waiting_resolution + .contains_key(&address_to_lookup); + } + + match (r_dns_waiting_resolution, r_dns_already_resolved) { + (false, false) => { + // rDNS not requested yet (first occurrence of this address to lookup) + + // Add this address to the map of addresses waiting for a resolution + // Useful to NOT perform again a rDNS lookup for this entry + resolutions_lock.addresses_waiting_resolution.insert( + address_to_lookup, + DataInfo::new_with_first_packet(exchanged_bytes, traffic_direction), + ); + drop(resolutions_lock); + + // launch new thread to resolve host name + let key2 = key; + let resolutions_state2 = resolutions_state.clone(); + let new_hosts_to_send2 = new_hosts_to_send.clone(); + let interface_addresses = cs.get_addresses().clone(); + let mmdb_readers_2 = mmdb_readers.clone(); + let tx2 = tx.clone(); + let _ = thread::Builder::new() + .name("thread_reverse_dns_lookup".to_string()) + .spawn(move || { + reverse_dns_lookup( + &resolutions_state2, + &new_hosts_to_send2, + &key2, + traffic_direction, + &interface_addresses, + &mmdb_readers_2, + &tx2, + ); + }) + .log_err(location!()); + } + (true, false) => { + // waiting for a previously requested rDNS resolution + // update the corresponding waiting address data + resolutions_lock + .addresses_waiting_resolution + .entry(address_to_lookup) + .and_modify(|data_info| { + data_info.add_packet(exchanged_bytes, traffic_direction); + }); + drop(resolutions_lock); + } + (_, true) => { + // rDNS already resolved + // update the corresponding host's data info + let host = resolutions_lock + .addresses_resolved + .get(&address_to_lookup) + .unwrap_or(&Host::default()) + .clone(); + drop(resolutions_lock); + info_traffic_msg + .hosts + .entry(host) + .and_modify(|data_info_host| { + data_info_host + .data_info + .add_packet(exchanged_bytes, traffic_direction); + }) + .or_insert_with(|| { + let my_interface_addresses = cs.get_addresses(); + let traffic_type = get_traffic_type( + &address_to_lookup, + my_interface_addresses, + traffic_direction, + ); + let is_loopback = address_to_lookup.is_loopback(); + let is_local = is_local_connection( + &address_to_lookup, + my_interface_addresses, + ); + let is_bogon = is_bogon(&address_to_lookup); + DataInfoHost { + data_info: DataInfo::new_with_first_packet( + exchanged_bytes, + traffic_direction, + ), + is_favorite: false, + is_loopback, + is_local, + is_bogon, + traffic_type, + } + }); + } + } + + //increment the packet count for the sniffed service + info_traffic_msg + .services + .entry(service) + .and_modify(|data_info| { + data_info.add_packet(exchanged_bytes, traffic_direction); + }) + .or_insert_with(|| { + DataInfo::new_with_first_packet(exchanged_bytes, traffic_direction) + }); //increment number of sniffed packets and bytes info_traffic_msg.all_packets += 1; diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index c2595cff..ae30cb31 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -14,11 +14,16 @@ pub enum CaptureContext { } impl CaptureContext { - pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>) -> Self { - let cap_type = match CaptureType::from_source(source, pcap_out_path) { + pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>, bpf: &str) -> Self { + let mut cap_type = match CaptureType::from_source(source, pcap_out_path) { Ok(c) => c, Err(e) => return Self::Error(e.to_string()), }; + + if let Err(e) = cap_type.set_bpf(bpf) { + return Self::Error(e.to_string()); + } + let cap = match cap_type { CaptureType::Live(cap) => cap, CaptureType::Offline(cap) => return Self::new_offline(cap), @@ -131,6 +136,13 @@ fn from_source(source: &CaptureSource, pcap_out_path: Option<&String>) -> Result CaptureSource::File(file) => Ok(Self::Offline(Capture::from_file(&file.path)?)), } } + + fn set_bpf(&mut self, bpf: &str) -> Result<(), Error> { + match self { + Self::Live(cap) => cap.filter(bpf, true), + Self::Offline(cap) => cap.filter(bpf, true), + } + } } #[derive(Clone)] diff --git a/src/networking/types/filters.rs b/src/networking/types/filters.rs deleted file mode 100644 index 62f7e1e4..00000000 --- a/src/networking/types/filters.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Module defining the `Filters` struct, which represents the possible filters applicable on network traffic. - -use std::collections::HashSet; - -use crate::networking::types::ip_collection::AddressCollection; -use crate::networking::types::packet_filters_fields::PacketFiltersFields; -use crate::networking::types::port_collection::PortCollection; -use crate::{IpVersion, Protocol}; - -/// Possible filters applicable to network traffic -#[derive(Clone)] -pub struct Filters { - /// Internet Protocol versions - pub ip_versions: HashSet, - /// Protocols - pub protocols: HashSet, - /// IP addresses string in Initial page text input - pub address_str: String, - /// IP address collection to match against traffic - pub address_collection: AddressCollection, - /// Ports string in Initial page text input - pub port_str: String, - /// Port collection to match against traffic - pub port_collection: PortCollection, -} - -impl Default for Filters { - fn default() -> Self { - Self { - ip_versions: HashSet::from(IpVersion::ALL), - protocols: HashSet::from(Protocol::ALL), - address_str: String::new(), - address_collection: AddressCollection::default(), - port_str: String::new(), - port_collection: PortCollection::default(), - } - } -} - -impl Filters { - /// Checks whether the filters match the current packet's protocols - pub fn matches(&self, packet_filters_fields: &PacketFiltersFields) -> bool { - self.ip_versions.contains(&packet_filters_fields.ip_version) - && self.protocols.contains(&packet_filters_fields.protocol) - && (self - .address_collection - .contains(&packet_filters_fields.source) - || self - .address_collection - .contains(&packet_filters_fields.dest)) - && (self.port_collection.contains(packet_filters_fields.sport) - || self.port_collection.contains(packet_filters_fields.dport)) - } - - pub fn are_valid(&self) -> bool { - self.ip_version_valid() - && self.protocol_valid() - && self.address_valid() - && self.port_valid() - } - - pub fn ip_version_valid(&self) -> bool { - !self.ip_versions.is_empty() - } - - pub fn protocol_valid(&self) -> bool { - !self.protocols.is_empty() - } - - pub fn address_valid(&self) -> bool { - AddressCollection::new(&self.address_str).is_some() - } - - pub fn port_valid(&self) -> bool { - PortCollection::new(&self.port_str).is_some() - } - - pub fn none_active(&self) -> bool { - !self.ip_version_active() - && !self.protocol_active() - && !self.address_active() - && !self.port_active() - } - - pub fn ip_version_active(&self) -> bool { - self.ip_versions.len() != IpVersion::ALL.len() - } - - pub fn protocol_active(&self) -> bool { - self.protocols.len() != Protocol::ALL.len() - } - - pub fn address_active(&self) -> bool { - self.address_collection != AddressCollection::default() - } - - pub fn port_active(&self) -> bool { - self.port_collection != PortCollection::default() - } - - pub fn pretty_print_ip(&self) -> String { - format!("{:?}", self.ip_versions) - .replace('{', "") - .replace('}', "") - } - - pub fn pretty_print_protocol(&self) -> String { - format!("{:?}", self.protocols) - .replace('{', "") - .replace('}', "") - } -} diff --git a/src/networking/types/ip_collection.rs b/src/networking/types/ip_collection.rs index 20b469ac..77e57b09 100644 --- a/src/networking/types/ip_collection.rs +++ b/src/networking/types/ip_collection.rs @@ -12,9 +12,6 @@ impl AddressCollection { const SEPARATOR: char = ','; const RANGE_SEPARATOR: char = '-'; - pub const PLACEHOLDER_STR: &'static str = - "0.0.0.0-255.255.255.255, ::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"; - pub(crate) fn new(str: &str) -> Option { let str = str.replace(' ', ""); diff --git a/src/networking/types/ip_version.rs b/src/networking/types/ip_version.rs index 1b844472..c8de3a6f 100644 --- a/src/networking/types/ip_version.rs +++ b/src/networking/types/ip_version.rs @@ -15,10 +15,6 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { } } -impl IpVersion { - pub(crate) const ALL: [IpVersion; 2] = [IpVersion::IPv4, IpVersion::IPv6]; -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/networking/types/mod.rs b/src/networking/types/mod.rs index 2dcc2ab2..e4710381 100644 --- a/src/networking/types/mod.rs +++ b/src/networking/types/mod.rs @@ -6,7 +6,6 @@ pub mod data_info; pub mod data_info_host; pub mod data_representation; -pub mod filters; pub mod host; pub mod host_data_states; pub mod icmp_type; @@ -17,7 +16,6 @@ pub mod my_device; pub mod my_link_type; pub mod packet_filters_fields; -pub mod port_collection; pub mod protocol; pub mod service; pub mod service_query; diff --git a/src/networking/types/port_collection.rs b/src/networking/types/port_collection.rs deleted file mode 100644 index 393c35a4..00000000 --- a/src/networking/types/port_collection.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::ops::RangeInclusive; -use std::str::FromStr; - -#[derive(Debug, Eq, PartialEq, Clone)] -pub(crate) struct PortCollection { - pub(crate) ports: Vec, - pub(crate) ranges: Vec>, -} - -impl PortCollection { - const SEPARATOR: char = ','; - const RANGE_SEPARATOR: char = '-'; - - pub const PLACEHOLDER_STR: &'static str = "0-65535"; - - pub(crate) fn new(str: &str) -> Option { - let str = str.replace(' ', ""); - - if str.is_empty() { - return Some(Self::default()); - } - - let mut ports = Vec::new(); - let mut ranges = Vec::new(); - - let objects: Vec<&str> = str.split(Self::SEPARATOR).collect(); - for object in objects { - if object.contains(Self::RANGE_SEPARATOR) { - // port range - let mut subparts = object.split(Self::RANGE_SEPARATOR); - if subparts.clone().count() != 2 { - return None; - } - let (lower_str, upper_str) = - (subparts.next().unwrap_or(""), subparts.next().unwrap_or("")); - let lower_port = u16::from_str(lower_str).ok()?; - let upper_port = u16::from_str(upper_str).ok()?; - let range = RangeInclusive::new(lower_port, upper_port); - if range.is_empty() { - return None; - } - ranges.push(range); - } else { - // individual port - let port = u16::from_str(object).ok()?; - ports.push(port); - } - } - - Some(Self { ports, ranges }) - } - - pub(crate) fn contains(&self, port: Option) -> bool { - // ignore port filter in case of ICMP or ARP - let Some(p) = port else { - return true; - }; - - for range in &self.ranges { - if range.contains(&p) { - return true; - } - } - self.ports.contains(&p) - } -} - -impl Default for PortCollection { - fn default() -> Self { - PortCollection { - ports: vec![], - ranges: vec![RangeInclusive::new(u16::MIN, u16::MAX)], - } - } -} - -#[cfg(test)] -mod tests { - use crate::networking::types::port_collection::PortCollection; - - #[test] - fn test_default_collection_contains_everything() { - let collection = PortCollection::default(); - assert!(collection.contains(Some(0))); - assert!(collection.contains(Some(1))); - assert!(collection.contains(Some(2))); - assert!(collection.contains(Some(80))); - assert!(collection.contains(Some(8080))); - assert!(collection.contains(Some(55333))); - assert!(collection.contains(Some(65535))); - } - - #[test] - fn test_new_port_collections() { - assert_eq!( - PortCollection::new("0").unwrap(), - PortCollection { - ports: vec![0], - ranges: vec![] - } - ); - - assert_eq!( - PortCollection::new(" 0 ").unwrap(), - PortCollection { - ports: vec![0], - ranges: vec![] - } - ); - - assert_eq!( - PortCollection::new("1,2,3,4,999").unwrap(), - PortCollection { - ports: vec![1, 2, 3, 4, 999], - ranges: vec![] - } - ); - - assert_eq!( - PortCollection::new("1, 2, 3, 4, 900-999").unwrap(), - PortCollection { - ports: vec![1, 2, 3, 4], - ranges: vec![900..=999] - } - ); - - assert_eq!( - PortCollection::new("1 - 999").unwrap(), - PortCollection { - ports: vec![], - ranges: vec![1..=999] - } - ); - - assert_eq!( - PortCollection::new(" 1,2,10-20,3,4, 999-1200 ").unwrap(), - PortCollection { - ports: vec![1, 2, 3, 4], - ranges: vec![10..=20, 999..=1200] - } - ); - } - - #[test] - fn test_new_port_collections_invalid() { - assert_eq!(PortCollection::new("1,2,10-20,3,4,-1200"), None); - - assert_eq!(PortCollection::new("1,2,10-20,3,4,999:1200"), None); - - assert_eq!(PortCollection::new("1,2,10-20,3,4,999-1200,"), None); - - assert_eq!(PortCollection::new("999-1"), None); - - assert_eq!(PortCollection::new("1:999"), None); - - assert_eq!(PortCollection::new("1-2-3"), None); - - assert_eq!(PortCollection::new("1-2-"), None); - } - - #[test] - fn test_port_collection_contains() { - let collection = PortCollection::new("1,2,25-30,55,101-117").unwrap(); - assert!(collection.contains(Some(1))); - assert!(collection.contains(Some(2))); - assert!(collection.contains(Some(25))); - assert!(collection.contains(Some(27))); - assert!(collection.contains(Some(30))); - assert!(collection.contains(Some(55))); - assert!(collection.contains(Some(101))); - assert!(collection.contains(Some(109))); - assert!(collection.contains(Some(117))); - assert!(!collection.contains(Some(4))); - assert!(!collection.contains(Some(24))); - assert!(!collection.contains(Some(31))); - assert!(!collection.contains(Some(100))); - assert!(!collection.contains(Some(118))); - assert!(!collection.contains(Some(8080))); - } -} diff --git a/src/networking/types/protocol.rs b/src/networking/types/protocol.rs index 31f20e62..00813df5 100644 --- a/src/networking/types/protocol.rs +++ b/src/networking/types/protocol.rs @@ -20,10 +20,6 @@ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { } } -impl Protocol { - pub const ALL: [Protocol; 4] = [Protocol::TCP, Protocol::UDP, Protocol::ICMP, Protocol::ARP]; -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/translations/translations.rs b/src/translations/translations.rs index ff833e63..25ee1ff7 100644 --- a/src/translations/translations.rs +++ b/src/translations/translations.rs @@ -163,33 +163,33 @@ pub fn addresses_translation(language: Language) -> &'static str { } } -pub fn ip_version_translation(language: Language) -> &'static str { - match language { - Language::EN => "IP version", - Language::IT => "Versione IP", - Language::FR => "Version IP", - Language::ES => "Versión IP", - Language::PL => "Wersja IP", - Language::DE => "IP Version", - Language::UK => "Версія IP", - Language::ZH => "目标IP协议版本", - Language::ZH_TW => "IP 版本", - Language::RO => "Versiune IP", - Language::KO => "IP 버전", - Language::TR => "IP versiyonu", - Language::RU => "Версия IP", - Language::PT => "Versão de IP", - Language::EL => "Έκδοση IP", - // Language::FA => "نسخهٔ IP", - Language::SV => "IP-version", - Language::FI => "IP-versio", - Language::JA => "IP バージョン", - Language::UZ => "IP versiyasi", - Language::VI => "Phiên bản IP", - Language::ID => "Versi IP", - Language::NL => "IP versie", - } -} +// pub fn ip_version_translation(language: Language) -> &'static str { +// match language { +// Language::EN => "IP version", +// Language::IT => "Versione IP", +// Language::FR => "Version IP", +// Language::ES => "Versión IP", +// Language::PL => "Wersja IP", +// Language::DE => "IP Version", +// Language::UK => "Версія IP", +// Language::ZH => "目标IP协议版本", +// Language::ZH_TW => "IP 版本", +// Language::RO => "Versiune IP", +// Language::KO => "IP 버전", +// Language::TR => "IP versiyonu", +// Language::RU => "Версия IP", +// Language::PT => "Versão de IP", +// Language::EL => "Έκδοση IP", +// // Language::FA => "نسخهٔ IP", +// Language::SV => "IP-version", +// Language::FI => "IP-versio", +// Language::JA => "IP バージョン", +// Language::UZ => "IP versiyasi", +// Language::VI => "Phiên bản IP", +// Language::ID => "Versi IP", +// Language::NL => "IP versie", +// } +// } // pub fn transport_protocol_translation(language: Language) -> &'static str { // match language { diff --git a/src/translations/translations_3.rs b/src/translations/translations_3.rs index 3fd89175..09d6a553 100644 --- a/src/translations/translations_3.rs +++ b/src/translations/translations_3.rs @@ -190,31 +190,31 @@ pub fn port_translation(language: Language) -> &'static str { } } -pub fn invalid_filters_translation(language: Language) -> &'static str { - match language { - Language::EN => "Invalid filters", - // Language::FA => "صافی نامعتبر", - Language::ES | Language::PT => "Filtros inválidos", - Language::IT => "Filtri non validi", - Language::FR => "Filtres invalides", - Language::DE => "Ungültige Filter", - Language::PL => "Nieprawidłowe filtry", - Language::RU => "Неверный формат фильтров", - Language::RO => "Filtre invalide", - Language::JA => "無効なフィルター", - Language::UZ => "Noto'g'ri filtrlar", - Language::SV => "Ogiltiga filter", - Language::VI => "Bộ lọc không khả dụng", - Language::ZH => "无效的过滤器", - Language::ZH_TW => "無效的篩選器", - Language::KO => "잘못된 필터", - Language::TR => "Geçersiz filtreler", - Language::UK => "Неправильний формат фільтрів", - Language::ID => "Filter salah", - Language::NL => "Ongeldige filters", - _ => "Invalid filters", - } -} +// pub fn invalid_filters_translation(language: Language) -> &'static str { +// match language { +// Language::EN => "Invalid filters", +// // Language::FA => "صافی نامعتبر", +// Language::ES | Language::PT => "Filtros inválidos", +// Language::IT => "Filtri non validi", +// Language::FR => "Filtres invalides", +// Language::DE => "Ungültige Filter", +// Language::PL => "Nieprawidłowe filtry", +// Language::RU => "Неверный формат фильтров", +// Language::RO => "Filtre invalide", +// Language::JA => "無効なフィルター", +// Language::UZ => "Noto'g'ri filtrlar", +// Language::SV => "Ogiltiga filter", +// Language::VI => "Bộ lọc không khả dụng", +// Language::ZH => "无效的过滤器", +// Language::ZH_TW => "無效的篩選器", +// Language::KO => "잘못된 필터", +// Language::TR => "Geçersiz filtreler", +// Language::UK => "Неправильний формат фільтрів", +// Language::ID => "Filter salah", +// Language::NL => "Ongeldige filters", +// _ => "Invalid filters", +// } +// } pub fn messages_translation(language: Language) -> &'static str { match language { diff --git a/src/utils/formatted_strings.rs b/src/utils/formatted_strings.rs index da4f9d05..8dcabc23 100644 --- a/src/utils/formatted_strings.rs +++ b/src/utils/formatted_strings.rs @@ -1,15 +1,8 @@ use std::cmp::min; use std::net::IpAddr; -use crate::Language; -use crate::networking::types::filters::Filters; -use crate::translations::translations::{ - address_translation, ip_version_translation, protocol_translation, -}; -use crate::translations::translations_3::{invalid_filters_translation, port_translation}; use crate::utils::types::timestamp::Timestamp; use chrono::{Local, TimeZone}; -use std::fmt::Write; /// Application version number (to be displayed in gui footer) pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -27,61 +20,6 @@ // } // } -pub fn get_invalid_filters_string(filters: &Filters, language: Language) -> String { - let mut ret_val = format!("{}:", invalid_filters_translation(language)); - if !filters.ip_version_valid() { - let _ = write!(ret_val, "\n • {}", ip_version_translation(language)); - } - if !filters.protocol_valid() { - let _ = write!(ret_val, "\n • {}", protocol_translation(language)); - } - if !filters.address_valid() { - let _ = write!(ret_val, "\n • {}", address_translation(language)); - } - if !filters.port_valid() { - let _ = write!(ret_val, "\n • {}", port_translation(language)); - } - ret_val -} - -/// Computes the string representing the active filters -pub fn get_active_filters_string(filters: &Filters, language: Language) -> String { - let mut filters_string = String::new(); - if filters.ip_version_active() { - let _ = writeln!( - filters_string, - "• {}: {}", - ip_version_translation(language), - filters.pretty_print_ip() - ); - } - if filters.protocol_active() { - let _ = writeln!( - filters_string, - "• {}: {}", - protocol_translation(language), - filters.pretty_print_protocol() - ); - } - if filters.address_active() { - let _ = writeln!( - filters_string, - "• {}: {}", - address_translation(language), - filters.address_str - ); - } - if filters.port_active() { - let _ = writeln!( - filters_string, - "• {}: {}", - port_translation(language), - filters.port_str - ); - } - filters_string -} - pub fn print_cli_welcome_message() { let ver = APP_VERSION; print!( From c276536fa9c073d5b6d770155996cfce36d86748 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 15 Aug 2025 22:42:31 +0200 Subject: [PATCH 42/74] removed informations about filtered out data --- src/chart/types/donut_chart.rs | 17 +- src/gui/pages/overview_page.rs | 157 +++++------------- src/gui/pages/thumbnail_page.rs | 3 +- src/gui/sniffer.rs | 6 +- src/gui/styles/rule.rs | 2 - src/networking/parse_packets.rs | 3 - src/networking/types/info_traffic.rs | 20 +-- src/translations/translations.rs | 236 +++++++++++++-------------- src/translations/translations_4.rs | 28 ++-- 9 files changed, 184 insertions(+), 288 deletions(-) diff --git a/src/chart/types/donut_chart.rs b/src/chart/types/donut_chart.rs index 13749306..f2bac3bd 100644 --- a/src/chart/types/donut_chart.rs +++ b/src/chart/types/donut_chart.rs @@ -12,7 +12,6 @@ pub struct DonutChart { data_repr: DataRepr, incoming: u128, outgoing: u128, - filtered_out: u128, dropped: u128, font: Font, thumbnail: bool, @@ -23,7 +22,6 @@ fn new( data_repr: DataRepr, incoming: u128, outgoing: u128, - filtered_out: u128, dropped: u128, font: Font, thumbnail: bool, @@ -32,7 +30,6 @@ fn new( data_repr, incoming, outgoing, - filtered_out, dropped, font, thumbnail, @@ -40,7 +37,7 @@ fn new( } fn total(&self) -> u128 { - self.incoming + self.outgoing + self.filtered_out + self.dropped + self.incoming + self.outgoing + self.dropped } fn title(&self) -> String { @@ -48,12 +45,11 @@ fn title(&self) -> String { self.data_repr.formatted_string(total) } - fn angles(&self) -> [(Radians, Radians); 4] { + fn angles(&self) -> [(Radians, Radians); 3] { #[allow(clippy::cast_precision_loss)] let mut values = [ self.incoming as f32, self.outgoing as f32, - self.filtered_out as f32, self.dropped as f32, ]; let total: f32 = values.iter().sum(); @@ -148,7 +144,6 @@ pub fn donut_chart( data_repr: DataRepr, incoming: u128, outgoing: u128, - filtered_out: u128, dropped: u128, font: Font, thumbnail: bool, @@ -159,13 +154,7 @@ pub fn donut_chart( Length::Fixed(110.0) }; iced::widget::canvas(DonutChart::new( - data_repr, - incoming, - outgoing, - filtered_out, - dropped, - font, - thumbnail, + data_repr, incoming, outgoing, dropped, font, thumbnail, )) .width(size) .height(size) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index d25020b4..083e2d71 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -27,15 +27,14 @@ use crate::report::types::sort_type::SortType; use crate::translations::translations::{ active_filters_translation, error_translation, incoming_translation, no_addresses_translation, - none_translation, outgoing_translation, some_observed_translation, traffic_rate_translation, - waiting_translation, + outgoing_translation, traffic_rate_translation, waiting_translation, }; use crate::translations::translations_2::{ data_representation_translation, dropped_translation, host_translation, only_top_30_items_translation, }; use crate::translations::translations_3::{service_translation, unsupported_link_type_translation}; -use crate::translations::translations_4::{excluded_translation, reading_from_pcap_translation}; +use crate::translations::translations_4::reading_from_pcap_translation; use crate::utils::types::icon::Icon; use crate::{ConfigSettings, Language, RunningPage, StyleType}; use iced::Length::{Fill, FillPortion}; @@ -68,58 +67,52 @@ pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { body = body_pcap_error(error, dots, language, font); } else { // NO pcap error detected - let observed = sniffer.info_traffic.all_packets; - let filtered = sniffer + let tot_packets = sniffer .info_traffic .tot_data_info .tot_data(DataRepr::Packets); - match (observed, filtered) { - (0, 0) => { - //no packets observed at all - body = body_no_packets(&sniffer.capture_source, font, language, dots); - } - (observed, 0) => { - //no packets have been filtered but some have been observed - body = body_no_observed(&sniffer.bpf_filter, observed, font, language, dots); - } - (_observed, _filtered) => { - //observed > filtered > 0 || observed = filtered > 0 - let tabs = get_pages_tabs( - RunningPage::Overview, - font, - font_headers, - language, - sniffer.unread_notifications, - ); - tab_and_body = tab_and_body.push(tabs); + if tot_packets == 0 { + // no packets observed at all + // TODO: add info about the capture filters (if any) in the method called below + body = body_no_packets(&sniffer.capture_source, font, language, dots); + } else { + // some packets are there! + let tabs = get_pages_tabs( + RunningPage::Overview, + font, + font_headers, + language, + sniffer.unread_notifications, + ); + tab_and_body = tab_and_body.push(tabs); - let container_chart = container_chart(sniffer, font); + let container_chart = container_chart(sniffer, font); - let container_info = col_info(sniffer); + let container_info = col_info(sniffer); - let container_report = row_report(sniffer); + let container_report = row_report(sniffer); - body = body - .width(Length::Fill) - .padding(10) - .spacing(10) - .align_x(Alignment::Center) - .push( - Row::new() - .height(280) - .spacing(10) - .push(container_info) - .push(container_chart), - ) - .push(container_report); - } + body = body + .width(Length::Fill) + .padding(10) + .spacing(10) + .align_x(Alignment::Center) + .push( + Row::new() + .height(280) + .spacing(10) + .push(container_info) + .push(container_chart), + ) + .push(container_report); } } Container::new(Column::new().push(tab_and_body.push(body))).height(Length::Fill) } +// TODO: add info about active filters if any fn body_no_packets<'a>( cs: &CaptureSource, font: Font, @@ -172,31 +165,6 @@ fn body_no_packets<'a>( .push(Space::with_height(FillPortion(2))) } -fn body_no_observed<'a>( - bpf: &'a str, - observed: u128, - font: Font, - language: Language, - dots: &str, -) -> Column<'a, Message, StyleType> { - let tot_packets_text = some_observed_translation(language, observed) - .align_x(Alignment::Center) - .font(font); - - Column::new() - .width(Length::Fill) - .padding(10) - .spacing(10) - .align_x(Alignment::Center) - .push(vertical_space()) - .push(Icon::Funnel.to_text().size(60)) - .push(get_active_filters_col(bpf, language, font)) - .push(Rule::horizontal(20)) - .push(tot_packets_text) - .push(Text::new(dots.to_owned()).font(font).size(50)) - .push(Space::with_height(FillPortion(2))) -} - fn body_pcap_error<'a>( pcap_error: &'a str, dots: &'a str, @@ -589,23 +557,8 @@ fn donut_row( sniffer: &Sniffer, ) -> Container<'_, Message, StyleType> { let data_repr = sniffer.traffic_chart.data_repr; - let bpf = &sniffer.bpf_filter; - let (in_data, out_data, filtered_out, dropped) = - sniffer.info_traffic.get_thumbnail_data(data_repr); - - let legend_entry_filtered = if bpf.trim().is_empty() { - None - } else { - Some(donut_legend_entry( - filtered_out, - data_repr, - RuleType::FilteredOut, - bpf, - font, - language, - )) - }; + let (in_data, out_data, dropped) = sniffer.info_traffic.get_thumbnail_data(data_repr); let legend_col = Column::new() .spacing(5) @@ -613,7 +566,6 @@ fn donut_row( in_data, data_repr, RuleType::Incoming, - bpf, font, language, )) @@ -621,16 +573,13 @@ fn donut_row( out_data, data_repr, RuleType::Outgoing, - bpf, font, language, )) - .push_maybe(legend_entry_filtered) .push(donut_legend_entry( dropped, data_repr, RuleType::Dropped, - bpf, font, language, )); @@ -642,7 +591,6 @@ fn donut_row( data_repr, in_data, out_data, - filtered_out, dropped, font, sniffer.thumbnail, @@ -656,30 +604,22 @@ fn donut_row( .align_y(Vertical::Center) } -fn donut_legend_entry( +fn donut_legend_entry<'a>( value: u128, data_repr: DataRepr, rule_type: RuleType, - bpf: &str, font: Font, language: Language, -) -> Row<'_, Message, StyleType> { +) -> Row<'a, Message, StyleType> { let value_text = data_repr.formatted_string(value); let label = match rule_type { RuleType::Incoming => incoming_translation(language), RuleType::Outgoing => outgoing_translation(language), - RuleType::FilteredOut => excluded_translation(language), RuleType::Dropped => dropped_translation(language), _ => "", }; - let tooltip = if matches!(rule_type, RuleType::FilteredOut) { - Some(get_active_filters_tooltip(bpf, language, font)) - } else { - None - }; - Row::new() .spacing(10) .align_y(Alignment::Center) @@ -689,7 +629,6 @@ fn donut_legend_entry( .push(Rule::horizontal(1).class(rule_type)), ) .push(Text::new(format!("{label}: {value_text}")).font(font)) - .push_maybe(tooltip) } const MIN_BARS_LENGTH: f32 = 4.0; @@ -792,25 +731,6 @@ fn get_star_button<'a>(is_favorite: bool, host: Host) -> Button<'a, Message, Sty .on_press(Message::AddOrRemoveFavorite(host, !is_favorite)) } -fn get_active_filters_col( - bpf: &str, - language: Language, - font: Font, -) -> Column<'_, Message, StyleType> { - let mut ret_val = Column::new().push( - Text::new(active_filters_translation(language)) - .font(font) - .class(TextType::Subtitle), - ); - - if bpf.trim().is_empty() { - ret_val = ret_val.push(Text::new(format!(" {}", none_translation(language))).font(font)); - } else { - ret_val = ret_val.push(Row::new().push(Text::new(bpf).font(font))); - } - ret_val -} - fn get_active_filters_tooltip( bpf: &str, language: Language, @@ -826,7 +746,8 @@ fn get_active_filters_tooltip( Tooltip::new( Container::new( - Text::new("i") + Icon::Funnel + .to_text() .font(font) .size(15) .line_height(LineHeight::Relative(1.0)), diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs index 68c4aa43..8935bf0e 100644 --- a/src/gui/pages/thumbnail_page.rs +++ b/src/gui/pages/thumbnail_page.rs @@ -46,7 +46,7 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let info_traffic = &sniffer.info_traffic; let data_repr = sniffer.traffic_chart.data_repr; - let (in_data, out_data, filtered_out, dropped) = info_traffic.get_thumbnail_data(data_repr); + let (in_data, out_data, dropped) = info_traffic.get_thumbnail_data(data_repr); let charts = Row::new() .padding(5) @@ -56,7 +56,6 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { data_repr, in_data, out_data, - filtered_out, dropped, font, sniffer.thumbnail, diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 74c70293..42652e21 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -967,7 +967,8 @@ fn shortcut_esc(&mut self) -> Task { // also called when the backspace shortcut is pressed fn reset_button_pressed(&mut self) -> Task { if self.running_page.ne(&RunningPage::Init) { - return if self.info_traffic.all_packets == 0 && self.settings_page.is_none() { + let tot_packets = self.info_traffic.tot_data_info.tot_data(DataRepr::Packets); + return if tot_packets == 0 && self.settings_page.is_none() { Task::done(Message::Reset) } else { Task::done(Message::ShowModal(MyModal::Reset)) @@ -977,7 +978,8 @@ fn reset_button_pressed(&mut self) -> Task { } fn quit_wrapper(&mut self) -> Task { - if self.running_page.eq(&RunningPage::Init) || self.info_traffic.all_packets == 0 { + let tot_packets = self.info_traffic.tot_data_info.tot_data(DataRepr::Packets); + if self.running_page.eq(&RunningPage::Init) || tot_packets == 0 { Task::done(Message::Quit) } else if self.thumbnail { // TODO: uncomment once issue #653 is fixed diff --git a/src/gui/styles/rule.rs b/src/gui/styles/rule.rs index d44f7cea..13eb0ba8 100644 --- a/src/gui/styles/rule.rs +++ b/src/gui/styles/rule.rs @@ -13,7 +13,6 @@ pub enum RuleType { PaletteColor(Color, u16), Incoming, Outgoing, - FilteredOut, Dropped, } @@ -27,7 +26,6 @@ fn appearance(&self, style: &StyleType) -> Style { RuleType::Outgoing => colors.outgoing, RuleType::PaletteColor(color, _) => *color, RuleType::Dropped => ext.red_alert_color, - RuleType::FilteredOut => ext.buttons_color, RuleType::Standard => Color { a: ext.alpha_round_borders, ..ext.buttons_color diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index 5201f75c..17b85c75 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -268,9 +268,6 @@ pub fn parse_packets( DataInfo::new_with_first_packet(exchanged_bytes, traffic_direction) }); - //increment number of sniffed packets and bytes - info_traffic_msg.all_packets += 1; - info_traffic_msg.all_bytes += exchanged_bytes; // update dropped packets number if let Ok(stats) = cap.stats() { info_traffic_msg.dropped_packets = stats.dropped; diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index 7fcde9e7..14e021a5 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -13,10 +13,6 @@ pub struct InfoTraffic { /// Total amount of exchanged data pub tot_data_info: DataInfo, - /// Total packets including those not filtered - pub all_packets: u128, - /// Total bytes including those not filtered - pub all_bytes: u128, /// Number of dropped packets pub dropped_packets: u32, /// Timestamp of the latest parsed packet @@ -33,8 +29,6 @@ impl InfoTraffic { pub fn refresh(&mut self, msg: &mut InfoTraffic) { self.tot_data_info.refresh(msg.tot_data_info); - self.all_packets += msg.all_packets; - self.all_bytes += msg.all_bytes; self.dropped_packets = msg.dropped_packets; // it can happen they're equal due to dis-alignments in the PCAP timestamp @@ -65,24 +59,20 @@ pub fn refresh(&mut self, msg: &mut InfoTraffic) { } } - pub fn get_thumbnail_data(&self, data_repr: DataRepr) -> (u128, u128, u128, u128) { + pub fn get_thumbnail_data(&self, data_repr: DataRepr) -> (u128, u128, u128) { let incoming = self.tot_data_info.incoming_data(data_repr); let outgoing = self.tot_data_info.outgoing_data(data_repr); - let all = match data_repr { - DataRepr::Packets => self.all_packets, - DataRepr::Bytes => self.all_bytes, - DataRepr::Bits => self.all_bytes * 8, - }; - let filtered = all - incoming - outgoing; + let all = incoming + outgoing; + let all_packets = self.tot_data_info.tot_data(DataRepr::Packets); let dropped = match data_repr { DataRepr::Packets => u128::from(self.dropped_packets), DataRepr::Bytes | DataRepr::Bits => { // assume that the dropped packets have the same size as the average packet - u128::from(self.dropped_packets) * all / self.all_packets + u128::from(self.dropped_packets) * all / all_packets } }; - (incoming, outgoing, filtered, dropped) + (incoming, outgoing, dropped) } pub fn take_but_leave_something(&mut self) -> Self { diff --git a/src/translations/translations.rs b/src/translations/translations.rs index 25ee1ff7..2c60159f 100644 --- a/src/translations/translations.rs +++ b/src/translations/translations.rs @@ -751,124 +751,124 @@ pub fn waiting_translation<'a>(language: Language, adapter: &str) -> Text<'a, St }) } -#[allow(clippy::too_many_lines)] -pub fn some_observed_translation<'a>(language: Language, observed: u128) -> Text<'a, StyleType> { - Text::new(match language { - Language::EN => format!( - "Total intercepted packets: {observed}\n\n\ - Filtered packets: 0\n\n\ - Some packets have been intercepted, but still none has been selected according to the filters you specified..." - ), - Language::IT => format!( - "Totale pacchetti intercettati: {observed}\n\n\ - Pacchetti filtrati: 0\n\n\ - Alcuni pacchetti sono stati intercettati, ma ancora nessuno è stato selezionato secondo i filtri specificati..." - ), - Language::FR => format!( - "Total des paquets interceptés: {observed}\n\n\ - Paquets filtrés: 0\n\n\ - Certains paquets ont été interceptés, mais aucun ne satisfait les critères des filtres sélectionnés..." - ), - Language::ES => format!( - "Total de paquetes interceptados: {observed}\n\n\ - Paquetes filtrados: 0\n\n\ - Se interceptaron algunos paquetes, pero ninguno de ellos cumplía los criterios de los filtros seleccionados..." - ), - Language::PL => format!( - "Suma przechwyconych pakietów: {observed}\n\n\ - Przefiltrowane pakiety: 0\n\n\ - Niektóre pakiety zostały przechwycone, ale żaden nie został wybrany zgodnie z wskazanymi filtrami..." - ), - Language::DE => format!( - "Anzahl der empfangenen Pakete: {observed}\n\n\ - Gefilterte Pakete: 0\n\n\ - Ein Paar Pakete wurden empfangen, aber es entsprechen noch keine den gewählten Filtern..." - ), - Language::UK => format!( - "Сума перехоплених пакетів: {observed}\n\n\ - Відфільтровані пакети: 0\n\n\ - Деякі пакети були перехоплені, але жоден з них не був вибраний відповідно до вказаних фільтрів..." - ), - Language::ZH => format!( - "监测到的数据包总数: {observed}\n\n\ - 目标数据包总数: 0\n\n\ - 当前已监测到一些数据包, 但其中并未包含您的目标数据包......" - ), - Language::ZH_TW => format!( - "攔截到的封包總數: {observed}\n\n\ - 已篩選封包: 0\n\n\ - 已攔截到部分封包,但尚未有任何封包符合您指定的篩選條件..." - ), - Language::RO => format!( - "Total pachete interceptate: {observed}\n\n\ - Pachete filtrate: 0\n\n\ - Unele pachete au fost interceptate, dar încă niciunul nu a fost selectat conform filtrelor pe care le-ați specificat..." - ), - Language::KO => format!( - "감지한 총 패킷: {observed}\n\n\ - 필터링된 패킷: 0\n\n\ - 일부 패킷이 감지되었지만, 지정한 필터에 따라 선택되지 않았습니다..." - ), - Language::TR => format!( - "Toplam yakalanan paketler: {observed}\n\n\ - Filterelenen paketler: 0\n\n\ - Bazı paketler yakalandı, fakat belirttiğiniz filtrelere göre hiç biri seçilmedi..." - ), - Language::RU => format!( - "Всего пакетов перехвачено: {observed}\n\n\ - Фильтровано пакетов: 0\n\n\ - Сетевые пакеты были перехвачены, но ни один из них не соответствует заданным фильтрам..." - ), - Language::PT => format!( - "Total de pacotes interceptados: {observed}\n\n\ - Pacotes filtrados: 0\n\n\ - Alguns pacotes foram interceptados, mas nenhum deles foi selecionado de acordo com os filtros especificados..." - ), - Language::EL => format!( - "Συνολικά αναχαιτισμένα πακέτα: {observed}\n\n\ - Φιλτραρισμένα πακέτα: 0\n\n\ - Κάποια από τα πακέτα έχουν αναχαιτιστεί, αλλά κανένα ακόμη δεν έχει επιλεγεί σύμφωνα με τα φίλτρα που επέλεξες..." - ), - // Language::FA => format!("مجموع بسته های رهگیری شده: {observed}\n\n\ - // بسته های صاف شده: 0\n\n\ - // شماری از بسته ها رهگیری شده اند، ولی هنوز هیچ کدام بر اساس صافی تعیین شده شما انتخاب نشده اند..."), - Language::SV => format!( - "Antal fångade paket: {observed}\n\n\ - Filtrerade paket: 0\n\n\ - Några paket har fångats, men än har inget valts enligt de angivna filtren ..." - ), - Language::FI => format!( - "Siepattuja paketteja yhteensä: {observed}\n\n\ - Suodatettuja paketteja: 0\n\n\ - Joitakin paketteja on siepattu, mutta yhtäkään ei ole valittu määrittämiesi suodattimien mukaan..." - ), - Language::JA => format!( - "取得したパケット数: {observed}\n\n\ - フィルター後のパケット数: 0\n\n\ - パケットは取得できていますが、設定されたフィルタリングにより表示されません..." - ), - Language::UZ => format!( - "Jami ushlangan paketlar: {observed}\n\n\ - Filtrlangan paketlar: 0\n\n\ - Tarmoq paketlari ushlandi, lekin ularning hech biri belgilangan filtrlarga mos kelmadi..." - ), - Language::VI => format!( - "Tổng số gói tin bị chặn: {observed}\n\n\ - Các gói tin đã lọc: 0\n\n\ - Một số gói đã bị chặn, nhưng vẫn chưa có gói tin nào được bắt theo bộ lọc bạn đã chọn..." - ), - Language::ID => format!( - "Total paket yang dilacak: {observed}\n\n\ - Paket yg difilter: 0\n\n\ - Beberapa paket dilacak, tetapi tidak ada yg terlihat berdasarkan filter yang kamu pilih..." - ), - Language::NL => format!( - "Totaal aantal onderschepte pakketten: {observed}\n\n\ - Gefilterde pakketten: 0\n\n\ - Er zijn enkele pakketten onderschept, maar nog geen enkele is geselecteerd volgens de filters die je hebt opgegeven..." - ), - }) -} +// #[allow(clippy::too_many_lines)] +// pub fn some_observed_translation<'a>(language: Language, observed: u128) -> Text<'a, StyleType> { +// Text::new(match language { +// Language::EN => format!( +// "Total intercepted packets: {observed}\n\n\ +// Filtered packets: 0\n\n\ +// Some packets have been intercepted, but still none has been selected according to the filters you specified..." +// ), +// Language::IT => format!( +// "Totale pacchetti intercettati: {observed}\n\n\ +// Pacchetti filtrati: 0\n\n\ +// Alcuni pacchetti sono stati intercettati, ma ancora nessuno è stato selezionato secondo i filtri specificati..." +// ), +// Language::FR => format!( +// "Total des paquets interceptés: {observed}\n\n\ +// Paquets filtrés: 0\n\n\ +// Certains paquets ont été interceptés, mais aucun ne satisfait les critères des filtres sélectionnés..." +// ), +// Language::ES => format!( +// "Total de paquetes interceptados: {observed}\n\n\ +// Paquetes filtrados: 0\n\n\ +// Se interceptaron algunos paquetes, pero ninguno de ellos cumplía los criterios de los filtros seleccionados..." +// ), +// Language::PL => format!( +// "Suma przechwyconych pakietów: {observed}\n\n\ +// Przefiltrowane pakiety: 0\n\n\ +// Niektóre pakiety zostały przechwycone, ale żaden nie został wybrany zgodnie z wskazanymi filtrami..." +// ), +// Language::DE => format!( +// "Anzahl der empfangenen Pakete: {observed}\n\n\ +// Gefilterte Pakete: 0\n\n\ +// Ein Paar Pakete wurden empfangen, aber es entsprechen noch keine den gewählten Filtern..." +// ), +// Language::UK => format!( +// "Сума перехоплених пакетів: {observed}\n\n\ +// Відфільтровані пакети: 0\n\n\ +// Деякі пакети були перехоплені, але жоден з них не був вибраний відповідно до вказаних фільтрів..." +// ), +// Language::ZH => format!( +// "监测到的数据包总数: {observed}\n\n\ +// 目标数据包总数: 0\n\n\ +// 当前已监测到一些数据包, 但其中并未包含您的目标数据包......" +// ), +// Language::ZH_TW => format!( +// "攔截到的封包總數: {observed}\n\n\ +// 已篩選封包: 0\n\n\ +// 已攔截到部分封包,但尚未有任何封包符合您指定的篩選條件..." +// ), +// Language::RO => format!( +// "Total pachete interceptate: {observed}\n\n\ +// Pachete filtrate: 0\n\n\ +// Unele pachete au fost interceptate, dar încă niciunul nu a fost selectat conform filtrelor pe care le-ați specificat..." +// ), +// Language::KO => format!( +// "감지한 총 패킷: {observed}\n\n\ +// 필터링된 패킷: 0\n\n\ +// 일부 패킷이 감지되었지만, 지정한 필터에 따라 선택되지 않았습니다..." +// ), +// Language::TR => format!( +// "Toplam yakalanan paketler: {observed}\n\n\ +// Filterelenen paketler: 0\n\n\ +// Bazı paketler yakalandı, fakat belirttiğiniz filtrelere göre hiç biri seçilmedi..." +// ), +// Language::RU => format!( +// "Всего пакетов перехвачено: {observed}\n\n\ +// Фильтровано пакетов: 0\n\n\ +// Сетевые пакеты были перехвачены, но ни один из них не соответствует заданным фильтрам..." +// ), +// Language::PT => format!( +// "Total de pacotes interceptados: {observed}\n\n\ +// Pacotes filtrados: 0\n\n\ +// Alguns pacotes foram interceptados, mas nenhum deles foi selecionado de acordo com os filtros especificados..." +// ), +// Language::EL => format!( +// "Συνολικά αναχαιτισμένα πακέτα: {observed}\n\n\ +// Φιλτραρισμένα πακέτα: 0\n\n\ +// Κάποια από τα πακέτα έχουν αναχαιτιστεί, αλλά κανένα ακόμη δεν έχει επιλεγεί σύμφωνα με τα φίλτρα που επέλεξες..." +// ), +// // Language::FA => format!("مجموع بسته های رهگیری شده: {observed}\n\n\ +// // بسته های صاف شده: 0\n\n\ +// // شماری از بسته ها رهگیری شده اند، ولی هنوز هیچ کدام بر اساس صافی تعیین شده شما انتخاب نشده اند..."), +// Language::SV => format!( +// "Antal fångade paket: {observed}\n\n\ +// Filtrerade paket: 0\n\n\ +// Några paket har fångats, men än har inget valts enligt de angivna filtren ..." +// ), +// Language::FI => format!( +// "Siepattuja paketteja yhteensä: {observed}\n\n\ +// Suodatettuja paketteja: 0\n\n\ +// Joitakin paketteja on siepattu, mutta yhtäkään ei ole valittu määrittämiesi suodattimien mukaan..." +// ), +// Language::JA => format!( +// "取得したパケット数: {observed}\n\n\ +// フィルター後のパケット数: 0\n\n\ +// パケットは取得できていますが、設定されたフィルタリングにより表示されません..." +// ), +// Language::UZ => format!( +// "Jami ushlangan paketlar: {observed}\n\n\ +// Filtrlangan paketlar: 0\n\n\ +// Tarmoq paketlari ushlandi, lekin ularning hech biri belgilangan filtrlarga mos kelmadi..." +// ), +// Language::VI => format!( +// "Tổng số gói tin bị chặn: {observed}\n\n\ +// Các gói tin đã lọc: 0\n\n\ +// Một số gói đã bị chặn, nhưng vẫn chưa có gói tin nào được bắt theo bộ lọc bạn đã chọn..." +// ), +// Language::ID => format!( +// "Total paket yang dilacak: {observed}\n\n\ +// Paket yg difilter: 0\n\n\ +// Beberapa paket dilacak, tetapi tidak ada yg terlihat berdasarkan filter yang kamu pilih..." +// ), +// Language::NL => format!( +// "Totaal aantal onderschepte pakketten: {observed}\n\n\ +// Gefilterde pakketten: 0\n\n\ +// Er zijn enkele pakketten onderschept, maar nog geen enkele is geselecteerd volgens de filters die je hebt opgegeven..." +// ), +// }) +// } // pub fn filtered_packets_translation(language: Language) -> &'static str { // match language { diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index 9028d663..d3b750a1 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -38,20 +38,20 @@ pub fn share_feedback_translation(language: Language) -> &'static str { } // refers to bytes or packets excluded because of the filters -pub fn excluded_translation(language: Language) -> &'static str { - match language { - Language::EN => "Excluded", - Language::IT => "Esclusi", - Language::JA => "除外", - Language::ZH => "已被过滤", - Language::UZ => "Chiqarib tashlangan", - Language::ZH_TW => "已排除", - Language::FR => "Exclus", - Language::NL => "Uitgesloten", - Language::DE => "Herausgefiltert", - _ => "Excluded", - } -} +// pub fn excluded_translation(language: Language) -> &'static str { +// match language { +// Language::EN => "Excluded", +// Language::IT => "Esclusi", +// Language::JA => "除外", +// Language::ZH => "已被过滤", +// Language::UZ => "Chiqarib tashlangan", +// Language::ZH_TW => "已排除", +// Language::FR => "Exclus", +// Language::NL => "Uitgesloten", +// Language::DE => "Herausgefiltert", +// _ => "Excluded", +// } +// } pub fn import_capture_translation(language: Language) -> &'static str { match language { From d58c27f36f840c4aefc6682e3a0f2a25b99097f3 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 15 Aug 2025 23:57:12 +0200 Subject: [PATCH 43/74] add checkbox for setting capture filters --- src/gui/pages/initial_page.rs | 89 +++++++++++++++---------- src/gui/sniffer.rs | 12 ++-- src/gui/types/filters.rs | 84 +++++++++++++++++++++++ src/gui/types/message.rs | 4 +- src/gui/types/mod.rs | 1 + src/networking/types/capture_context.rs | 11 +-- src/translations/mod.rs | 1 + src/translations/translations_5.rs | 12 ++++ 8 files changed, 171 insertions(+), 43 deletions(-) create mode 100644 src/gui/types/filters.rs create mode 100644 src/translations/translations_5.rs diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index ea6b903e..c4cf72fa 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -4,15 +4,6 @@ use std::fmt::Write; -use iced::Length::FillPortion; -use iced::widget::scrollable::Direction; -use iced::widget::tooltip::Position; -use iced::widget::{ - Button, Checkbox, Column, Container, Row, Rule, Scrollable, Space, Text, TextInput, Tooltip, - button, center, -}; -use iced::{Alignment, Font, Length, Padding, alignment}; - use crate::gui::components::button::button_open_file; use crate::gui::sniffer::Sniffer; use crate::gui::styles::button::ButtonType; @@ -20,9 +11,9 @@ use crate::gui::styles::scrollbar::ScrollbarType; use crate::gui::styles::style_constants::{FONT_SIZE_SUBTITLE, FONT_SIZE_TITLE}; use crate::gui::styles::text::TextType; -use crate::gui::styles::text_input::TextInputType; use crate::gui::styles::types::gradient_type::GradientType; use crate::gui::types::export_pcap::ExportPcap; +use crate::gui::types::filters::Filters; use crate::gui::types::message::Message; use crate::networking::types::capture_context::CaptureSource; use crate::translations::translations::{ @@ -33,10 +24,19 @@ directory_translation, export_capture_translation, file_name_translation, }; use crate::translations::translations_4::import_capture_translation; +use crate::translations::translations_5::filter_traffic_translation; use crate::utils::formatted_strings::get_path_termination_string; use crate::utils::types::file_info::FileInfo; use crate::utils::types::icon::Icon; use crate::{ConfigSettings, Language, StyleType}; +use iced::Length::FillPortion; +use iced::widget::scrollable::Direction; +use iced::widget::tooltip::Position; +use iced::widget::{ + Button, Checkbox, Column, Container, Row, Rule, Scrollable, Space, Text, TextInput, Tooltip, + button, center, +}; +use iced::{Alignment, Font, Length, Padding, alignment}; /// Computes the body of gui initial page pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { @@ -57,8 +57,6 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { ); let col_capture_source = Column::new().push(col_adapter).push(col_import_pcap); - let col_bpf_filter = col_bpf_input(&sniffer.bpf_filter, font); - let filters_pane = Column::new() .width(FillPortion(6)) .padding(10) @@ -69,7 +67,7 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { .class(TextType::Title) .size(FONT_SIZE_TITLE), ) - .push(col_bpf_filter) + .push(get_filters_group(&sniffer.filters, font, language)) .push(Rule::horizontal(40)) .push(get_export_pcap_group( &sniffer.capture_source, @@ -95,27 +93,6 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { Container::new(body).height(Length::Fill) } -fn col_bpf_input(value: &str, font: Font) -> Column<'_, Message, StyleType> { - let input_row = Row::new().padding(Padding::ZERO.left(5)).push( - TextInput::new("Berkeley Packet Filter (BPF)", value) - .padding([3, 5]) - .on_input(Message::BpfFilter) - .font(font) - .class(TextInputType::Standard), - ); - - Column::new() - .width(Length::Fill) - .spacing(7) - .push( - Text::new("Berkeley Packet Filter (BPF)") - .font(font) - .class(TextType::Subtitle) - .size(FONT_SIZE_SUBTITLE), - ) - .push(input_row) -} - fn button_start<'a>( font: Font, language: Language, @@ -298,6 +275,50 @@ fn get_col_import_pcap<'a>( .push(button) } +fn get_filters_group<'a>( + filters: &Filters, + font: Font, + language: Language, +) -> Container<'a, Message, StyleType> { + let expanded = filters.expanded(); + let bpf = filters.bpf(); + + let caption = filter_traffic_translation(language); + let checkbox = Checkbox::new(caption, expanded) + .on_toggle(move |_| Message::ToggleFilters) + .size(18) + .font(font); + + let mut ret_val = Column::new().spacing(10).push(checkbox); + + if expanded { + let input = TextInput::new("", bpf) + .on_input(Message::BpfFilter) + .padding([2, 5]) + .font(font); + let inner_col = Column::new() + .spacing(10) + .padding(Padding::ZERO.left(45)) + .push( + Row::new() + .align_y(Alignment::Center) + .spacing(5) + .push(Text::new("BPF:").font(font)) + .push(input), + ); + ret_val = ret_val.push(inner_col); + } + + Container::new( + Container::new(ret_val) + .padding(10) + .width(Length::Fill) + .class(ContainerType::BorderedRound), + ) + .height(Length::Fill) + .align_y(Alignment::Start) +} + fn get_export_pcap_group<'a>( cs: &CaptureSource, export_pcap: &ExportPcap, diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 42652e21..e71ad7bb 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -38,6 +38,7 @@ use crate::gui::styles::types::custom_palette::{CustomPalette, ExtraStyles}; use crate::gui::styles::types::palette::Palette; use crate::gui::types::export_pcap::ExportPcap; +use crate::gui::types::filters::Filters; use crate::gui::types::message::Message; use crate::gui::types::timing_events::TimingEvents; use crate::mmdb::asn::ASN_MMDB; @@ -92,7 +93,7 @@ pub struct Sniffer { /// List of network devices pub my_devices: Vec, /// BPF filter program to be applied to the capture - pub bpf_filter: String, + pub filters: Filters, /// Signals if a pcap error occurred pub pcap_error: Option, /// Messages status @@ -155,7 +156,7 @@ pub fn new(configs: Configs) -> Self { newer_release_available: None, capture_source: CaptureSource::Device(device), my_devices: Vec::new(), - bpf_filter: String::new(), + filters: Filters::default(), pcap_error: None, dots_pulse: (".".to_string(), 0), traffic_chart: TrafficChart::new(style, language), @@ -276,8 +277,11 @@ pub fn update(&mut self, message: Message) -> Task { } } Message::DeviceSelection(name) => self.set_device(&name), + Message::ToggleFilters => { + self.filters.toggle(); + } Message::BpfFilter(value) => { - self.bpf_filter = value; + self.filters.set_bpf(value); } Message::DataReprSelection(unit) => self.traffic_chart.change_kind(unit), Message::ReportSortSelection(sort) => { @@ -716,7 +720,7 @@ fn start(&mut self) -> Task { } let pcap_path = self.export_pcap.full_path(); let capture_context = - CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.bpf_filter); + CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.filters); self.pcap_error = capture_context.error().map(ToString::to_string); self.running_page = RunningPage::Overview; diff --git a/src/gui/types/filters.rs b/src/gui/types/filters.rs new file mode 100644 index 00000000..f5076430 --- /dev/null +++ b/src/gui/types/filters.rs @@ -0,0 +1,84 @@ +#[derive(Default)] +pub struct Filters { + expanded: bool, + bpf: String, +} + +impl Filters { + pub fn toggle(&mut self) { + self.expanded = !self.expanded; + } + + pub fn set_bpf(&mut self, bpf: String) { + self.bpf = bpf; + } + + pub fn expanded(&self) -> bool { + self.expanded + } + + pub fn bpf(&self) -> &str { + &self.bpf + } + + pub fn is_some_filter_active(&self) -> bool { + self.expanded && !self.bpf.trim().is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default() { + let filters = Filters::default(); + assert_eq!(filters.expanded(), false); + assert_eq!(filters.bpf(), ""); + } + + #[test] + fn test_toggle() { + let mut filters = Filters::default(); + assert_eq!(filters.expanded(), false); + + filters.toggle(); + assert_eq!(filters.expanded(), true); + + filters.toggle(); + assert_eq!(filters.expanded(), false); + } + + #[test] + fn test_set_bpf() { + let mut filters = Filters::default(); + assert_eq!(filters.bpf(), ""); + + filters.set_bpf("tcp port 80".to_string()); + assert_eq!(filters.bpf(), "tcp port 80"); + + filters.set_bpf(" udp port 53 ".to_string()); + assert_eq!(filters.bpf(), " udp port 53 "); + } + + #[test] + fn test_is_some_filter_active() { + let mut filters = Filters::default(); + assert_eq!(filters.is_some_filter_active(), false); + + filters.toggle(); + assert_eq!(filters.is_some_filter_active(), false); + + filters.set_bpf("tcp port 80".to_string()); + assert_eq!(filters.is_some_filter_active(), true); + + filters.toggle(); + assert_eq!(filters.is_some_filter_active(), false); + + filters.toggle(); + assert_eq!(filters.is_some_filter_active(), true); + + filters.set_bpf(" \t \n ".to_string()); + assert_eq!(filters.is_some_filter_active(), false); + } +} diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index 491e7b61..8b683414 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -24,7 +24,9 @@ pub enum Message { TickRun(usize, InfoTraffic, Vec, bool), /// Select network device DeviceSelection(String), - /// Changed BPF filter + /// Toggle BPF filter checkbox + ToggleFilters, + /// Change BPF filter string BpfFilter(String), /// Select data representation to use DataReprSelection(DataRepr), diff --git a/src/gui/types/mod.rs b/src/gui/types/mod.rs index 9bc7a238..50f45629 100644 --- a/src/gui/types/mod.rs +++ b/src/gui/types/mod.rs @@ -1,3 +1,4 @@ pub mod export_pcap; +pub mod filters; pub mod message; pub mod timing_events; diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index ae30cb31..93fc812b 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -1,10 +1,10 @@ -use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat}; - +use crate::gui::types::filters::Filters; use crate::networking::types::my_device::MyDevice; use crate::networking::types::my_link_type::MyLinkType; use crate::translations::translations::network_adapter_translation; use crate::translations::translations_3::file_name_translation; use crate::translations::types::language::Language; +use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat}; pub enum CaptureContext { Live(Live), @@ -14,13 +14,16 @@ pub enum CaptureContext { } impl CaptureContext { - pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>, bpf: &str) -> Self { + pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>, filters: &Filters) -> Self { let mut cap_type = match CaptureType::from_source(source, pcap_out_path) { Ok(c) => c, Err(e) => return Self::Error(e.to_string()), }; - if let Err(e) = cap_type.set_bpf(bpf) { + // only apply BPF filter if it is active, and return an error if it fails to apply + if filters.is_some_filter_active() + && let Err(e) = cap_type.set_bpf(filters.bpf()) + { return Self::Error(e.to_string()); } diff --git a/src/translations/mod.rs b/src/translations/mod.rs index b55b4908..408e867e 100644 --- a/src/translations/mod.rs +++ b/src/translations/mod.rs @@ -3,4 +3,5 @@ pub mod translations_2; pub mod translations_3; pub mod translations_4; +pub mod translations_5; pub mod types; diff --git a/src/translations/translations_5.rs b/src/translations/translations_5.rs new file mode 100644 index 00000000..e2a04221 --- /dev/null +++ b/src/translations/translations_5.rs @@ -0,0 +1,12 @@ +#![allow(clippy::match_same_arms)] + +use crate::translations::types::language::Language; + +pub fn filter_traffic_translation(language: Language) -> String { + match language { + Language::EN => "Filter traffic", + Language::IT => "Filtra il traffico", + _ => "Filter traffic", + } + .to_string() +} From f5d689ad7bd1928533698065c7882c5010b5b82e Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 16 Aug 2025 12:15:45 +0200 Subject: [PATCH 44/74] restructuring initial page (WIP) --- src/gui/pages/initial_page.rs | 192 +++++++++---------- src/gui/pages/settings_notifications_page.rs | 54 +++--- src/translations/translations_5.rs | 9 + src/utils/types/icon.rs | 4 +- 4 files changed, 131 insertions(+), 128 deletions(-) diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index c4cf72fa..87bfff4f 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -17,14 +17,13 @@ use crate::gui::types::message::Message; use crate::networking::types::capture_context::CaptureSource; use crate::translations::translations::{ - address_translation, addresses_translation, choose_adapters_translation, - select_filters_translation, start_translation, + address_translation, addresses_translation, choose_adapters_translation, start_translation, }; use crate::translations::translations_3::{ directory_translation, export_capture_translation, file_name_translation, }; use crate::translations::translations_4::import_capture_translation; -use crate::translations::translations_5::filter_traffic_translation; +use crate::translations::translations_5::{filter_traffic_translation, traffic_source_translation}; use crate::utils::formatted_strings::get_path_termination_string; use crate::utils::types::file_info::FileInfo; use crate::utils::types::icon::Icon; @@ -33,8 +32,8 @@ use iced::widget::scrollable::Direction; use iced::widget::tooltip::Position; use iced::widget::{ - Button, Checkbox, Column, Container, Row, Rule, Scrollable, Space, Text, TextInput, Tooltip, - button, center, + Button, Checkbox, Column, Container, PickList, Row, Rule, Scrollable, Space, Text, TextInput, + Tooltip, button, center, vertical_space, }; use iced::{Alignment, Font, Length, Padding, alignment}; @@ -47,82 +46,107 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { .. } = sniffer.configs.settings; let font = style.get_extension().font; + let font_headers = style.get_extension().font_headers; - let col_adapter = get_col_adapter(sniffer, font); - let col_import_pcap = get_col_import_pcap( - language, - font, - &sniffer.capture_source, - &sniffer.import_pcap_path, - ); - let col_capture_source = Column::new().push(col_adapter).push(col_import_pcap); + let col_data_source = get_col_data_source(sniffer, font, language); - let filters_pane = Column::new() - .width(FillPortion(6)) - .padding(10) - .spacing(15) - .push( - select_filters_translation(language) - .font(font) - .class(TextType::Title) - .size(FONT_SIZE_TITLE), - ) + let col_checkboxes = Column::new() + .spacing(10) + .push(Space::with_height(82)) .push(get_filters_group(&sniffer.filters, font, language)) - .push(Rule::horizontal(40)) - .push(get_export_pcap_group( + .push_maybe(get_export_pcap_group_maybe( &sniffer.capture_source, &sniffer.export_pcap, language, font, - )) - .push( - Container::new(button_start(font, language, color_gradient)) - .width(Length::Fill) - .height(Length::Fill) - .align_y(Alignment::Start) - .align_x(Alignment::Center), - ); + )); + + let right_col = Column::new() + .width(FillPortion(1)) + .padding(10) + .push(col_checkboxes) + .push(vertical_space()) + .push(button_start(font_headers, language, color_gradient)) + .push(vertical_space()); let body = Column::new().push(Space::with_height(5)).push( Row::new() - .push(col_capture_source) - .push(Space::with_width(30)) - .push(filters_pane), + .push(col_data_source) + .push(Space::with_width(15)) + .push(right_col), ); Container::new(body).height(Length::Fill) } fn button_start<'a>( - font: Font, + font_headers: Font, language: Language, color_gradient: GradientType, -) -> Tooltip<'a, Message, StyleType> { - let content = button( - Icon::Rocket - .to_text() - .size(25) +) -> Button<'a, Message, StyleType> { + button( + Text::new(start_translation(language)) + .font(font_headers) + .size(FONT_SIZE_TITLE) + .width(Length::Fill) .align_x(alignment::Alignment::Center) .align_y(alignment::Alignment::Center), ) - .padding(10) - .height(80) - .width(160) + .padding(15) + .width(Length::Fill) .class(ButtonType::Gradient(color_gradient)) - .on_press(Message::Start); - - let tooltip = start_translation(language).to_string(); - //tooltip.push_str(" [⏎]"); - let position = Position::Top; - - Tooltip::new(content, Text::new(tooltip).font(font), position) - .gap(5) - .class(ContainerType::Tooltip) + .on_press(Message::Start) } -fn get_col_adapter(sniffer: &Sniffer, font: Font) -> Column<'_, Message, StyleType> { - let ConfigSettings { language, .. } = sniffer.configs.settings; +fn get_col_data_source( + sniffer: &Sniffer, + font: Font, + language: Language, +) -> Column<'_, Message, StyleType> { + let picklist = PickList::new( + &Language::ALL[..], + Some(language), + Message::LanguageSelection, + ) + .padding([2, 7]) + .font(font); + let mut col = Column::new() + .align_x(Alignment::Center) + .padding(10) + .spacing(20) + .height(Length::Fill) + .width(FillPortion(1)) + .push( + Text::new(traffic_source_translation(language)) + .font(font) + .class(TextType::Title) + .size(FONT_SIZE_TITLE), + ) + .push(picklist); + + match &sniffer.capture_source { + CaptureSource::Device(_) => { + col = col.push(get_col_adapter(sniffer, font, language)); + } + CaptureSource::File(_) => { + col = col.push(get_col_import_pcap( + language, + font, + &sniffer.capture_source, + &sniffer.import_pcap_path, + )); + } + } + + col +} + +fn get_col_adapter( + sniffer: &Sniffer, + font: Font, + language: Language, +) -> Column<'_, Message, StyleType> { let mut dev_str_list = vec![]; for my_dev in &sniffer.my_devices { let mut title = String::new(); @@ -163,16 +187,8 @@ fn get_col_adapter(sniffer: &Sniffer, font: Font) -> Column<'_, Message, StyleTy } Column::new() - .padding(10) .spacing(5) .height(Length::Fill) - .width(FillPortion(4)) - .push( - choose_adapters_translation(language) - .font(font) - .class(TextType::Title) - .size(FONT_SIZE_TITLE), - ) .push(if dev_str_list.is_empty() { Into::>::into(center( Icon::get_hourglass(sniffer.dots_pulse.0.len()).size(60), @@ -180,7 +196,7 @@ fn get_col_adapter(sniffer: &Sniffer, font: Font) -> Column<'_, Message, StyleTy } else { Scrollable::with_direction( dev_str_list.into_iter().fold( - Column::new().padding(13).spacing(5), + Column::new().padding(Padding::ZERO.right(13)).spacing(5), |scroll_adapters, (name, title, subtitle, addrs)| { let addrs_text = if addrs.is_empty() { None @@ -245,7 +261,6 @@ fn get_col_import_pcap<'a>( let content = Column::new() .width(Length::Fill) - .align_x(Alignment::Center) .spacing(5) .push(button_row); @@ -260,19 +275,9 @@ fn get_col_import_pcap<'a>( }) .on_press(Message::SetPcapImport(path.to_string())), ) - .padding(13); + .padding(Padding::ZERO.right(13)); - Column::new() - .padding(10) - .spacing(5) - .width(FillPortion(4)) - .push( - Text::new(import_capture_translation(language)) - .font(font) - .class(TextType::Title) - .size(FONT_SIZE_TITLE), - ) - .push(button) + Column::new().spacing(5).push(button) } fn get_filters_group<'a>( @@ -298,7 +303,7 @@ fn get_filters_group<'a>( .font(font); let inner_col = Column::new() .spacing(10) - .padding(Padding::ZERO.left(45)) + .padding(Padding::ZERO.left(26)) .push( Row::new() .align_y(Alignment::Center) @@ -309,24 +314,20 @@ fn get_filters_group<'a>( ret_val = ret_val.push(inner_col); } - Container::new( - Container::new(ret_val) - .padding(10) - .width(Length::Fill) - .class(ContainerType::BorderedRound), - ) - .height(Length::Fill) - .align_y(Alignment::Start) + Container::new(ret_val) + .padding(15) + .width(Length::Fill) + .class(ContainerType::BorderedRound) } -fn get_export_pcap_group<'a>( +fn get_export_pcap_group_maybe<'a>( cs: &CaptureSource, export_pcap: &ExportPcap, language: Language, font: Font, -) -> Container<'a, Message, StyleType> { +) -> Option> { if matches!(cs, CaptureSource::File(_)) { - return Container::new(Space::with_height(Length::Fill)); + return None; } let enabled = export_pcap.enabled(); @@ -344,7 +345,7 @@ fn get_export_pcap_group<'a>( if enabled { let inner_col = Column::new() .spacing(10) - .padding(Padding::ZERO.left(45)) + .padding(Padding::ZERO.left(26)) .push( Row::new() .align_y(Alignment::Center) @@ -354,8 +355,7 @@ fn get_export_pcap_group<'a>( TextInput::new(ExportPcap::DEFAULT_FILE_NAME, file_name) .on_input(Message::OutputPcapFile) .padding([2, 5]) - .font(font) - .width(200), + .font(font), ), ) .push( @@ -376,12 +376,10 @@ fn get_export_pcap_group<'a>( ret_val = ret_val.push(inner_col); } - Container::new( + Some( Container::new(ret_val) - .padding(10) + .padding(15) .width(Length::Fill) .class(ContainerType::BorderedRound), ) - .height(Length::Fill) - .align_y(Alignment::Start) } diff --git a/src/gui/pages/settings_notifications_page.rs b/src/gui/pages/settings_notifications_page.rs index 2d168e2a..1f70a25b 100644 --- a/src/gui/pages/settings_notifications_page.rs +++ b/src/gui/pages/settings_notifications_page.rs @@ -71,11 +71,14 @@ pub fn settings_notifications_page<'a>(sniffer: &Sniffer) -> Container<'a, Messa .push(Space::with_height(5)); let volume_notification_col = Column::new() + .padding(5) + .spacing(10) .align_x(Alignment::Center) .width(Length::Fill) .push(volume_slider(language, font, notifications.volume)) .push(Scrollable::with_direction( Column::new() + .spacing(10) .align_x(Alignment::Center) .width(Length::Fill) .push(get_data_notify( @@ -103,7 +106,7 @@ fn get_data_notify<'a>( data_notification: DataNotification, language: Language, font: Font, -) -> Column<'a, Message, StyleType> { +) -> Container<'a, Message, StyleType> { let checkbox = Checkbox::new( data_exceeded_translation(language), data_notification.threshold.is_some(), @@ -133,12 +136,10 @@ fn get_data_notify<'a>( let mut ret_val = Column::new().spacing(15).push(checkbox); if data_notification.threshold.is_none() { - Column::new().padding(5).push( - Container::new(ret_val) - .padding(10) - .width(700) - .class(ContainerType::BorderedRound), - ) + Container::new(ret_val) + .padding(15) + .width(700) + .class(ContainerType::BorderedRound) } else { let data_representation_row = row_data_representation( data_notification, @@ -152,12 +153,11 @@ fn get_data_notify<'a>( .push(sound_row) .push(data_representation_row) .push(input_row); - Column::new().padding(5).push( - Container::new(ret_val) - .padding(10) - .width(700) - .class(ContainerType::BorderedRound), - ) + + Container::new(ret_val) + .padding(15) + .width(700) + .class(ContainerType::BorderedRound) } } @@ -165,7 +165,7 @@ fn get_favorite_notify<'a>( favorite_notification: FavoriteNotification, language: Language, font: Font, -) -> Column<'a, Message, StyleType> { +) -> Container<'a, Message, StyleType> { let checkbox = Checkbox::new( favorite_transmitted_translation(language), favorite_notification.notify_on_favorite, @@ -192,19 +192,15 @@ fn get_favorite_notify<'a>( language, ); ret_val = ret_val.push(sound_row); - Column::new().padding(5).push( - Container::new(ret_val) - .padding(10) - .width(700) - .class(ContainerType::BorderedRound), - ) + Container::new(ret_val) + .padding(15) + .width(700) + .class(ContainerType::BorderedRound) } else { - Column::new().padding(5).push( - Container::new(ret_val) - .padding(10) - .width(700) - .class(ContainerType::BorderedRound), - ) + Container::new(ret_val) + .padding(15) + .width(700) + .class(ContainerType::BorderedRound) } } @@ -220,7 +216,7 @@ fn input_group_bytes<'a>( let input_row = Row::new() .spacing(5) .align_y(Alignment::Center) - .push(Space::with_width(45)) + .padding(Padding::ZERO.left(26)) .push(Text::new(format!("{}:", threshold_translation(language))).font(font)) .push( TextInput::new( @@ -305,7 +301,7 @@ fn sound_buttons<'a>( .width(Length::Shrink) .align_y(Alignment::Center) .spacing(5) - .push(Space::with_width(45)) + .padding(Padding::ZERO.left(26)) .push(Text::new(format!("{}:", sound_translation(language))).font(font)); for option in Sound::ALL { @@ -378,7 +374,7 @@ fn row_data_representation<'a>( .width(Length::Shrink) .align_y(Alignment::Center) .spacing(5) - .push(Space::with_width(45)) + .padding(Padding::ZERO.left(26)) .push(Text::new(format!("{}:", data_representation_translation(language))).font(font)); for option in DataRepr::ALL { diff --git a/src/translations/translations_5.rs b/src/translations/translations_5.rs index e2a04221..cfe4516e 100644 --- a/src/translations/translations_5.rs +++ b/src/translations/translations_5.rs @@ -10,3 +10,12 @@ pub fn filter_traffic_translation(language: Language) -> String { } .to_string() } + +// the source from which Sniffnet reads network traffic (i.e., a capture file or a network adapter) +pub fn traffic_source_translation(language: Language) -> &'static str { + match language { + Language::EN => "Traffic source", + Language::IT => "Fonte del traffico", + _ => "Traffic source", + } +} diff --git a/src/utils/types/icon.rs b/src/utils/types/icon.rs index 87f16a39..5208aa6a 100644 --- a/src/utils/types/icon.rs +++ b/src/utils/types/icon.rs @@ -39,7 +39,7 @@ pub enum Icon { PacketsThreshold, // Restore, Roadmap, - Rocket, + // Rocket, Settings, Sniffnet, SortAscending, @@ -85,7 +85,7 @@ pub fn codepoint(&self) -> char { Icon::Overview => 'd', Icon::PacketsThreshold => '\\', // Icon::Restore => 'k', - Icon::Rocket => 'S', + // Icon::Rocket => 'S', Icon::Settings => 'a', Icon::Sniffnet => 'A', Icon::Star => 'g', From ee175b6a4330431177fc93b33ea47c13bf0e2d15 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 16 Aug 2025 16:30:57 +0200 Subject: [PATCH 45/74] initial page: add picklist to select capture source --- src/gui/pages/initial_page.rs | 81 +++++++++++++++++-------- src/gui/sniffer.rs | 39 +++++++++--- src/gui/styles/button.rs | 2 +- src/gui/types/message.rs | 3 + src/networking/types/capture_context.rs | 11 +++- src/translations/translations.rs | 54 ++++++++--------- src/translations/translations_4.rs | 20 +++--- 7 files changed, 134 insertions(+), 76 deletions(-) diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index 87bfff4f..105b7647 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -15,14 +15,14 @@ use crate::gui::types::export_pcap::ExportPcap; use crate::gui::types::filters::Filters; use crate::gui::types::message::Message; -use crate::networking::types::capture_context::CaptureSource; +use crate::networking::types::capture_context::{CaptureSource, CaptureSourcePicklist}; use crate::translations::translations::{ - address_translation, addresses_translation, choose_adapters_translation, start_translation, + address_translation, addresses_translation, network_adapter_translation, start_translation, }; use crate::translations::translations_3::{ directory_translation, export_capture_translation, file_name_translation, }; -use crate::translations::translations_4::import_capture_translation; +use crate::translations::translations_4::capture_file_translation; use crate::translations::translations_5::{filter_traffic_translation, traffic_source_translation}; use crate::utils::formatted_strings::get_path_termination_string; use crate::utils::types::file_info::FileInfo; @@ -30,10 +30,9 @@ use crate::{ConfigSettings, Language, StyleType}; use iced::Length::FillPortion; use iced::widget::scrollable::Direction; -use iced::widget::tooltip::Position; use iced::widget::{ - Button, Checkbox, Column, Container, PickList, Row, Rule, Scrollable, Space, Text, TextInput, - Tooltip, button, center, vertical_space, + Button, Checkbox, Column, Container, PickList, Row, Scrollable, Space, Text, TextInput, button, + center, vertical_space, }; use iced::{Alignment, Font, Length, Padding, alignment}; @@ -52,21 +51,27 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let col_checkboxes = Column::new() .spacing(10) - .push(Space::with_height(82)) + .push(Space::with_height(66)) .push(get_filters_group(&sniffer.filters, font, language)) .push_maybe(get_export_pcap_group_maybe( - &sniffer.capture_source, + sniffer.capture_source_picklist, &sniffer.export_pcap, language, font, )); + let is_capture_source_consistent = sniffer.is_capture_source_consistent(); let right_col = Column::new() .width(FillPortion(1)) .padding(10) .push(col_checkboxes) .push(vertical_space()) - .push(button_start(font_headers, language, color_gradient)) + .push(button_start( + font_headers, + language, + color_gradient, + is_capture_source_consistent, + )) .push(vertical_space()); let body = Column::new().push(Space::with_height(5)).push( @@ -83,6 +88,7 @@ fn button_start<'a>( font_headers: Font, language: Language, color_gradient: GradientType, + is_capture_source_consistent: bool, ) -> Button<'a, Message, StyleType> { button( Text::new(start_translation(language)) @@ -95,7 +101,11 @@ fn button_start<'a>( .padding(15) .width(Length::Fill) .class(ButtonType::Gradient(color_gradient)) - .on_press(Message::Start) + .on_press_maybe(if is_capture_source_consistent { + Some(Message::Start) + } else { + None + }) } fn get_col_data_source( @@ -103,33 +113,51 @@ fn get_col_data_source( font: Font, language: Language, ) -> Column<'_, Message, StyleType> { + let current_option = if sniffer.capture_source_picklist == CaptureSourcePicklist::Device { + network_adapter_translation(language) + } else { + capture_file_translation(language) + }; let picklist = PickList::new( - &Language::ALL[..], - Some(language), - Message::LanguageSelection, + [ + network_adapter_translation(language), + capture_file_translation(language), + ], + Some(current_option), + move |option| { + if option == network_adapter_translation(language) { + Message::SetCaptureSource(CaptureSourcePicklist::Device) + } else { + Message::SetCaptureSource(CaptureSourcePicklist::File) + } + }, ) .padding([2, 7]) .font(font); let mut col = Column::new() .align_x(Alignment::Center) - .padding(10) - .spacing(20) + .padding(Padding::new(10.0).top(30)) + .spacing(30) .height(Length::Fill) .width(FillPortion(1)) .push( - Text::new(traffic_source_translation(language)) - .font(font) - .class(TextType::Title) - .size(FONT_SIZE_TITLE), - ) - .push(picklist); + Row::new() + .spacing(10) + .push( + Text::new(traffic_source_translation(language)) + .font(font) + .class(TextType::Title) + .size(FONT_SIZE_TITLE), + ) + .push(picklist), + ); - match &sniffer.capture_source { - CaptureSource::Device(_) => { + match &sniffer.capture_source_picklist { + CaptureSourcePicklist::Device => { col = col.push(get_col_adapter(sniffer, font, language)); } - CaptureSource::File(_) => { + CaptureSourcePicklist::File => { col = col.push(get_col_import_pcap( language, font, @@ -261,6 +289,7 @@ fn get_col_import_pcap<'a>( let content = Column::new() .width(Length::Fill) + .align_x(alignment::Alignment::Center) .spacing(5) .push(button_row); @@ -321,12 +350,12 @@ fn get_filters_group<'a>( } fn get_export_pcap_group_maybe<'a>( - cs: &CaptureSource, + cs_pick: CaptureSourcePicklist, export_pcap: &ExportPcap, language: Language, font: Font, ) -> Option> { - if matches!(cs, CaptureSource::File(_)) { + if cs_pick == CaptureSourcePicklist::File { return None; } diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index e71ad7bb..29e28dd8 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -46,7 +46,9 @@ use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders}; use crate::networking::parse_packets::BackendTrafficMessage; use crate::networking::parse_packets::parse_packets; -use crate::networking::types::capture_context::{CaptureContext, CaptureSource, MyPcapImport}; +use crate::networking::types::capture_context::{ + CaptureContext, CaptureSource, CaptureSourcePicklist, MyPcapImport, +}; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::host_data_states::HostDataStates; @@ -88,6 +90,8 @@ pub struct Sniffer { pub logged_notifications: (VecDeque, usize), /// Reports if a newer release of the software is available on GitHub pub newer_release_available: Option, + /// Capture source picklist, to select the source of the capture + pub capture_source_picklist: CaptureSourcePicklist, /// Network device to be analyzed, or PCAP file to be imported pub capture_source: CaptureSource, /// List of network devices @@ -154,6 +158,7 @@ pub fn new(configs: Configs) -> Self { favorite_hosts: HashSet::new(), logged_notifications: (VecDeque::new(), 0), newer_release_available: None, + capture_source_picklist: CaptureSourcePicklist::default(), capture_source: CaptureSource::Device(device), my_devices: Vec::new(), filters: Filters::default(), @@ -277,6 +282,16 @@ pub fn update(&mut self, message: Message) -> Task { } } Message::DeviceSelection(name) => self.set_device(&name), + Message::SetCaptureSource(cs_pick) => { + self.capture_source_picklist = cs_pick; + return if cs_pick == CaptureSourcePicklist::File { + Task::done(Message::SetPcapImport(self.import_pcap_path.clone())) + } else { + Task::done(Message::DeviceSelection( + self.configs.device.device_name.clone(), + )) + }; + } Message::ToggleFilters => { self.filters.toggle(); } @@ -289,7 +304,11 @@ pub fn update(&mut self, message: Message) -> Task { self.report_sort_type = sort; } Message::OpenWebPage(web_page) => Self::open_web(&web_page), - Message::Start => return self.start(), + Message::Start => { + if self.is_capture_source_consistent() { + return self.start(); + } + } Message::Reset => self.reset(), Message::Style(style) => { self.configs.settings.style = style; @@ -680,14 +699,6 @@ fn refresh_data(&mut self, mut msg: InfoTraffic, no_more_packets: bool) { } self.traffic_chart.update_charts_data(&msg, no_more_packets); - if let CaptureSource::Device(device) = &self.capture_source { - let current_device_name = device.get_name().clone(); - // update ConfigDevice stored if different from last sniffed device - let last_device_name_sniffed = self.configs.device.device_name.clone(); - if current_device_name.ne(&last_device_name_sniffed) { - self.configs.device.device_name = current_device_name; - } - } // update host dropdowns self.host_data_states.update_states(&self.search); } @@ -792,6 +803,7 @@ fn reset(&mut self) { fn set_device(&mut self, name: &str) { for my_dev in &self.my_devices { if my_dev.get_name().eq(&name) { + self.configs.device.device_name = name.to_string(); self.capture_source = CaptureSource::Device(my_dev.clone()); break; } @@ -1069,6 +1081,13 @@ fn register_sigint_handler() -> Task { Task::run(rx, |()| Message::Quit) } + + pub fn is_capture_source_consistent(&self) -> bool { + self.capture_source_picklist == CaptureSourcePicklist::Device + && matches!(self.capture_source, CaptureSource::Device(_)) + || self.capture_source_picklist == CaptureSourcePicklist::File + && matches!(self.capture_source, CaptureSource::File(_)) + } } #[cfg(test)] diff --git a/src/gui/styles/button.rs b/src/gui/styles/button.rs index ee75d3fb..f65e8265 100644 --- a/src/gui/styles/button.rs +++ b/src/gui/styles/button.rs @@ -216,7 +216,7 @@ fn disabled(&self, style: &StyleType) -> Style { }, }, text_color: Color { - a: ext.alpha_chart_badge, + a: 0.5, ..colors.text_headers }, shadow: Shadow::default(), diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index 8b683414..34455cc5 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -5,6 +5,7 @@ use crate::gui::pages::types::running_page::RunningPage; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::types::gradient_type::GradientType; +use crate::networking::types::capture_context::CaptureSourcePicklist; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::{Host, HostMessage}; use crate::networking::types::info_traffic::InfoTraffic; @@ -22,6 +23,8 @@ pub enum Message { StartApp(Option), /// Sent by the backend parsing packets; includes the capture id, new data, new hosts batched data, and whether an offline capture has finished TickRun(usize, InfoTraffic, Vec, bool), + /// Capture source selected from the picklist + SetCaptureSource(CaptureSourcePicklist), /// Select network device DeviceSelection(String), /// Toggle BPF filter checkbox diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index 93fc812b..0b1964e3 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -2,7 +2,7 @@ use crate::networking::types::my_device::MyDevice; use crate::networking::types::my_link_type::MyLinkType; use crate::translations::translations::network_adapter_translation; -use crate::translations::translations_3::file_name_translation; +use crate::translations::translations_4::capture_file_translation; use crate::translations::types::language::Language; use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat}; @@ -158,7 +158,7 @@ impl CaptureSource { pub fn title(&self, language: Language) -> &str { match self { Self::Device(_) => network_adapter_translation(language), - Self::File(_) => file_name_translation(language), + Self::File(_) => capture_file_translation(language), } } @@ -221,3 +221,10 @@ pub fn new(path: String) -> Self { } } } + +#[derive(Clone, Eq, PartialEq, Debug, Copy, Default)] +pub enum CaptureSourcePicklist { + #[default] + Device, + File, +} diff --git a/src/translations/translations.rs b/src/translations/translations.rs index 2c60159f..c0d9fcd4 100644 --- a/src/translations/translations.rs +++ b/src/translations/translations.rs @@ -5,33 +5,33 @@ use crate::StyleType; use crate::translations::types::language::Language; -pub fn choose_adapters_translation<'a>(language: Language) -> Text<'a, StyleType> { - Text::new(match language { - Language::EN => "Select network adapter to inspect", - Language::IT => "Seleziona la scheda di rete da ispezionare", - Language::FR => "Sélectionnez une carte réseau à inspecter", - Language::ES => "Seleccione el adaptador de red que desea inspeccionar", - Language::PL => "Wybierz adapter sieciowy do inspekcji", - Language::DE => "Wähle einen Netzwerkadapter zum überwachen aus", - Language::UK => "Виберіть мережевий адаптер для перевірки", - Language::ZH => "选择需要监控的网络适配器", - Language::ZH_TW => "選取要檢視的網路介面卡", - Language::RO => "Selectați adaptor de rețea pentru a inspecta", - Language::KO => "검사할 네트워크 어댑터 선택", - Language::TR => "İncelemek için bir ağ adaptörü seçiniz", - Language::RU => "Выберите сетевой адаптер для инспекции", - Language::PT => "Selecione o adaptador de rede a inspecionar", - Language::EL => "Επίλεξε τον προσαρμογέα δικτύου για επιθεώρηση", - // Language::FA => "مبدل شبکه را برای بازرسی انتخاب کنید", - Language::SV => "Välj nätverksadapter att inspektera", - Language::FI => "Valitse tarkasteltava verkkosovitin", - Language::JA => "使用するネットワーク アダプターを選択してください", - Language::UZ => "Tekshirish uchun tarmoq adapterini tanlang", - Language::VI => "Hãy chọn network adapter để quan sát", - Language::ID => "Pilih Adapter Jaringan yang ingin dicek", - Language::NL => "Selecteer netwerkadapter om te inspecteren", - }) -} +// pub fn choose_adapters_translation<'a>(language: Language) -> Text<'a, StyleType> { +// Text::new(match language { +// Language::EN => "Select network adapter to inspect", +// Language::IT => "Seleziona la scheda di rete da ispezionare", +// Language::FR => "Sélectionnez une carte réseau à inspecter", +// Language::ES => "Seleccione el adaptador de red que desea inspeccionar", +// Language::PL => "Wybierz adapter sieciowy do inspekcji", +// Language::DE => "Wähle einen Netzwerkadapter zum überwachen aus", +// Language::UK => "Виберіть мережевий адаптер для перевірки", +// Language::ZH => "选择需要监控的网络适配器", +// Language::ZH_TW => "選取要檢視的網路介面卡", +// Language::RO => "Selectați adaptor de rețea pentru a inspecta", +// Language::KO => "검사할 네트워크 어댑터 선택", +// Language::TR => "İncelemek için bir ağ adaptörü seçiniz", +// Language::RU => "Выберите сетевой адаптер для инспекции", +// Language::PT => "Selecione o adaptador de rede a inspecionar", +// Language::EL => "Επίλεξε τον προσαρμογέα δικτύου για επιθεώρηση", +// // Language::FA => "مبدل شبکه را برای بازرسی انتخاب کنید", +// Language::SV => "Välj nätverksadapter att inspektera", +// Language::FI => "Valitse tarkasteltava verkkosovitin", +// Language::JA => "使用するネットワーク アダプターを選択してください", +// Language::UZ => "Tekshirish uchun tarmoq adapterini tanlang", +// Language::VI => "Hãy chọn network adapter để quan sát", +// Language::ID => "Pilih Adapter Jaringan yang ingin dicek", +// Language::NL => "Selecteer netwerkadapter om te inspecteren", +// }) +// } // pub fn application_protocol_translation(language: Language) -> &'static str { // match language { diff --git a/src/translations/translations_4.rs b/src/translations/translations_4.rs index d3b750a1..c07fde21 100644 --- a/src/translations/translations_4.rs +++ b/src/translations/translations_4.rs @@ -53,17 +53,17 @@ pub fn share_feedback_translation(language: Language) -> &'static str { // } // } -pub fn import_capture_translation(language: Language) -> &'static str { +pub fn capture_file_translation(language: Language) -> &'static str { match language { - Language::EN => "Import capture file", - Language::IT => "Importa file di cattura", - Language::FR => "Importer un fichier de capture", - Language::JA => "キャプチャファイルをインポート", - Language::ZH => "导入捕获文件", - Language::NL => "Importeer capture bestand", - Language::DE => "Aufzeichnungsdatei importieren", - Language::UZ => "Tahlil faylini import qilish", - _ => "Import capture file", + Language::EN => "Capture file", + Language::IT => "File di cattura", + Language::FR => "Fichier de capture", + Language::JA => "キャプチャファイル", + Language::ZH => "捕获文件", + Language::NL => "Capture bestand", + Language::DE => "Aufzeichnungsdatei", + Language::UZ => "Tahlil faylini", + _ => "Capture file", } } From f129c3b7849d5764cefafa8324e9e05193ff7888 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 17 Aug 2025 14:39:21 +0200 Subject: [PATCH 46/74] add active filters info in overview page --- src/gui/pages/initial_page.rs | 4 +- src/gui/pages/overview_page.rs | 57 +++++++++++++++++----------- src/gui/styles/button.rs | 10 +++-- src/networking/types/my_link_type.rs | 34 +---------------- src/translations/translations.rs | 54 +++++++++++++------------- 5 files changed, 71 insertions(+), 88 deletions(-) diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index 105b7647..b079c6fe 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -51,7 +51,6 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let col_checkboxes = Column::new() .spacing(10) - .push(Space::with_height(66)) .push(get_filters_group(&sniffer.filters, font, language)) .push_maybe(get_export_pcap_group_maybe( sniffer.capture_source_picklist, @@ -64,6 +63,7 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let right_col = Column::new() .width(FillPortion(1)) .padding(10) + .push(Space::with_height(76)) .push(col_checkboxes) .push(vertical_space()) .push(button_start( @@ -98,7 +98,7 @@ fn button_start<'a>( .align_x(alignment::Alignment::Center) .align_y(alignment::Alignment::Center), ) - .padding(15) + .padding(20) .width(Length::Fill) .class(ButtonType::Gradient(color_gradient)) .on_press_maybe(if is_capture_source_consistent { diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 083e2d71..979b6e05 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -12,22 +12,24 @@ use crate::gui::styles::container::ContainerType; use crate::gui::styles::rule::RuleType; use crate::gui::styles::scrollbar::ScrollbarType; -use crate::gui::styles::style_constants::FONT_SIZE_TITLE; +use crate::gui::styles::style_constants::{FONT_SIZE_FOOTER, FONT_SIZE_TITLE}; use crate::gui::styles::text::TextType; use crate::gui::styles::types::palette_extension::PaletteExtension; +use crate::gui::types::filters::Filters; use crate::gui::types::message::Message; use crate::networking::types::capture_context::CaptureSource; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; +use crate::networking::types::my_link_type::MyLinkType; use crate::networking::types::service::Service; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; use crate::report::types::search_parameters::SearchParameters; use crate::report::types::sort_type::SortType; use crate::translations::translations::{ active_filters_translation, error_translation, incoming_translation, no_addresses_translation, - outgoing_translation, traffic_rate_translation, waiting_translation, + none_translation, outgoing_translation, traffic_rate_translation, waiting_translation, }; use crate::translations::translations_2::{ data_representation_translation, dropped_translation, host_translation, @@ -74,7 +76,6 @@ pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { if tot_packets == 0 { // no packets observed at all - // TODO: add info about the capture filters (if any) in the method called below body = body_no_packets(&sniffer.capture_source, font, language, dots); } else { // some packets are there! @@ -112,7 +113,6 @@ pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { Container::new(Column::new().push(tab_and_body.push(body))).height(Length::Fill) } -// TODO: add info about active filters if any fn body_no_packets<'a>( cs: &CaptureSource, font: Font, @@ -436,7 +436,7 @@ fn col_info(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { } = sniffer.configs.settings; let PaletteExtension { font, .. } = style.get_extension(); - let col_device = col_device(language, font, &sniffer.capture_source); + let col_device = col_device(language, font, &sniffer.capture_source, &sniffer.filters); let col_data_representation = col_data_representation(language, font, sniffer.traffic_chart.data_repr); @@ -496,6 +496,7 @@ fn col_device<'a>( language: Language, font: Font, cs: &CaptureSource, + filters: &'a Filters, ) -> Column<'a, Message, StyleType> { let link_type = cs.get_link_type(); #[cfg(not(target_os = "windows"))] @@ -503,15 +504,34 @@ fn col_device<'a>( #[cfg(target_os = "windows")] let cs_info = cs.get_desc().unwrap_or(cs.get_name()); + let filters_desc = if filters.is_some_filter_active() { + filters.bpf() + } else { + none_translation(language) + }; + Column::new() .height(Length::Fill) .spacing(10) + .push( + Column::new() + .push( + Text::new(format!("{}:", cs.title(language))) + .class(TextType::Subtitle) + .font(font), + ) + .push( + Row::new() + .spacing(10) + .push(Text::new(format!(" {}", &cs_info)).font(font)) + .push(get_link_type_tooltip(link_type, language, font)), + ), + ) .push(TextType::highlighted_subtitle_with_desc( - cs.title(language), - &cs_info, + active_filters_translation(language), + filters_desc, font, )) - .push(link_type.link_type_col(language, font)) } fn col_data_representation<'a>( @@ -731,25 +751,16 @@ fn get_star_button<'a>(is_favorite: bool, host: Host) -> Button<'a, Message, Sty .on_press(Message::AddOrRemoveFavorite(host, !is_favorite)) } -fn get_active_filters_tooltip( - bpf: &str, +fn get_link_type_tooltip<'a>( + link_type: MyLinkType, language: Language, font: Font, -) -> Tooltip<'_, Message, StyleType> { - let mut ret_val = Column::new().push( - Text::new(active_filters_translation(language)) - .font(font) - .class(TextType::Subtitle), - ); - - ret_val = ret_val.push(Row::new().push(Text::new(bpf).font(font))); - +) -> Tooltip<'a, Message, StyleType> { Tooltip::new( Container::new( - Icon::Funnel - .to_text() + Text::new("i") + .size(FONT_SIZE_FOOTER) .font(font) - .size(15) .line_height(LineHeight::Relative(1.0)), ) .align_x(Alignment::Center) @@ -757,7 +768,7 @@ fn get_active_filters_tooltip( .height(20) .width(20) .class(ContainerType::BadgeInfo), - ret_val, + Text::new(link_type.full_print_on_one_line(language)).font(font), Position::FollowCursor, ) .class(ContainerType::Tooltip) diff --git a/src/gui/styles/button.rs b/src/gui/styles/button.rs index f65e8265..899e19e3 100644 --- a/src/gui/styles/button.rs +++ b/src/gui/styles/button.rs @@ -64,7 +64,9 @@ fn active(&self, style: &StyleType) -> Style { radius: match self { ButtonType::Neutral => 0.0.into(), ButtonType::TabActive | ButtonType::TabInactive => Radius::new(0).bottom(30), - ButtonType::BorderedRound | ButtonType::BorderedRoundSelected => 12.0.into(), + ButtonType::BorderedRound + | ButtonType::BorderedRoundSelected + | ButtonType::Gradient(_) => 12.0.into(), ButtonType::Starred | ButtonType::NotStarred => 100.0.into(), _ => BORDER_BUTTON_RADIUS.into(), }, @@ -154,7 +156,9 @@ fn hovered(&self, style: &StyleType) -> Style { radius: match self { ButtonType::Neutral => 0.0.into(), ButtonType::TabActive | ButtonType::TabInactive => Radius::new(0).bottom(30), - ButtonType::BorderedRound | ButtonType::BorderedRoundSelected => 12.0.into(), + ButtonType::BorderedRound + | ButtonType::BorderedRoundSelected + | ButtonType::Gradient(_) => 12.0.into(), ButtonType::Starred | ButtonType::NotStarred => 100.0.into(), _ => BORDER_BUTTON_RADIUS.into(), }, @@ -208,7 +212,7 @@ fn disabled(&self, style: &StyleType) -> Style { _ => Background::Color(ext.buttons_color), }), border: Border { - radius: BORDER_BUTTON_RADIUS.into(), + radius: 12.0.into(), width: BORDER_WIDTH, color: Color { a: ext.alpha_chart_badge, diff --git a/src/networking/types/my_link_type.rs b/src/networking/types/my_link_type.rs index d9d74dd4..4730523b 100644 --- a/src/networking/types/my_link_type.rs +++ b/src/networking/types/my_link_type.rs @@ -1,11 +1,7 @@ -use iced::Font; -use iced::widget::Column; use pcap::Linktype; -use crate::gui::styles::text::TextType; -use crate::gui::types::message::Message; +use crate::Language; use crate::translations::translations_3::link_type_translation; -use crate::{Language, StyleType}; /// Currently supported link types #[derive(Copy, Clone, Default)] @@ -57,32 +53,4 @@ pub fn full_print_on_one_line(self, language: Language) -> String { Self::NotYetAssigned => String::new(), } } - - pub fn link_type_col<'a>( - self, - language: Language, - font: Font, - ) -> Column<'a, Message, StyleType> { - match self { - Self::Null(l) - | Self::Ethernet(l) - | Self::RawIp(l) - | Self::Loop(l) - | Self::IPv4(l) - | Self::IPv6(l) - | Self::Unsupported(l) => { - let link_info = format!( - "{} ({})", - l.get_name().unwrap_or_else(|_| l.0.to_string()), - l.get_description().unwrap_or_else(|_| String::new()) - ); - TextType::highlighted_subtitle_with_desc( - link_type_translation(language), - &link_info, - font, - ) - } - Self::NotYetAssigned => Column::new().height(0), - } - } } diff --git a/src/translations/translations.rs b/src/translations/translations.rs index c0d9fcd4..f1c92a01 100644 --- a/src/translations/translations.rs +++ b/src/translations/translations.rs @@ -60,33 +60,33 @@ // } // } -pub fn select_filters_translation<'a>(language: Language) -> Text<'a, StyleType> { - Text::new(match language { - Language::EN => "Select filters to be applied on network traffic", - Language::IT => "Seleziona i filtri da applicare al traffico di rete", - Language::FR => "Sélectionnez les filtres à appliquer sur le traffic réseau", - Language::ES => "Seleccionar los filtros que se aplicarán al tráfico de red", - Language::PL => "Wybierz filtry, które mają być zastosowane na ruchu sieciowym", - Language::DE => "Wähle die Filter, die auf den Netzwerkverkehr angewendet werden sollen", - Language::UK => "Виберіть фільтри, які мають бути застосовані до мережевого руху", - Language::ZH => "选择需要监控的目标", - Language::ZH_TW => "選取要套用於網路流量的篩選器", - Language::RO => "Selectați filtre pentru traficul de rețea", - Language::KO => "네트워크 트레픽에 적용할 필터 선택", - Language::TR => "Ağ trafiğine uygulanacak filtreleri seçiniz", - Language::RU => "Выберите фильтры для применения к сетевому трафику", - Language::PT => "Selecione os filtros a serem aplicados no tráfego de rede", - Language::EL => "Επίλεξε τα φίλτρα για εφαρμογή στην κίνηση του δικτύου", - // Language::FA => "صافی ها را جهت اعمال بر آمد و شد شبکه انتخاب کنید", - Language::SV => "Välj filtren som ska appliceras på nätverkstrafiken", - Language::FI => "Valitse suodattimet verkkoliikenteelle", - Language::JA => "トラフィックに適用するフィルターを選択してください", - Language::UZ => "Tarmoq trafigiga qo'llaniladigan filtrlarni tanlang", - Language::VI => "Hãy chọn bộ lọc cho lưu lượng mạng", - Language::ID => "Pilih filter yang ingin dipasang dilalulintas jaringan", - Language::NL => "Selecteer filters om toe te passen op netwerkverkeer", - }) -} +// pub fn select_filters_translation<'a>(language: Language) -> Text<'a, StyleType> { +// Text::new(match language { +// Language::EN => "Select filters to be applied on network traffic", +// Language::IT => "Seleziona i filtri da applicare al traffico di rete", +// Language::FR => "Sélectionnez les filtres à appliquer sur le traffic réseau", +// Language::ES => "Seleccionar los filtros que se aplicarán al tráfico de red", +// Language::PL => "Wybierz filtry, które mają być zastosowane na ruchu sieciowym", +// Language::DE => "Wähle die Filter, die auf den Netzwerkverkehr angewendet werden sollen", +// Language::UK => "Виберіть фільтри, які мають бути застосовані до мережевого руху", +// Language::ZH => "选择需要监控的目标", +// Language::ZH_TW => "選取要套用於網路流量的篩選器", +// Language::RO => "Selectați filtre pentru traficul de rețea", +// Language::KO => "네트워크 트레픽에 적용할 필터 선택", +// Language::TR => "Ağ trafiğine uygulanacak filtreleri seçiniz", +// Language::RU => "Выберите фильтры для применения к сетевому трафику", +// Language::PT => "Selecione os filtros a serem aplicados no tráfego de rede", +// Language::EL => "Επίλεξε τα φίλτρα για εφαρμογή στην κίνηση του δικτύου", +// // Language::FA => "صافی ها را جهت اعمال بر آمد و شد شبکه انتخاب کنید", +// Language::SV => "Välj filtren som ska appliceras på nätverkstrafiken", +// Language::FI => "Valitse suodattimet verkkoliikenteelle", +// Language::JA => "トラフィックに適用するフィルターを選択してください", +// Language::UZ => "Tarmoq trafigiga qo'llaniladigan filtrlarni tanlang", +// Language::VI => "Hãy chọn bộ lọc cho lưu lượng mạng", +// Language::ID => "Pilih filter yang ingin dipasang dilalulintas jaringan", +// Language::NL => "Selecteer filters om toe te passen op netwerkverkeer", +// }) +// } pub fn start_translation(language: Language) -> &'static str { match language { From 6c23c7afbe721173083a5e428ee3a6a3808bedeb Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 17 Aug 2025 15:00:47 +0200 Subject: [PATCH 47/74] show tooltip for active filter in overview page (if any) --- src/gui/pages/overview_page.rs | 44 +++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 979b6e05..c094ba84 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -22,7 +22,6 @@ use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; -use crate::networking::types::my_link_type::MyLinkType; use crate::networking::types::service::Service; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; use crate::report::types::search_parameters::SearchParameters; @@ -48,7 +47,7 @@ Button, Column, Container, Row, Rule, Scrollable, Space, Text, Tooltip, button, horizontal_space, vertical_space, }; -use iced::{Alignment, Font, Length, Padding}; +use iced::{Alignment, Element, Font, Length, Padding}; use std::fmt::Write; /// Computes the body of gui overview page @@ -504,10 +503,14 @@ fn col_device<'a>( #[cfg(target_os = "windows")] let cs_info = cs.get_desc().unwrap_or(cs.get_name()); - let filters_desc = if filters.is_some_filter_active() { - filters.bpf() + let filters_desc: Element = if filters.is_some_filter_active() { + Row::new() + .spacing(10) + .push(Text::new("BPF")) + .push(get_info_tooltip(filters.bpf().to_string(), font)) + .into() } else { - none_translation(language) + Text::new(none_translation(language)).font(font).into() }; Column::new() @@ -524,14 +527,25 @@ fn col_device<'a>( Row::new() .spacing(10) .push(Text::new(format!(" {}", &cs_info)).font(font)) - .push(get_link_type_tooltip(link_type, language, font)), + .push(get_info_tooltip( + link_type.full_print_on_one_line(language), + font, + )), + ), + ) + .push( + Column::new() + .push( + Text::new(format!("{}:", active_filters_translation(language))) + .class(TextType::Subtitle) + .font(font), + ) + .push( + Row::new() + .push(Text::new(" ".to_string()).font(font)) + .push(filters_desc), ), ) - .push(TextType::highlighted_subtitle_with_desc( - active_filters_translation(language), - filters_desc, - font, - )) } fn col_data_representation<'a>( @@ -751,11 +765,7 @@ fn get_star_button<'a>(is_favorite: bool, host: Host) -> Button<'a, Message, Sty .on_press(Message::AddOrRemoveFavorite(host, !is_favorite)) } -fn get_link_type_tooltip<'a>( - link_type: MyLinkType, - language: Language, - font: Font, -) -> Tooltip<'a, Message, StyleType> { +fn get_info_tooltip<'a>(info_str: String, font: Font) -> Tooltip<'a, Message, StyleType> { Tooltip::new( Container::new( Text::new("i") @@ -768,7 +778,7 @@ fn get_link_type_tooltip<'a>( .height(20) .width(20) .class(ContainerType::BadgeInfo), - Text::new(link_type.full_print_on_one_line(language)).font(font), + Text::new(info_str).font(font), Position::FollowCursor, ) .class(ContainerType::Tooltip) From fa70d118257da31bf3211c3d304b39abac41049c Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 17 Aug 2025 15:11:47 +0200 Subject: [PATCH 48/74] fix old tests --- src/chart/manage_chart_data.rs | 2 -- src/gui/sniffer.rs | 47 ++---------------------------- src/networking/manage_packets.rs | 2 +- src/networking/types/ip_version.rs | 22 -------------- src/networking/types/protocol.rs | 26 ----------------- 5 files changed, 3 insertions(+), 96 deletions(-) diff --git a/src/chart/manage_chart_data.rs b/src/chart/manage_chart_data.rs index 78a5fa7e..f194672e 100644 --- a/src/chart/manage_chart_data.rs +++ b/src/chart/manage_chart_data.rs @@ -327,8 +327,6 @@ fn test_chart_data_updates() { no_more_packets: false, }; let mut info_traffic = InfoTraffic { - all_bytes: 0, - all_packets: 0, tot_data_info, dropped_packets: 0, ..Default::default() diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 29e28dd8..3f830a48 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -1122,8 +1122,8 @@ mod tests { use crate::notifications::types::sound::Sound; use crate::report::types::sort_type::SortType; use crate::{ - ByteMultiple, ConfigDevice, ConfigSettings, ConfigWindow, Configs, IpVersion, Language, - Protocol, ReportSortType, RunningPage, Sniffer, StyleType, + ByteMultiple, ConfigDevice, ConfigSettings, ConfigWindow, Configs, Language, + ReportSortType, RunningPage, Sniffer, StyleType, }; // helpful to clean up files generated from tests @@ -1149,49 +1149,6 @@ fn drop(&mut self) { } } - #[test] - #[parallel] // needed to not collide with other tests generating configs files - fn test_correctly_update_ip_version() { - let mut sniffer = Sniffer::new(Configs::default()); - - assert_eq!(sniffer.filters.ip_versions, HashSet::from(IpVersion::ALL)); - sniffer.update(Message::IpVersionSelection(IpVersion::IPv6, true)); - assert_eq!(sniffer.filters.ip_versions, HashSet::from(IpVersion::ALL)); - sniffer.update(Message::IpVersionSelection(IpVersion::IPv4, false)); - assert_eq!( - sniffer.filters.ip_versions, - HashSet::from([IpVersion::IPv6]) - ); - sniffer.update(Message::IpVersionSelection(IpVersion::IPv6, false)); - assert_eq!(sniffer.filters.ip_versions, HashSet::new()); - } - - #[test] - #[parallel] // needed to not collide with other tests generating configs files - fn test_correctly_update_protocol() { - let mut sniffer = Sniffer::new(Configs::default()); - - assert_eq!(sniffer.filters.protocols, HashSet::from(Protocol::ALL)); - sniffer.update(Message::ProtocolSelection(Protocol::UDP, true)); - assert_eq!(sniffer.filters.protocols, HashSet::from(Protocol::ALL)); - sniffer.update(Message::ProtocolSelection(Protocol::UDP, false)); - assert_eq!( - sniffer.filters.protocols, - HashSet::from([Protocol::TCP, Protocol::ICMP, Protocol::ARP]) - ); - sniffer.update(Message::ProtocolSelection(Protocol::TCP, false)); - assert_eq!( - sniffer.filters.protocols, - HashSet::from([Protocol::ICMP, Protocol::ARP]) - ); - sniffer.update(Message::ProtocolSelection(Protocol::ICMP, false)); - assert_eq!(sniffer.filters.protocols, HashSet::from([Protocol::ARP])); - sniffer.update(Message::ProtocolSelection(Protocol::ARP, false)); - assert_eq!(sniffer.filters.protocols, HashSet::new()); - sniffer.update(Message::ProtocolSelection(Protocol::UDP, true)); - assert_eq!(sniffer.filters.protocols, HashSet::from([Protocol::UDP])); - } - #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_update_chart_kind() { diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 38a6f581..c34b328d 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -1500,7 +1500,7 @@ fn test_get_service_different_tcp_udp() { #[test] fn test_get_service_not_applicable() { - for p in Protocol::ALL { + for p in [Protocol::TCP, Protocol::UDP, Protocol::ICMP, Protocol::ARP] { for d in [TrafficDirection::Incoming, TrafficDirection::Outgoing] { for (p1, p2) in [(None, Some(443)), (None, None), (Some(443), None)] { let key = AddressPortPair::new( diff --git a/src/networking/types/ip_version.rs b/src/networking/types/ip_version.rs index c8de3a6f..0ec6928b 100644 --- a/src/networking/types/ip_version.rs +++ b/src/networking/types/ip_version.rs @@ -14,25 +14,3 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{self:?}") } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ip_version_display() { - for version in IpVersion::ALL { - match version { - IpVersion::IPv4 => assert_eq!(version.to_string(), "IPv4"), - IpVersion::IPv6 => assert_eq!(version.to_string(), "IPv6"), - } - } - } - - #[test] - fn test_all_ip_versions_collection() { - assert_eq!(IpVersion::ALL.len(), 2); - assert_eq!(IpVersion::ALL.get(0).unwrap(), &IpVersion::IPv4); - assert_eq!(IpVersion::ALL.get(1).unwrap(), &IpVersion::IPv6); - } -} diff --git a/src/networking/types/protocol.rs b/src/networking/types/protocol.rs index 00813df5..bef8c720 100644 --- a/src/networking/types/protocol.rs +++ b/src/networking/types/protocol.rs @@ -19,29 +19,3 @@ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{self:?}") } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_protocol_display() { - for protocol in Protocol::ALL { - match protocol { - Protocol::TCP => assert_eq!(protocol.to_string(), "TCP"), - Protocol::UDP => assert_eq!(protocol.to_string(), "UDP"), - Protocol::ICMP => assert_eq!(protocol.to_string(), "ICMP"), - Protocol::ARP => assert_eq!(protocol.to_string(), "ARP"), - } - } - } - - #[test] - fn test_all_protocols_collection() { - assert_eq!(Protocol::ALL.len(), 4); - assert_eq!(Protocol::ALL.get(0).unwrap(), &Protocol::TCP); - assert_eq!(Protocol::ALL.get(1).unwrap(), &Protocol::UDP); - assert_eq!(Protocol::ALL.get(2).unwrap(), &Protocol::ICMP); - assert_eq!(Protocol::ALL.get(3).unwrap(), &Protocol::ARP); - } -} From b1cf5233485c1c805386e2118f25144573ea7b61 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 17 Aug 2025 18:06:11 +0200 Subject: [PATCH 49/74] update CHANGELOG --- CHANGELOG.md | 1 + src/gui/pages/overview_page.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 817b798d..2a67adb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # Changelog All Sniffnet releases with the relative changes are documented in this file. ## [UNRELEASED] +- Enhanced traffic filtering capabilities: Berkeley Packet Filter ([#937](https://github.com/GyulyVGC/sniffnet/pull/937) — fixes [#810](https://github.com/GyulyVGC/sniffnet/issues/810)) - Added _bits_ data representation ([#936](https://github.com/GyulyVGC/sniffnet/pull/936) — fixes [#506](https://github.com/GyulyVGC/sniffnet/issues/506)) - An AppImage of Sniffnet is now available ([#859](https://github.com/GyulyVGC/sniffnet/pull/859) — fixes [#900](https://github.com/GyulyVGC/sniffnet/issues/900)) - Added Dutch translation 🇳🇱 ([#854](https://github.com/GyulyVGC/sniffnet/pull/854)) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index c094ba84..741c9a8e 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -506,7 +506,7 @@ fn col_device<'a>( let filters_desc: Element = if filters.is_some_filter_active() { Row::new() .spacing(10) - .push(Text::new("BPF")) + .push(Text::new("BPF").font(font)) .push(get_info_tooltip(filters.bpf().to_string(), font)) .into() } else { From 7449a7315ae641ba2ba6932313312958a2f551c5 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 18 Aug 2025 23:55:04 +0200 Subject: [PATCH 50/74] fix donut chart dropped data color and legend --- src/chart/types/donut_chart.rs | 7 +------ src/chart/types/traffic_chart.rs | 8 ++++---- src/gui/pages/overview_page.rs | 2 +- src/gui/pages/thumbnail_page.rs | 4 ++-- src/gui/sniffer.rs | 2 +- src/gui/styles/donut.rs | 4 +--- src/gui/styles/rule.rs | 2 +- src/networking/types/info_traffic.rs | 2 +- 8 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/chart/types/donut_chart.rs b/src/chart/types/donut_chart.rs index f2bac3bd..14566466 100644 --- a/src/chart/types/donut_chart.rs +++ b/src/chart/types/donut_chart.rs @@ -96,12 +96,7 @@ fn draw( let radius = (frame.width().min(frame.height()) / 2.0) * 0.9; let style = ::style(theme, &::default()); - let colors = [ - style.incoming, - style.outgoing, - style.filtered_out, - style.dropped, - ]; + let colors = [style.incoming, style.outgoing, style.dropped]; for ((start_angle, end_angle), color) in self.angles().into_iter().zip(colors) { let path = canvas::Path::new(|builder| { diff --git a/src/chart/types/traffic_chart.rs b/src/chart/types/traffic_chart.rs index 3b41d2be..ccb7efad 100644 --- a/src/chart/types/traffic_chart.rs +++ b/src/chart/types/traffic_chart.rs @@ -27,13 +27,13 @@ pub struct TrafficChart { /// Current time interval number pub ticks: u32, - /// Sent bytes filtered and their time occurrence + /// Sent bytes and their time occurrence pub out_bytes: ChartSeries, - /// Received bytes filtered and their time occurrence + /// Received bytes and their time occurrence pub in_bytes: ChartSeries, - /// Sent packets filtered and their time occurrence + /// Sent packets and their time occurrence pub out_packets: ChartSeries, - /// Received packets filtered and their time occurrence + /// Received packets and their time occurrence pub in_packets: ChartSeries, /// Minimum number of bytes per time interval (computed on last 30 intervals) pub min_bytes: f32, diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 741c9a8e..9d08a2b4 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -1,7 +1,7 @@ //! Module defining the run page of the application. //! //! It contains elements to display traffic statistics: chart, detailed connections data -//! and overall statistics about the filtered traffic. +//! and overall statistics about the traffic. use crate::chart::types::donut_chart::donut_chart; use crate::countries::country_utils::get_flag_tooltip; diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs index 8935bf0e..590e602f 100644 --- a/src/gui/pages/thumbnail_page.rs +++ b/src/gui/pages/thumbnail_page.rs @@ -27,12 +27,12 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let ConfigSettings { style, .. } = sniffer.configs.settings; let font = style.get_extension().font; - let filtered = sniffer + let tot_packets = sniffer .info_traffic .tot_data_info .tot_data(DataRepr::Packets); - if filtered == 0 { + if tot_packets == 0 { return Container::new( Column::new() .push(vertical_space()) diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 3f830a48..f23684f0 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -940,7 +940,7 @@ fn switch_page(&mut self, next: bool) { ) => { // Running with no overlays if self.info_traffic.tot_data_info.tot_data(DataRepr::Packets) > 0 { - // Running with no overlays and some packets filtered + // Running with no overlays and some packets self.running_page = if next { self.running_page.next() } else { diff --git a/src/gui/styles/donut.rs b/src/gui/styles/donut.rs index 1cb768f8..a63b0251 100644 --- a/src/gui/styles/donut.rs +++ b/src/gui/styles/donut.rs @@ -25,8 +25,7 @@ fn active(&self, style: &StyleType) -> Style { incoming: colors.secondary, outgoing: colors.outgoing, text_color: colors.text_body, - filtered_out: ext.buttons_color, - dropped: ext.red_alert_color, + dropped: ext.buttons_color, } } } @@ -48,7 +47,6 @@ pub struct Style { pub(crate) text_color: Color, pub(crate) incoming: Color, pub(crate) outgoing: Color, - pub(crate) filtered_out: Color, pub(crate) dropped: Color, } diff --git a/src/gui/styles/rule.rs b/src/gui/styles/rule.rs index 13eb0ba8..f197c6fb 100644 --- a/src/gui/styles/rule.rs +++ b/src/gui/styles/rule.rs @@ -25,7 +25,7 @@ fn appearance(&self, style: &StyleType) -> Style { RuleType::Incoming => colors.secondary, RuleType::Outgoing => colors.outgoing, RuleType::PaletteColor(color, _) => *color, - RuleType::Dropped => ext.red_alert_color, + RuleType::Dropped => ext.buttons_color, RuleType::Standard => Color { a: ext.alpha_round_borders, ..ext.buttons_color diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index 14e021a5..4d9d16f4 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -17,7 +17,7 @@ pub struct InfoTraffic { pub dropped_packets: u32, /// Timestamp of the latest parsed packet pub last_packet_timestamp: Timestamp, - /// Map of the filtered traffic + /// Map of the traffic pub map: HashMap, /// Map of the upper layer services with their data info pub services: HashMap, From 8d162ed3f0b5ffebbada2ced846c038aec8d6b19 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 18 Aug 2025 23:58:06 +0200 Subject: [PATCH 51/74] rename AddressCollection struct to IpCollection --- src/networking/types/bogon.rs | 50 +++++++++---------- src/networking/types/ip_collection.rs | 72 +++++++++++++-------------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/networking/types/bogon.rs b/src/networking/types/bogon.rs index e54fe507..d21fa260 100644 --- a/src/networking/types/bogon.rs +++ b/src/networking/types/bogon.rs @@ -1,20 +1,20 @@ -use crate::networking::types::ip_collection::AddressCollection; +use crate::networking::types::ip_collection::IpCollection; use std::net::IpAddr; pub struct Bogon { - pub range: AddressCollection, + pub range: IpCollection, pub description: &'static str, } // IPv4 bogons static THIS_NETWORK: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("0.0.0.0-0.255.255.255").unwrap(), + range: IpCollection::new("0.0.0.0-0.255.255.255").unwrap(), description: "\"this\" network", }); static PRIVATE_USE: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new( + range: IpCollection::new( "10.0.0.0-10.255.255.255, 172.16.0.0-172.31.255.255, 192.168.0.0-192.168.255.255", ) .unwrap(), @@ -22,112 +22,112 @@ pub struct Bogon { }); static CARRIER_GRADE: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("100.64.0.0-100.127.255.255").unwrap(), + range: IpCollection::new("100.64.0.0-100.127.255.255").unwrap(), description: "carrier-grade NAT", }); static LOOPBACK: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("127.0.0.0-127.255.255.255").unwrap(), + range: IpCollection::new("127.0.0.0-127.255.255.255").unwrap(), description: "loopback", }); static LINK_LOCAL: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("169.254.0.0-169.254.255.255").unwrap(), + range: IpCollection::new("169.254.0.0-169.254.255.255").unwrap(), description: "link local", }); static IETF_PROTOCOL: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("192.0.0.0-192.0.0.255").unwrap(), + range: IpCollection::new("192.0.0.0-192.0.0.255").unwrap(), description: "IETF protocol assignments", }); static TEST_NET_1: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("192.0.2.0-192.0.2.255").unwrap(), + range: IpCollection::new("192.0.2.0-192.0.2.255").unwrap(), description: "TEST-NET-1", }); static NETWORK_INTERCONNECT: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("198.18.0.0-198.19.255.255").unwrap(), + range: IpCollection::new("198.18.0.0-198.19.255.255").unwrap(), description: "network interconnect device benchmark testing", }); static TEST_NET_2: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("198.51.100.0-198.51.100.255").unwrap(), + range: IpCollection::new("198.51.100.0-198.51.100.255").unwrap(), description: "TEST-NET-2", }); static TEST_NET_3: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("203.0.113.0-203.0.113.255").unwrap(), + range: IpCollection::new("203.0.113.0-203.0.113.255").unwrap(), description: "TEST-NET-3", }); static MULTICAST: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("224.0.0.0-239.255.255.255").unwrap(), + range: IpCollection::new("224.0.0.0-239.255.255.255").unwrap(), description: "multicast", }); static FUTURE_USE: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("240.0.0.0-255.255.255.255").unwrap(), + range: IpCollection::new("240.0.0.0-255.255.255.255").unwrap(), description: "future use", }); // IPv6 bogons static NODE_SCOPE_UNSPECIFIED: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("::").unwrap(), + range: IpCollection::new("::").unwrap(), description: "node-scope unicast unspecified", }); static NODE_SCOPE_LOOPBACK: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("::1").unwrap(), + range: IpCollection::new("::1").unwrap(), description: "node-scope unicast loopback", }); static IPV4_MAPPED: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("::ffff:0.0.0.0-::ffff:255.255.255.255").unwrap(), + range: IpCollection::new("::ffff:0.0.0.0-::ffff:255.255.255.255").unwrap(), description: "IPv4-mapped", }); static IPV4_COMPATIBLE: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("::-::255.255.255.255").unwrap(), + range: IpCollection::new("::-::255.255.255.255").unwrap(), description: "IPv4-compatible", }); static REMOTELY_TRIGGERED: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("100::-100::ffff:ffff:ffff:ffff").unwrap(), + range: IpCollection::new("100::-100::ffff:ffff:ffff:ffff").unwrap(), description: "remotely triggered black hole", }); static ORCHID: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("2001:10::-2001:1f:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), + range: IpCollection::new("2001:10::-2001:1f:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), description: "ORCHID", }); static DOCUMENTATION_PREFIX: std::sync::LazyLock = std::sync::LazyLock::new(|| { Bogon { - range: AddressCollection::new("2001:db8::-2001:db8:ffff:ffff:ffff:ffff:ffff:ffff, 3fff::-3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff") + range: IpCollection::new("2001:db8::-2001:db8:ffff:ffff:ffff:ffff:ffff:ffff, 3fff::-3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff") .unwrap(), description: "documentation prefix", } }); static ULA: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), + range: IpCollection::new("fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), description: "ULA", }); static LINK_LOCAL_UNICAST: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), + range: IpCollection::new("fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), description: "link-local unicast", }); static SITE_LOCAL_UNICAST: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("fec0::-feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), + range: IpCollection::new("fec0::-feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), description: "site-local unicast", }); static MULTICAST_V6: std::sync::LazyLock = std::sync::LazyLock::new(|| Bogon { - range: AddressCollection::new("ff00::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), + range: IpCollection::new("ff00::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap(), description: "multicast v6", }); diff --git a/src/networking/types/ip_collection.rs b/src/networking/types/ip_collection.rs index 77e57b09..09b0d327 100644 --- a/src/networking/types/ip_collection.rs +++ b/src/networking/types/ip_collection.rs @@ -3,12 +3,12 @@ use std::str::FromStr; #[derive(Debug, Eq, PartialEq, Clone)] -pub(crate) struct AddressCollection { +pub(crate) struct IpCollection { ips: Vec, ranges: Vec>, } -impl AddressCollection { +impl IpCollection { const SEPARATOR: char = ','; const RANGE_SEPARATOR: char = '-'; @@ -59,9 +59,9 @@ pub(crate) fn contains(&self, ip: &IpAddr) -> bool { } } -impl Default for AddressCollection { +impl Default for IpCollection { fn default() -> Self { - AddressCollection { + IpCollection { ips: vec![], ranges: vec![ RangeInclusive::new( @@ -86,11 +86,11 @@ mod tests { use std::ops::RangeInclusive; use std::str::FromStr; - use crate::networking::types::ip_collection::AddressCollection; + use crate::networking::types::ip_collection::IpCollection; #[test] fn test_default_collection_contains_everything() { - let collection = AddressCollection::default(); + let collection = IpCollection::default(); assert!(collection.contains(&IpAddr::from_str("1.1.1.1").unwrap())); assert!(collection.contains(&IpAddr::from_str("0.0.0.0").unwrap())); assert!(collection.contains(&IpAddr::from_str("255.255.255.255").unwrap())); @@ -113,8 +113,8 @@ fn test_default_collection_contains_everything() { #[test] fn test_new_collections_1() { assert_eq!( - AddressCollection::new("1.1.1.1,2.2.2.2").unwrap(), - AddressCollection { + IpCollection::new("1.1.1.1,2.2.2.2").unwrap(), + IpCollection { ips: vec![ IpAddr::from_str("1.1.1.1").unwrap(), IpAddr::from_str("2.2.2.2").unwrap() @@ -124,11 +124,9 @@ fn test_new_collections_1() { ); assert_eq!( - AddressCollection::new( - "1.1.1.1, 2.2.2.2, 3.3.3.3 - 5.5.5.5, 10.0.0.1-10.0.0.255,9.9.9.9", - ) - .unwrap(), - AddressCollection { + IpCollection::new("1.1.1.1, 2.2.2.2, 3.3.3.3 - 5.5.5.5, 10.0.0.1-10.0.0.255,9.9.9.9",) + .unwrap(), + IpCollection { ips: vec![ IpAddr::from_str("1.1.1.1").unwrap(), IpAddr::from_str("2.2.2.2").unwrap(), @@ -148,8 +146,8 @@ fn test_new_collections_1() { ); assert_eq!( - AddressCollection::new(" aaaa::ffff,bbbb::1-cccc::2").unwrap(), - AddressCollection { + IpCollection::new(" aaaa::ffff,bbbb::1-cccc::2").unwrap(), + IpCollection { ips: vec![IpAddr::from_str("aaaa::ffff").unwrap(),], ranges: vec![RangeInclusive::new( IpAddr::from_str("bbbb::1").unwrap(), @@ -162,8 +160,8 @@ fn test_new_collections_1() { #[test] fn test_new_collections_2() { assert_eq!( - AddressCollection::new("1.1.1.1,2.2.2.2, 8.8.8.8 ").unwrap(), - AddressCollection { + IpCollection::new("1.1.1.1,2.2.2.2, 8.8.8.8 ").unwrap(), + IpCollection { ips: vec![ IpAddr::from_str("1.1.1.1").unwrap(), IpAddr::from_str("2.2.2.2").unwrap(), @@ -174,8 +172,8 @@ fn test_new_collections_2() { ); assert_eq!( - AddressCollection::new(" 1.1.1.1 -1.1.1.1").unwrap(), - AddressCollection { + IpCollection::new(" 1.1.1.1 -1.1.1.1").unwrap(), + IpCollection { ips: vec![], ranges: vec![RangeInclusive::new( IpAddr::from_str("1.1.1.1").unwrap(), @@ -185,9 +183,9 @@ fn test_new_collections_2() { ); assert_eq!( - AddressCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9",) + IpCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9",) .unwrap(), - AddressCollection { + IpCollection { ips: vec![ IpAddr::from_str("1.1.1.1").unwrap(), IpAddr::from_str("2.2.2.2").unwrap(), @@ -207,8 +205,8 @@ fn test_new_collections_2() { ); assert_eq!( - AddressCollection::new("aaaa::ffff,bbbb::1-cccc::2,ff::dd").unwrap(), - AddressCollection { + IpCollection::new("aaaa::ffff,bbbb::1-cccc::2,ff::dd").unwrap(), + IpCollection { ips: vec![ IpAddr::from_str("aaaa::ffff").unwrap(), IpAddr::from_str("ff::dd").unwrap() @@ -224,32 +222,32 @@ fn test_new_collections_2() { #[test] fn test_new_collections_invalid() { assert_eq!( - AddressCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9"), + IpCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9"), None ); assert_eq!( - AddressCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1:10.0.0.255,9.9.9.9"), + IpCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1:10.0.0.255,9.9.9.9"), None ); - assert_eq!(AddressCollection::new("1.1.1.1-aa::ff"), None); + assert_eq!(IpCollection::new("1.1.1.1-aa::ff"), None); - assert_eq!(AddressCollection::new("aa::ff-1.1.1.1"), None); + assert_eq!(IpCollection::new("aa::ff-1.1.1.1"), None); - assert_eq!(AddressCollection::new("aa::ff-aa::ee"), None); + assert_eq!(IpCollection::new("aa::ff-aa::ee"), None); - assert_eq!(AddressCollection::new("1.1.1.1-1.1.0.1"), None); + assert_eq!(IpCollection::new("1.1.1.1-1.1.0.1"), None); - assert_eq!(AddressCollection::new("1.1.1.1-2.2.2.2-3.3.3.3"), None); + assert_eq!(IpCollection::new("1.1.1.1-2.2.2.2-3.3.3.3"), None); - assert_eq!(AddressCollection::new("1.1.1.1-2.2.2.2-"), None); + assert_eq!(IpCollection::new("1.1.1.1-2.2.2.2-"), None); } #[test] fn test_ip_collection_contains() { let collection = - AddressCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9") + IpCollection::new("1.1.1.1,2.2.2.2,3.3.3.3-5.5.5.5,10.0.0.1-10.0.0.255,9.9.9.9") .unwrap(); assert!(collection.contains(&IpAddr::from_str("1.1.1.1").unwrap())); assert!(collection.contains(&IpAddr::from_str("2.2.2.2").unwrap())); @@ -265,12 +263,12 @@ fn test_ip_collection_contains() { assert!(!collection.contains(&IpAddr::from_str("9.9.9.10").unwrap())); assert!(!collection.contains(&IpAddr::from_str("3.3.3.2").unwrap())); - let collection_2 = AddressCollection::new("1.1.1.0-1.1.9.0").unwrap(); + let collection_2 = IpCollection::new("1.1.1.0-1.1.9.0").unwrap(); assert!(!collection_2.contains(&IpAddr::from_str("1.1.100.5").unwrap())); assert!(collection_2.contains(&IpAddr::from_str("1.1.3.255").unwrap())); // check that ipv4 range doesn't contain ipv6 - let collection_3 = AddressCollection::new("0.0.0.0-255.255.255.255").unwrap(); + let collection_3 = IpCollection::new("0.0.0.0-255.255.255.255").unwrap(); assert!(!collection_3.contains(&IpAddr::from_str("::").unwrap())); assert!(!collection_3.contains(&IpAddr::from_str("1111::2222").unwrap())); } @@ -278,7 +276,7 @@ fn test_ip_collection_contains() { #[test] fn test_ip_collection_contains_ipv6() { let collection = - AddressCollection::new( "2001:db8:1234:0000:0000:0000:0000:0000-2001:db8:1234:ffff:ffff:ffff:ffff:ffff,daa::aad,caa::aac").unwrap(); + IpCollection::new( "2001:db8:1234:0000:0000:0000:0000:0000-2001:db8:1234:ffff:ffff:ffff:ffff:ffff,daa::aad,caa::aac").unwrap(); assert!( collection .contains(&IpAddr::from_str("2001:db8:1234:0000:0000:0000:0000:0000").unwrap()) @@ -312,14 +310,14 @@ fn test_ip_collection_contains_ipv6() { assert!(!collection.contains(&IpAddr::from_str("da::aad").unwrap())); assert!(!collection.contains(&IpAddr::from_str("caa::aab").unwrap())); - let collection_2 = AddressCollection::new("aa::bb-aa:1::00").unwrap(); + let collection_2 = IpCollection::new("aa::bb-aa:1::00").unwrap(); assert!(!collection_2.contains(&IpAddr::from_str("aa:11::0").unwrap())); assert!(collection_2.contains(&IpAddr::from_str("aa::bc").unwrap())); assert!(collection_2.contains(&IpAddr::from_str("aa::bbcc").unwrap())); assert!(collection_2.contains(&IpAddr::from_str("00aa:0001::00").unwrap())); // check that ipv6 range doesn't contain ipv4 - let collection_3 = AddressCollection::new("0000::0000-ffff::8888").unwrap(); + let collection_3 = IpCollection::new("0000::0000-ffff::8888").unwrap(); assert!(!collection_3.contains(&IpAddr::from_str("192.168.1.1").unwrap())); assert!(!collection_3.contains(&IpAddr::from_str("0.0.0.0").unwrap())); } From 2929bf1bbc64b41f1a1c3dab117d47ddad8720ca Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 22 Aug 2025 00:42:49 +0200 Subject: [PATCH 52/74] create new Conf struct to accomodate parameters to be persisted (WIP) --- src/cli/mod.rs | 4 +- src/configs/types/configs.rs | 8 +- src/configs/types/mod.rs | 3 - src/gui/components/header.rs | 8 +- src/gui/pages/connection_details_page.rs | 11 +- src/gui/pages/initial_page.rs | 19 +- src/gui/pages/inspect_page.rs | 13 +- src/gui/pages/notifications_page.rs | 11 +- src/gui/pages/overview_page.rs | 44 ++-- src/gui/pages/settings_general_page.rs | 11 +- src/gui/pages/settings_notifications_page.rs | 7 +- src/gui/pages/settings_style_page.rs | 7 +- src/gui/pages/thumbnail_page.rs | 8 +- src/gui/sniffer.rs | 207 +++++++----------- src/gui/types/conf.rs | 36 +++ src/{configs => gui}/types/config_window.rs | 0 src/gui/types/mod.rs | 3 + .../types/settings.rs} | 33 ++- src/main.rs | 4 +- .../types/config_device.rs | 5 +- src/networking/types/mod.rs | 1 + src/report/get_report_entries.rs | 2 +- 22 files changed, 226 insertions(+), 219 deletions(-) create mode 100644 src/gui/types/conf.rs rename src/{configs => gui}/types/config_window.rs (100%) rename src/{configs/types/config_settings.rs => gui/types/settings.rs} (67%) rename src/{configs => networking}/types/config_device.rs (94%) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 0256167c..68e0375a 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -76,7 +76,7 @@ mod tests { use crate::gui::styles::types::custom_palette::ExtraStyles; use crate::gui::styles::types::gradient_type::GradientType; use crate::notifications::types::notifications::Notifications; - use crate::{ConfigDevice, ConfigSettings, ConfigWindow, Language, Sniffer, StyleType}; + use crate::{ConfigDevice, ConfigWindow, Language, Settings, Sniffer, StyleType}; use super::*; @@ -86,7 +86,7 @@ fn test_restore_default_configs() { // initial configs stored are the default ones assert_eq!(Configs::load(), Configs::default()); let modified_configs = Configs { - settings: ConfigSettings { + settings: Settings { color_gradient: GradientType::Wild, language: Language::ZH, scale_factor: 0.65, diff --git a/src/configs/types/configs.rs b/src/configs/types/configs.rs index db66fd88..6cc15345 100644 --- a/src/configs/types/configs.rs +++ b/src/configs/types/configs.rs @@ -1,11 +1,13 @@ -use crate::{ConfigDevice, ConfigSettings, ConfigWindow}; +use crate::ConfigWindow; +use crate::gui::types::settings::Settings; +use crate::networking::types::config_device::ConfigDevice; use confy::ConfyError; pub static CONFIGS: std::sync::LazyLock = std::sync::LazyLock::new(Configs::load); #[derive(Default, Clone, PartialEq, Debug)] pub struct Configs { - pub settings: ConfigSettings, + pub settings: Settings, pub device: ConfigDevice, pub window: ConfigWindow, } @@ -15,7 +17,7 @@ impl Configs { /// use `CONFIGS` instead to access the initial instance pub fn load() -> Self { Configs { - settings: ConfigSettings::load(), + settings: Settings::load(), device: ConfigDevice::load(), window: ConfigWindow::load(), } diff --git a/src/configs/types/mod.rs b/src/configs/types/mod.rs index 76d46b33..3810d5b3 100644 --- a/src/configs/types/mod.rs +++ b/src/configs/types/mod.rs @@ -1,4 +1 @@ -pub mod config_device; -pub mod config_settings; -pub mod config_window; pub mod configs; diff --git a/src/gui/components/header.rs b/src/gui/components/header.rs index af7ad3f0..fd087efd 100644 --- a/src/gui/components/header.rs +++ b/src/gui/components/header.rs @@ -5,7 +5,6 @@ use iced::widget::{Container, Row, Space, Text, Tooltip, button, horizontal_space}; use iced::{Alignment, Font, Length}; -use crate::configs::types::config_settings::ConfigSettings; use crate::gui::components::tab::notifications_badge; use crate::gui::pages::types::running_page::RunningPage; use crate::gui::pages::types::settings_page::SettingsPage; @@ -14,6 +13,7 @@ use crate::gui::styles::container::ContainerType; use crate::gui::styles::types::gradient_type::GradientType; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::translations::translations::{quit_analysis_translation, settings_translation}; use crate::translations::translations_3::thumbnail_mode_translation; use crate::utils::types::icon::Icon; @@ -21,12 +21,12 @@ pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let thumbnail = sniffer.thumbnail; - let ConfigSettings { + let Settings { style, language, color_gradient, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; if thumbnail { @@ -41,7 +41,7 @@ pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { ); } - let last_opened_setting = sniffer.last_opened_setting; + let last_opened_setting = sniffer.conf.last_opened_setting; let is_running = sniffer.running_page.ne(&RunningPage::Init); let logo = Icon::Sniffnet diff --git a/src/gui/pages/connection_details_page.rs b/src/gui/pages/connection_details_page.rs index 1bd0a2f6..f26a53d4 100644 --- a/src/gui/pages/connection_details_page.rs +++ b/src/gui/pages/connection_details_page.rs @@ -8,6 +8,7 @@ use crate::gui::styles::text::TextType; use crate::gui::styles::types::gradient_type::GradientType; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::gui::types::timing_events::TimingEvents; use crate::networking::manage_packets::{ get_address_to_lookup, get_traffic_type, is_local_connection, is_my_address, @@ -34,7 +35,7 @@ }; use crate::utils::formatted_strings::{get_formatted_timestamp, get_socket_address}; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, Language, Protocol, Sniffer, StyleType}; +use crate::{Language, Protocol, Sniffer, StyleType}; use iced::alignment::Vertical; use iced::widget::scrollable::Direction; use iced::widget::tooltip::Position; @@ -50,12 +51,12 @@ pub fn connection_details_page( } fn page_content<'a>(sniffer: &Sniffer, key: &AddressPortPair) -> Container<'a, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, color_gradient, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let data_repr = sniffer.traffic_chart.data_repr; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -299,9 +300,9 @@ fn get_local_tooltip<'a>( address_to_lookup: &IpAddr, key: &AddressPortPair, ) -> Tooltip<'a, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let local_address = if address_to_lookup.eq(&key.address1) { &key.address2 diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index b079c6fe..5294355d 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -15,6 +15,7 @@ use crate::gui::types::export_pcap::ExportPcap; use crate::gui::types::filters::Filters; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::networking::types::capture_context::{CaptureSource, CaptureSourcePicklist}; use crate::translations::translations::{ address_translation, addresses_translation, network_adapter_translation, start_translation, @@ -27,7 +28,7 @@ use crate::utils::formatted_strings::get_path_termination_string; use crate::utils::types::file_info::FileInfo; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, Language, StyleType}; +use crate::{Language, StyleType}; use iced::Length::FillPortion; use iced::widget::scrollable::Direction; use iced::widget::{ @@ -38,12 +39,12 @@ /// Computes the body of gui initial page pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, color_gradient, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -51,10 +52,10 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let col_checkboxes = Column::new() .spacing(10) - .push(get_filters_group(&sniffer.filters, font, language)) + .push(get_filters_group(&sniffer.conf.filters, font, language)) .push_maybe(get_export_pcap_group_maybe( - sniffer.capture_source_picklist, - &sniffer.export_pcap, + sniffer.conf.capture_source_picklist, + &sniffer.conf.export_pcap, language, font, )); @@ -113,7 +114,7 @@ fn get_col_data_source( font: Font, language: Language, ) -> Column<'_, Message, StyleType> { - let current_option = if sniffer.capture_source_picklist == CaptureSourcePicklist::Device { + let current_option = if sniffer.conf.capture_source_picklist == CaptureSourcePicklist::Device { network_adapter_translation(language) } else { capture_file_translation(language) @@ -153,7 +154,7 @@ fn get_col_data_source( .push(picklist), ); - match &sniffer.capture_source_picklist { + match &sniffer.conf.capture_source_picklist { CaptureSourcePicklist::Device => { col = col.push(get_col_adapter(sniffer, font, language)); } @@ -162,7 +163,7 @@ fn get_col_data_source( language, font, &sniffer.capture_source, - &sniffer.import_pcap_path, + &sniffer.conf.import_pcap_path, )); } } diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index 961880da..5c78278c 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -21,6 +21,7 @@ use crate::gui::styles::text::TextType; use crate::gui::styles::text_input::TextInputType; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_representation::DataRepr; @@ -36,13 +37,13 @@ }; use crate::translations::translations_3::filter_by_host_translation; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, Language, ReportSortType, RunningPage, Sniffer, StyleType}; +use crate::{Language, ReportSortType, RunningPage, Sniffer, StyleType}; /// Computes the body of gui inspect page pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -74,7 +75,7 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { language, &sniffer.search, font, - sniffer.report_sort_type, + sniffer.conf.report_sort_type, sniffer.traffic_chart.data_repr, )) .push(Space::with_height(4)) @@ -105,9 +106,9 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { } fn report<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let data_repr = sniffer.traffic_chart.data_repr; let font = style.get_extension().font; diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 3e9914c8..3c92f6d7 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -10,6 +10,7 @@ use crate::gui::styles::style_constants::FONT_SIZE_FOOTER; use crate::gui::styles::text::TextType; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::data_representation::DataRepr; @@ -26,7 +27,7 @@ threshold_translation, }; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, Language, RunningPage, Sniffer, StyleType}; +use crate::{Language, RunningPage, Sniffer, StyleType}; use iced::Length::FillPortion; use iced::widget::scrollable::Direction; use iced::widget::text::LineHeight; @@ -38,12 +39,12 @@ /// Computes the body of gui notifications page pub fn notifications_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, notifications, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -282,9 +283,9 @@ fn get_button_clear_all<'a>(font: Font, language: Language) -> Tooltip<'a, Messa } fn logged_notifications<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let data_repr = sniffer.traffic_chart.data_repr; let font = style.get_extension().font; let mut ret_val = Column::new() diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 9d08a2b4..d433d3e3 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -17,6 +17,7 @@ use crate::gui::styles::types::palette_extension::PaletteExtension; use crate::gui::types::filters::Filters; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::networking::types::capture_context::CaptureSource; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_info_host::DataInfoHost; @@ -37,7 +38,7 @@ use crate::translations::translations_3::{service_translation, unsupported_link_type_translation}; use crate::translations::translations_4::reading_from_pcap_translation; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, Language, RunningPage, StyleType}; +use crate::{Language, RunningPage, StyleType}; use iced::Length::{Fill, FillPortion}; use iced::alignment::{Horizontal, Vertical}; use iced::widget::scrollable::Direction; @@ -52,9 +53,9 @@ /// Computes the body of gui overview page pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -210,16 +211,20 @@ fn row_report<'a>(sniffer: &Sniffer) -> Row<'a, Message, StyleType> { } fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let data_repr = sniffer.traffic_chart.data_repr; let mut scroll_host = Column::new() .padding(Padding::ZERO.right(11.0)) .align_x(Alignment::Center); - let entries = get_host_entries(&sniffer.info_traffic, data_repr, sniffer.host_sort_type); + let entries = get_host_entries( + &sniffer.info_traffic, + data_repr, + sniffer.conf.host_sort_type, + ); let first_entry_data_info = entries .iter() .map(|(_, d)| d.data_info) @@ -273,7 +278,7 @@ fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { ) .push(horizontal_space()) .push(sort_arrows( - sniffer.host_sort_type, + sniffer.conf.host_sort_type, Message::HostSortSelection, )), ) @@ -287,16 +292,20 @@ fn col_host<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { } fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let data_repr = sniffer.traffic_chart.data_repr; let mut scroll_service = Column::new() .padding(Padding::ZERO.right(11.0)) .align_x(Alignment::Center); - let entries = get_service_entries(&sniffer.info_traffic, data_repr, sniffer.service_sort_type); + let entries = get_service_entries( + &sniffer.info_traffic, + data_repr, + sniffer.conf.service_sort_type, + ); let first_entry_data_info = entries .iter() .map(|&(_, d)| d) @@ -337,7 +346,7 @@ fn col_service<'a>(sniffer: &Sniffer) -> Column<'a, Message, StyleType> { ) .push(horizontal_space()) .push(sort_arrows( - sniffer.service_sort_type, + sniffer.conf.service_sort_type, Message::ServiceSortSelection, )), ) @@ -430,12 +439,17 @@ pub fn service_bar<'a>( } fn col_info(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let PaletteExtension { font, .. } = style.get_extension(); - let col_device = col_device(language, font, &sniffer.capture_source, &sniffer.filters); + let col_device = col_device( + language, + font, + &sniffer.capture_source, + &sniffer.conf.filters, + ); let col_data_representation = col_data_representation(language, font, sniffer.traffic_chart.data_repr); @@ -469,7 +483,7 @@ fn col_info(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { } fn container_chart(sniffer: &Sniffer, font: Font) -> Container<'_, Message, StyleType> { - let ConfigSettings { language, .. } = sniffer.configs.settings; + let Settings { language, .. } = sniffer.conf.settings; let traffic_chart = &sniffer.traffic_chart; Container::new( diff --git a/src/gui/pages/settings_general_page.rs b/src/gui/pages/settings_general_page.rs index deacf03c..e54a7a28 100644 --- a/src/gui/pages/settings_general_page.rs +++ b/src/gui/pages/settings_general_page.rs @@ -14,6 +14,7 @@ use crate::gui::styles::style_constants::FONT_SIZE_SUBTITLE; use crate::gui::styles::text::TextType; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders}; use crate::translations::translations::language_translation; use crate::translations::translations_2::country_translation; @@ -25,15 +26,15 @@ use crate::utils::types::file_info::FileInfo; use crate::utils::types::icon::Icon; use crate::utils::types::web_page::WebPage; -use crate::{ConfigSettings, Language, RunningPage, Sniffer, StyleType}; +use crate::{Language, RunningPage, Sniffer, StyleType}; pub fn settings_general_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, color_gradient, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -57,13 +58,13 @@ pub fn settings_general_page(sniffer: &Sniffer) -> Container<'_, Message, StyleT } fn column_all_general_setting(sniffer: &Sniffer, font: Font) -> Column<'_, Message, StyleType> { - let ConfigSettings { + let Settings { language, scale_factor, mmdb_country, mmdb_asn, .. - } = sniffer.configs.settings.clone(); + } = sniffer.conf.settings.clone(); let is_editable = sniffer.running_page.eq(&RunningPage::Init); diff --git a/src/gui/pages/settings_notifications_page.rs b/src/gui/pages/settings_notifications_page.rs index 1f70a25b..c1df9e9a 100644 --- a/src/gui/pages/settings_notifications_page.rs +++ b/src/gui/pages/settings_notifications_page.rs @@ -13,6 +13,7 @@ use crate::gui::styles::text::TextType; use crate::gui::styles::types::gradient_type::GradientType; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::networking::types::data_representation::DataRepr; use crate::notifications::types::notifications::{ DataNotification, FavoriteNotification, Notification, @@ -25,16 +26,16 @@ use crate::translations::translations_2::data_representation_translation; use crate::translations::translations_4::data_exceeded_translation; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, Language, Sniffer, StyleType}; +use crate::{Language, Sniffer, StyleType}; pub fn settings_notifications_page<'a>(sniffer: &Sniffer) -> Container<'a, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, color_gradient, mut notifications, .. - } = sniffer.configs.settings; + } = sniffer.conf.settings; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; diff --git a/src/gui/pages/settings_style_page.rs b/src/gui/pages/settings_style_page.rs index 0a8519ec..efbd68fe 100644 --- a/src/gui/pages/settings_style_page.rs +++ b/src/gui/pages/settings_style_page.rs @@ -19,22 +19,23 @@ use crate::gui::styles::types::palette::Palette; use crate::gui::styles::types::palette_extension::PaletteExtension; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::translations::translations::appearance_title_translation; use crate::translations::translations_2::color_gradients_translation; use crate::translations::translations_3::custom_style_translation; use crate::utils::formatted_strings::get_path_termination_string; use crate::utils::types::file_info::FileInfo; use crate::utils::types::icon::Icon; -use crate::{ConfigSettings, Language, Sniffer, StyleType}; +use crate::{Language, Sniffer, StyleType}; pub fn settings_style_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, color_gradient, style_path, .. - } = sniffer.configs.settings.clone(); + } = sniffer.conf.settings.clone(); let PaletteExtension { font, font_headers, .. } = style.get_extension(); diff --git a/src/gui/pages/thumbnail_page.rs b/src/gui/pages/thumbnail_page.rs index 590e602f..17914b48 100644 --- a/src/gui/pages/thumbnail_page.rs +++ b/src/gui/pages/thumbnail_page.rs @@ -5,12 +5,12 @@ use iced::{Alignment, Font, Length}; use crate::chart::types::donut_chart::donut_chart; -use crate::configs::types::config_settings::ConfigSettings; use crate::countries::country_utils::get_flag_tooltip; use crate::gui::sniffer::Sniffer; use crate::gui::styles::style_constants::FONT_SIZE_FOOTER; use crate::gui::styles::types::style_type::StyleType; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::{Host, ThumbnailHost}; use crate::networking::types::info_traffic::InfoTraffic; @@ -24,7 +24,7 @@ /// Computes the body of the thumbnail view pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { - let ConfigSettings { style, .. } = sniffer.configs.settings; + let Settings { style, .. } = sniffer.conf.settings; let font = style.get_extension().font; let tot_packets = sniffer @@ -75,14 +75,14 @@ pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { info_traffic, data_repr, font, - sniffer.host_sort_type, + sniffer.conf.host_sort_type, )) .push(Rule::vertical(10)) .push(service_col( info_traffic, data_repr, font, - sniffer.service_sort_type, + sniffer.conf.service_sort_type, )); let content = Column::new() diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index f23684f0..8da6a9be 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -17,9 +17,6 @@ use std::thread; use std::time::Duration; -use crate::configs::types::config_window::{ - ConfigWindow, PositionTuple, ScaleAndCheck, SizeTuple, ToPoint, ToSize, -}; use crate::gui::components::footer::footer; use crate::gui::components::header::header; use crate::gui::components::modal::{get_clear_all_overlay, get_exit_overlay, modal}; @@ -37,9 +34,12 @@ use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::types::custom_palette::{CustomPalette, ExtraStyles}; use crate::gui::styles::types::palette::Palette; -use crate::gui::types::export_pcap::ExportPcap; -use crate::gui::types::filters::Filters; +use crate::gui::types::conf::Conf; +use crate::gui::types::config_window::{ + ConfigWindow, PositionTuple, ScaleAndCheck, SizeTuple, ToPoint, ToSize, +}; use crate::gui::types::message::Message; +use crate::gui::types::settings::Settings; use crate::gui::types::timing_events::TimingEvents; use crate::mmdb::asn::ASN_MMDB; use crate::mmdb::country::COUNTRY_MMDB; @@ -59,25 +59,23 @@ use crate::notifications::types::notifications::{DataNotification, Notification}; use crate::notifications::types::sound::{Sound, play}; use crate::report::get_report_entries::get_searched_entries; -use crate::report::types::report_sort_type::ReportSortType; use crate::report::types::search_parameters::SearchParameters; -use crate::report::types::sort_type::SortType; use crate::translations::types::language::Language; use crate::utils::check_updates::set_newer_release_status; use crate::utils::error_logger::{ErrorLogger, Location}; use crate::utils::types::file_info::FileInfo; use crate::utils::types::web_page::WebPage; -use crate::{ConfigSettings, Configs, StyleType, TrafficChart, location}; +use crate::{StyleType, TrafficChart, location}; pub const FONT_FAMILY_NAME: &str = "Sarasa Mono SC for Sniffnet"; pub const ICON_FONT_FAMILY_NAME: &str = "Icons for Sniffnet"; -/// Struct on which the gui is based +/// Struct on which the GUI is based /// -/// It contains gui statuses and network traffic statistics +/// It carries statuses, network traffic statistics, and more pub struct Sniffer { - /// Application's configurations: settings, window properties, name of last device sniffed - pub configs: Configs, + /// Parameters that are persistent across runs + pub conf: Conf, /// Capture receiver clone (to close the channel after every run), with the current capture id (to ignore pending messages from previous captures) pub current_capture_rx: (usize, Option>), /// Capture data @@ -90,32 +88,21 @@ pub struct Sniffer { pub logged_notifications: (VecDeque, usize), /// Reports if a newer release of the software is available on GitHub pub newer_release_available: Option, - /// Capture source picklist, to select the source of the capture - pub capture_source_picklist: CaptureSourcePicklist, /// Network device to be analyzed, or PCAP file to be imported + /// TODO: Conf??? pub capture_source: CaptureSource, /// List of network devices pub my_devices: Vec, - /// BPF filter program to be applied to the capture - pub filters: Filters, /// Signals if a pcap error occurred pub pcap_error: Option, /// Messages status pub dots_pulse: (String, u8), /// Chart displayed pub traffic_chart: TrafficChart, - /// Report sort type (inspect page) - pub report_sort_type: ReportSortType, - /// Host sort type (overview page) - pub host_sort_type: SortType, - /// Service sort type (overview page) - pub service_sort_type: SortType, /// Currently displayed modal; None if no modal is displayed pub modal: Option, /// Currently displayed settings page; None if settings is closed pub settings_page: Option, - /// Remembers the last opened setting page - pub last_opened_setting: SettingsPage, /// Defines the current running page pub running_page: RunningPage, /// Number of unread notifications @@ -128,49 +115,39 @@ pub struct Sniffer { pub mmdb_readers: MmdbReaders, /// Time-related events pub timing_events: TimingEvents, - /// Information about PCAP file export - pub export_pcap: ExportPcap, /// Whether thumbnail mode is currently active pub thumbnail: bool, /// Window id pub id: Option, /// Host data for filter dropdowns (comboboxes) pub host_data_states: HostDataStates, - /// Import path for PCAP file - pub import_pcap_path: String, } impl Sniffer { - pub fn new(configs: Configs) -> Self { - let ConfigSettings { + pub fn new(conf: Conf) -> Self { + let Settings { style, language, mmdb_country, mmdb_asn, .. - } = configs.settings.clone(); - let device = configs.device.to_my_device(); + } = conf.settings.clone(); + let device = conf.device.to_my_device(); Self { - configs, + conf, current_capture_rx: (0, None), info_traffic: InfoTraffic::default(), addresses_resolved: HashMap::new(), favorite_hosts: HashSet::new(), logged_notifications: (VecDeque::new(), 0), newer_release_available: None, - capture_source_picklist: CaptureSourcePicklist::default(), capture_source: CaptureSource::Device(device), my_devices: Vec::new(), - filters: Filters::default(), pcap_error: None, dots_pulse: (".".to_string(), 0), traffic_chart: TrafficChart::new(style, language), - report_sort_type: ReportSortType::default(), - host_sort_type: SortType::default(), - service_sort_type: SortType::default(), modal: None, settings_page: None, - last_opened_setting: SettingsPage::Notifications, running_page: RunningPage::Init, unread_notifications: 0, search: SearchParameters::default(), @@ -180,11 +157,9 @@ pub fn new(configs: Configs) -> Self { asn: Arc::new(MmdbReader::from(&mmdb_asn, ASN_MMDB)), }, timing_events: TimingEvents::default(), - export_pcap: ExportPcap::default(), thumbnail: false, id: None, host_data_states: HostDataStates::default(), - import_pcap_path: String::new(), } } @@ -283,25 +258,25 @@ pub fn update(&mut self, message: Message) -> Task { } Message::DeviceSelection(name) => self.set_device(&name), Message::SetCaptureSource(cs_pick) => { - self.capture_source_picklist = cs_pick; + self.conf.capture_source_picklist = cs_pick; return if cs_pick == CaptureSourcePicklist::File { - Task::done(Message::SetPcapImport(self.import_pcap_path.clone())) + Task::done(Message::SetPcapImport(self.conf.import_pcap_path.clone())) } else { Task::done(Message::DeviceSelection( - self.configs.device.device_name.clone(), + self.conf.device.device_name.clone(), )) }; } Message::ToggleFilters => { - self.filters.toggle(); + self.conf.filters.toggle(); } Message::BpfFilter(value) => { - self.filters.set_bpf(value); + self.conf.filters.set_bpf(value); } Message::DataReprSelection(unit) => self.traffic_chart.change_kind(unit), Message::ReportSortSelection(sort) => { self.page_number = 1; - self.report_sort_type = sort; + self.conf.report_sort_type = sort; } Message::OpenWebPage(web_page) => Self::open_web(&web_page), Message::Start => { @@ -311,16 +286,16 @@ pub fn update(&mut self, message: Message) -> Task { } Message::Reset => self.reset(), Message::Style(style) => { - self.configs.settings.style = style; + self.conf.settings.style = style; self.traffic_chart.change_style(style); } Message::LoadStyle(path) => { - self.configs.settings.style_path.clone_from(&path); + self.conf.settings.style_path.clone_from(&path); if let Ok(palette) = Palette::from_file(path) { let style = StyleType::Custom(ExtraStyles::CustomToml( CustomPalette::from_palette(palette), )); - self.configs.settings.style = style; + self.conf.settings.style = style; self.traffic_chart.change_style(style); } } @@ -338,7 +313,7 @@ pub fn update(&mut self, message: Message) -> Task { } Message::OpenLastSettings => { if self.modal.is_none() && self.settings_page.is_none() { - self.settings_page = Some(self.last_opened_setting); + self.settings_page = Some(self.conf.last_opened_setting); } } Message::CloseSettings => self.close_settings(), @@ -349,7 +324,7 @@ pub fn update(&mut self, message: Message) -> Task { } } Message::LanguageSelection(language) => { - self.configs.settings.language = language; + self.conf.settings.language = language; self.traffic_chart.change_language(language); } Message::UpdateNotificationSettings(notification, emit_sound) => { @@ -357,7 +332,7 @@ pub fn update(&mut self, message: Message) -> Task { } Message::ChangeVolume(volume) => { play(Sound::Pop, volume); - self.configs.settings.notifications.volume = volume; + self.conf.settings.notifications.volume = volume; } Message::ClearAllNotifications => { self.logged_notifications.0 = VecDeque::new(); @@ -405,41 +380,40 @@ pub fn update(&mut self, message: Message) -> Task { } Message::WindowFocused => self.timing_events.focus_now(), Message::GradientsSelection(gradient_type) => { - self.configs.settings.color_gradient = gradient_type; + self.conf.settings.color_gradient = gradient_type; } Message::ChangeScaleFactor(slider_val) => { let scale_factor_str = format!("{:.1}", 3.0_f64.powf(slider_val)); - self.configs.settings.scale_factor = scale_factor_str.parse().unwrap_or(1.0); + self.conf.settings.scale_factor = scale_factor_str.parse().unwrap_or(1.0); } Message::WindowMoved(x, y) => { - let scale_factor = self.configs.settings.scale_factor; + let scale_factor = self.conf.settings.scale_factor; let scaled = PositionTuple(x, y).scale_and_check(scale_factor); if self.thumbnail { - self.configs.window.thumbnail_position = scaled; + self.conf.window.thumbnail_position = scaled; } else { - self.configs.window.position = scaled; + self.conf.window.position = scaled; } } Message::WindowResized(width, height) => { if !self.thumbnail { - let scale_factor = self.configs.settings.scale_factor; - self.configs.window.size = - SizeTuple(width, height).scale_and_check(scale_factor); + let scale_factor = self.conf.settings.scale_factor; + self.conf.window.size = SizeTuple(width, height).scale_and_check(scale_factor); } else if !self.timing_events.was_just_thumbnail_enter() { return Task::done(Message::ToggleThumbnail(true)); } } Message::CustomCountryDb(db) => { - self.configs.settings.mmdb_country.clone_from(&db); + self.conf.settings.mmdb_country.clone_from(&db); self.mmdb_readers.country = Arc::new(MmdbReader::from(&db, COUNTRY_MMDB)); } Message::CustomAsnDb(db) => { - self.configs.settings.mmdb_asn.clone_from(&db); + self.conf.settings.mmdb_asn.clone_from(&db); self.mmdb_readers.asn = Arc::new(MmdbReader::from(&db, ASN_MMDB)); } Message::QuitWrapper => return self.quit_wrapper(), Message::Quit => { - let _ = self.configs.clone().store(); + let _ = self.conf.clone().store(); return window::close(self.id.unwrap_or_else(Id::unique)); } Message::CopyIp(ip) => { @@ -448,24 +422,24 @@ pub fn update(&mut self, message: Message) -> Task { } Message::OpenFile(old_file, file_info, consumer_message) => { return Task::perform( - Self::open_file(old_file, file_info, self.configs.settings.language), + Self::open_file(old_file, file_info, self.conf.settings.language), consumer_message, ); } Message::HostSortSelection(sort_type) => { - self.host_sort_type = sort_type; + self.conf.host_sort_type = sort_type; } Message::ServiceSortSelection(sort_type) => { - self.service_sort_type = sort_type; + self.conf.service_sort_type = sort_type; } Message::ToggleExportPcap => { - self.export_pcap.toggle(); + self.conf.export_pcap.toggle(); } Message::OutputPcapDir(path) => { - self.export_pcap.set_directory(path); + self.conf.export_pcap.set_directory(path); } Message::OutputPcapFile(name) => { - self.export_pcap.set_file_name(&name); + self.conf.export_pcap.set_file_name(&name); } Message::ToggleThumbnail(triggered_by_resize) => { let window_id = self.id.unwrap_or_else(Id::unique); @@ -474,9 +448,9 @@ pub fn update(&mut self, message: Message) -> Task { self.traffic_chart.thumbnail = self.thumbnail; return if self.thumbnail { - let scale_factor = self.configs.settings.scale_factor; + let scale_factor = self.conf.settings.scale_factor; let size = ConfigWindow::thumbnail_size(scale_factor).to_size(); - let position = self.configs.window.thumbnail_position; + let position = self.conf.window.thumbnail_position; self.timing_events.thumbnail_enter_now(); Task::batch([ window::maximize(window_id, false), @@ -494,8 +468,8 @@ pub fn update(&mut self, message: Message) -> Task { window::change_level(window_id, Level::Normal), ]; if !triggered_by_resize { - let size = self.configs.window.size.to_size(); - let position = self.configs.window.position.to_point(); + let size = self.conf.window.size.to_size(); + let position = self.conf.window.position.to_point(); commands.push(window::move_to(window_id, position)); commands.push(window::resize(window_id, size)); } @@ -519,16 +493,16 @@ pub fn update(&mut self, message: Message) -> Task { } } Message::ScaleFactorShortcut(increase) => { - let scale_factor = self.configs.settings.scale_factor; + let scale_factor = self.conf.settings.scale_factor; if !(scale_factor > 2.99 && increase || scale_factor < 0.31 && !increase) { let delta = if increase { 0.1 } else { -0.1 }; - self.configs.settings.scale_factor += delta; + self.conf.settings.scale_factor += delta; } } Message::SetNewerReleaseStatus(status) => self.newer_release_available = status, Message::SetPcapImport(path) => { if !path.is_empty() { - self.import_pcap_path.clone_from(&path); + self.conf.import_pcap_path.clone_from(&path); self.capture_source = CaptureSource::File(MyPcapImport::new(path)); } } @@ -564,12 +538,12 @@ pub fn update(&mut self, message: Message) -> Task { } pub fn view(&self) -> Element<'_, Message, StyleType> { - let ConfigSettings { + let Settings { style, language, color_gradient, .. - } = self.configs.settings; + } = self.conf.settings; let font = style.get_extension().font; let font_headers = style.get_extension().font_headers; @@ -652,11 +626,11 @@ pub fn subscription(&self) -> Subscription { } pub fn theme(&self) -> StyleType { - self.configs.settings.style + self.conf.settings.style } pub fn scale_factor(&self) -> f64 { - self.configs.settings.scale_factor + self.conf.settings.scale_factor } /// Updates threshold if it hasn't been edited for a while @@ -664,17 +638,13 @@ fn update_threshold(&mut self) { // Ignore if just edited if let Some(temp_threshold) = self.timing_events.threshold_adjust_expired_take() { // Apply the temporary threshold to the actual config - self.configs - .settings - .notifications - .data_notification - .threshold = temp_threshold.threshold; - self.configs + self.conf.settings.notifications.data_notification.threshold = temp_threshold.threshold; + self.conf .settings .notifications .data_notification .byte_multiple = temp_threshold.byte_multiple; - self.configs + self.conf .settings .notifications .data_notification @@ -689,7 +659,7 @@ fn refresh_data(&mut self, mut msg: InfoTraffic, no_more_packets: bool) { } let emitted_notifications = notify_and_log( &mut self.logged_notifications, - self.configs.settings.notifications, + self.conf.settings.notifications, &msg, &self.favorite_hosts, &self.capture_source, @@ -729,9 +699,9 @@ fn start(&mut self) -> Task { let current_device_name = &self.capture_source.get_name(); self.set_device(current_device_name); } - let pcap_path = self.export_pcap.full_path(); + let pcap_path = self.conf.export_pcap.full_path(); let capture_context = - CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.filters); + CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.conf.filters); self.pcap_error = capture_context.error().map(ToString::to_string); self.running_page = RunningPage::Overview; @@ -776,9 +746,9 @@ fn reset(&mut self) { if let Some(rx) = &self.current_capture_rx.1 { rx.close(); } - let ConfigSettings { + let Settings { style, language, .. - } = self.configs.settings; + } = self.conf.settings; // increment capture id to ignore pending messages from previous captures self.current_capture_rx = (self.current_capture_rx.0 + 1, None); self.info_traffic = InfoTraffic::default(); @@ -787,9 +757,6 @@ fn reset(&mut self) { self.logged_notifications = (VecDeque::new(), 0); self.pcap_error = None; self.traffic_chart = TrafficChart::new(style, language); - self.report_sort_type = ReportSortType::default(); - self.host_sort_type = SortType::default(); - self.service_sort_type = SortType::default(); self.modal = None; self.settings_page = None; self.running_page = RunningPage::Init; @@ -803,7 +770,7 @@ fn reset(&mut self) { fn set_device(&mut self, name: &str) { for my_dev in &self.my_devices { if my_dev.get_name().eq(&name) { - self.configs.device.device_name = name.to_string(); + self.conf.device.device_name = name.to_string(); self.capture_source = CaptureSource::Device(my_dev.clone()); break; } @@ -845,7 +812,7 @@ fn add_or_remove_favorite(&mut self, host: &Host, add: bool) { fn close_settings(&mut self) { if let Some(page) = self.settings_page { - self.last_opened_setting = page; + self.conf.last_opened_setting = page; self.settings_page = None; } } @@ -855,7 +822,7 @@ fn close_settings(&mut self) { /// Threshold adjustments are saved in `self.timing_events.threshold_adjust` and then applied /// after timeout fn update_notifications_settings(&mut self, notification: Notification, emit_sound: bool) { - let notifications = self.configs.settings.notifications; + let notifications = self.conf.settings.notifications; let sound = match notification { Notification::Data(DataNotification { data_repr, @@ -879,37 +846,29 @@ fn update_notifications_settings(&mut self, notification: Notification, emit_sou self.timing_events.threshold_adjust_now(temp_threshold); } if threshold.is_some() != notifications.data_notification.threshold.is_some() { - self.configs - .settings - .notifications - .data_notification - .threshold = threshold; - self.configs + self.conf.settings.notifications.data_notification.threshold = threshold; + self.conf .settings .notifications .data_notification .byte_multiple = byte_multiple; - self.configs + self.conf .settings .notifications .data_notification .previous_threshold = previous_threshold; } - self.configs.settings.notifications.data_notification.sound = sound; - self.configs - .settings - .notifications - .data_notification - .data_repr = data_repr; + self.conf.settings.notifications.data_notification.sound = sound; + self.conf.settings.notifications.data_notification.data_repr = data_repr; sound } Notification::Favorite(favorite_notification) => { - self.configs.settings.notifications.favorite_notification = favorite_notification; + self.conf.settings.notifications.favorite_notification = favorite_notification; favorite_notification.sound } }; if emit_sound { - play(sound, self.configs.settings.notifications.volume); + play(sound, self.conf.settings.notifications.volume); } } @@ -918,7 +877,7 @@ fn get_temp_threshold(&self) -> DataNotification { if let Some(temp_threshold) = self.timing_events.temp_threshold() { temp_threshold } else { - let notifications = self.configs.settings.notifications; + let notifications = self.conf.settings.notifications; notifications.data_notification } } @@ -1083,9 +1042,9 @@ fn register_sigint_handler() -> Task { } pub fn is_capture_source_consistent(&self) -> bool { - self.capture_source_picklist == CaptureSourcePicklist::Device + self.conf.capture_source_picklist == CaptureSourcePicklist::Device && matches!(self.capture_source, CaptureSource::Device(_)) - || self.capture_source_picklist == CaptureSourcePicklist::File + || self.conf.capture_source_picklist == CaptureSourcePicklist::File && matches!(self.capture_source, CaptureSource::File(_)) } } @@ -1122,17 +1081,17 @@ mod tests { use crate::notifications::types::sound::Sound; use crate::report::types::sort_type::SortType; use crate::{ - ByteMultiple, ConfigDevice, ConfigSettings, ConfigWindow, Configs, Language, - ReportSortType, RunningPage, Sniffer, StyleType, + ByteMultiple, ConfigDevice, ConfigWindow, Configs, Language, ReportSortType, RunningPage, + Settings, Sniffer, StyleType, }; // helpful to clean up files generated from tests impl Drop for Sniffer { fn drop(&mut self) { - let settings_path_str = ConfigSettings::test_path(); + let settings_path_str = Settings::test_path(); let settings_path = Path::new(&settings_path_str); if settings_path.exists() { - remove_file(ConfigSettings::test_path()).unwrap(); + remove_file(Settings::test_path()).unwrap(); } let device_path_str = ConfigDevice::test_path(); @@ -1823,7 +1782,7 @@ fn test_correctly_switch_running_and_settings_pages() { #[test] #[serial] // needed to not collide with other tests generating configs files fn test_config_settings() { - let path_string = ConfigSettings::test_path(); + let path_string = Settings::test_path(); let path = Path::new(&path_string); assert!(!path.exists()); @@ -1836,7 +1795,7 @@ fn test_config_settings() { let settings_start = sniffer.configs.settings.clone(); assert_eq!( settings_start, - ConfigSettings { + Settings { color_gradient: GradientType::None, language: Language::EN, scale_factor: 1.0, @@ -1874,7 +1833,7 @@ fn test_config_settings() { let settings_end = Sniffer::new(Configs::load()).configs.settings.clone(); assert_eq!( settings_end, - ConfigSettings { + Settings { color_gradient: GradientType::Wild, language: Language::ZH, scale_factor: 1.0, diff --git a/src/gui/types/conf.rs b/src/gui/types/conf.rs new file mode 100644 index 00000000..f42cd6a7 --- /dev/null +++ b/src/gui/types/conf.rs @@ -0,0 +1,36 @@ +use crate::gui::pages::types::settings_page::SettingsPage; +use crate::gui::types::config_window::ConfigWindow; +use crate::gui::types::export_pcap::ExportPcap; +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::report::types::report_sort_type::ReportSortType; +use crate::report::types::sort_type::SortType; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct Conf { + /// Parameters from settings pages + pub settings: Settings, + /// Last selected network device name + pub device: ConfigDevice, + /// Window configuration, such as size and position + pub window: ConfigWindow, + /// Capture source picklist, to select the source of the capture + pub capture_source_picklist: CaptureSourcePicklist, + /// BPF filter program to be applied to the capture + pub filters: Filters, + /// Report sort type (inspect page) + pub report_sort_type: ReportSortType, + /// Host sort type (overview page) + pub host_sort_type: SortType, + /// Service sort type (overview page) + pub service_sort_type: SortType, + /// Remembers the last opened setting page + pub last_opened_setting: SettingsPage, + /// Information about PCAP file export + pub export_pcap: ExportPcap, + /// Import path for PCAP file + pub import_pcap_path: String, +} diff --git a/src/configs/types/config_window.rs b/src/gui/types/config_window.rs similarity index 100% rename from src/configs/types/config_window.rs rename to src/gui/types/config_window.rs diff --git a/src/gui/types/mod.rs b/src/gui/types/mod.rs index 50f45629..f96fc44b 100644 --- a/src/gui/types/mod.rs +++ b/src/gui/types/mod.rs @@ -1,4 +1,7 @@ +pub mod conf; +pub mod config_window; pub mod export_pcap; pub mod filters; pub mod message; +pub mod settings; pub mod timing_events; diff --git a/src/configs/types/config_settings.rs b/src/gui/types/settings.rs similarity index 67% rename from src/configs/types/config_settings.rs rename to src/gui/types/settings.rs index 284b674b..7dac7151 100644 --- a/src/configs/types/config_settings.rs +++ b/src/gui/types/settings.rs @@ -1,6 +1,3 @@ -//! Module defining the `ConfigSettings` struct, which allows to save and reload -//! the application default configuration. - use serde::{Deserialize, Serialize}; use crate::gui::styles::types::gradient_type::GradientType; @@ -12,7 +9,7 @@ use crate::{SNIFFNET_LOWERCASE, location}; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct ConfigSettings { +pub struct Settings { pub color_gradient: GradientType, pub language: Language, pub scale_factor: f64, @@ -24,21 +21,17 @@ pub struct ConfigSettings { pub style: StyleType, } -impl ConfigSettings { +impl Settings { const FILE_NAME: &'static str = "settings"; #[cfg(not(test))] pub fn load() -> Self { - if let Ok(settings) = confy::load::(SNIFFNET_LOWERCASE, Self::FILE_NAME) { + if let Ok(settings) = confy::load::(SNIFFNET_LOWERCASE, Self::FILE_NAME) { settings } else { - let _ = confy::store( - SNIFFNET_LOWERCASE, - Self::FILE_NAME, - ConfigSettings::default(), - ) - .log_err(location!()); - ConfigSettings::default() + let _ = confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, Settings::default()) + .log_err(location!()); + Settings::default() } } @@ -48,9 +41,9 @@ pub fn store(self) -> Result<(), confy::ConfyError> { } } -impl Default for ConfigSettings { +impl Default for Settings { fn default() -> Self { - ConfigSettings { + Settings { color_gradient: GradientType::default(), language: Language::default(), scale_factor: 1.0, @@ -65,20 +58,20 @@ fn default() -> Self { #[cfg(test)] mod tests { - use crate::ConfigSettings; + use crate::Settings; - impl ConfigSettings { + impl Settings { pub fn test_path() -> String { format!("{}/{}.toml", env!("CARGO_MANIFEST_DIR"), Self::FILE_NAME) } pub fn load() -> Self { - confy::load_path::(ConfigSettings::test_path()) - .unwrap_or_else(|_| ConfigSettings::default()) + confy::load_path::(Settings::test_path()) + .unwrap_or_else(|_| Settings::default()) } pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store_path(ConfigSettings::test_path(), self) + confy::store_path(Settings::test_path(), self) } } } diff --git a/src/main.rs b/src/main.rs index 230d5785..51434b6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,6 @@ use chart::types::traffic_chart::TrafficChart; use cli::handle_cli_args; -use configs::types::config_device::ConfigDevice; -use configs::types::config_settings::ConfigSettings; use gui::pages::types::running_page::RunningPage; use gui::sniffer::Sniffer; use gui::styles::style_constants::FONT_SIZE_BODY; @@ -26,10 +24,10 @@ use translations::types::language::Language; use utils::formatted_strings::print_cli_welcome_message; -use crate::configs::types::config_window::{ConfigWindow, ToPosition, ToSize}; use crate::configs::types::configs::{CONFIGS, Configs}; use crate::gui::sniffer::FONT_FAMILY_NAME; use crate::gui::styles::style_constants::{ICONS_BYTES, SARASA_MONO_BOLD_BYTES, SARASA_MONO_BYTES}; +use crate::gui::types::config_window::{ConfigWindow, ToPosition, ToSize}; mod chart; mod cli; diff --git a/src/configs/types/config_device.rs b/src/networking/types/config_device.rs similarity index 94% rename from src/configs/types/config_device.rs rename to src/networking/types/config_device.rs index f33226d1..e5dacb78 100644 --- a/src/configs/types/config_device.rs +++ b/src/networking/types/config_device.rs @@ -1,6 +1,3 @@ -//! Module defining the `ConfigDevice` struct, which allows to save and reload -//! the application default configuration. - use crate::networking::types::my_device::MyDevice; #[cfg(not(test))] use crate::utils::error_logger::{ErrorLogger, Location}; @@ -67,7 +64,7 @@ pub fn to_my_device(&self) -> MyDevice { #[cfg(test)] mod tests { - use crate::ConfigDevice; + use crate::networking::types::config_device::ConfigDevice; impl ConfigDevice { pub fn test_path() -> String { diff --git a/src/networking/types/mod.rs b/src/networking/types/mod.rs index e4710381..ab112f21 100644 --- a/src/networking/types/mod.rs +++ b/src/networking/types/mod.rs @@ -3,6 +3,7 @@ pub mod asn; pub mod bogon; pub mod capture_context; +pub mod config_device; pub mod data_info; pub mod data_info_host; pub mod data_representation; diff --git a/src/report/get_report_entries.rs b/src/report/get_report_entries.rs index 5e67c271..52624291 100644 --- a/src/report/get_report_entries.rs +++ b/src/report/get_report_entries.rs @@ -50,7 +50,7 @@ pub fn get_searched_entries( all_results.sort_by(|&(_, a), &(_, b)| { a.compare( b, - sniffer.report_sort_type.data_sort, + sniffer.conf.report_sort_type.data_sort, sniffer.traffic_chart.data_repr, ) }); From a0ebe291fe0c3feb00f1867575720622d068679b Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 24 Aug 2025 17:21:20 +0200 Subject: [PATCH 53/74] fix Conf trait impls --- src/cli/mod.rs | 12 +++--- src/configs/mod.rs | 1 - src/configs/types/configs.rs | 32 ---------------- src/configs/types/mod.rs | 1 - src/gui/pages/types/settings_page.rs | 4 +- src/gui/sniffer.rs | 5 +-- src/gui/types/conf.rs | 50 ++++++++++++++++++++++++- src/gui/types/config_window.rs | 41 -------------------- src/gui/types/export_pcap.rs | 2 + src/gui/types/filters.rs | 4 +- src/gui/types/settings.rs | 44 ---------------------- src/main.rs | 9 ++--- src/networking/types/capture_context.rs | 17 ++++++++- src/networking/types/config_device.rs | 42 --------------------- src/report/types/report_sort_type.rs | 6 +-- src/report/types/sort_type.rs | 6 +-- 16 files changed, 90 insertions(+), 186 deletions(-) delete mode 100644 src/configs/mod.rs delete mode 100644 src/configs/types/configs.rs delete mode 100644 src/configs/types/mod.rs diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 68e0375a..f42ba192 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,11 +1,9 @@ -use clap::Parser; -use iced::{Task, window}; - -use crate::CONFIGS; -use crate::Configs; use crate::SNIFFNET_LOWERCASE; +use crate::gui::types::conf::{CONF, Conf}; use crate::gui::types::message::Message; use crate::utils::formatted_strings::APP_VERSION; +use clap::Parser; +use iced::{Task, window}; #[derive(Parser, Debug)] #[command( @@ -16,7 +14,7 @@ )] struct Args { /// Start sniffing packets from the supplied network adapter - #[arg(short, long, value_name = "NAME", default_missing_value = CONFIGS.device.device_name.as_str(), num_args = 0..=1)] + #[arg(short, long, value_name = "NAME", default_missing_value = CONF.device.device_name.as_str(), num_args = 0..=1)] adapter: Option, #[cfg(all(windows, not(debug_assertions)))] /// Show the logs (stdout and stderr) of the most recent application run @@ -50,7 +48,7 @@ pub fn handle_cli_args() -> Task { } if args.restore_default { - if Configs::default().store().is_ok() { + if Conf::default().store().is_ok() { println!("Restored default settings"); } std::process::exit(0); diff --git a/src/configs/mod.rs b/src/configs/mod.rs deleted file mode 100644 index cd408564..00000000 --- a/src/configs/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod types; diff --git a/src/configs/types/configs.rs b/src/configs/types/configs.rs deleted file mode 100644 index 6cc15345..00000000 --- a/src/configs/types/configs.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::ConfigWindow; -use crate::gui::types::settings::Settings; -use crate::networking::types::config_device::ConfigDevice; -use confy::ConfyError; - -pub static CONFIGS: std::sync::LazyLock = std::sync::LazyLock::new(Configs::load); - -#[derive(Default, Clone, PartialEq, Debug)] -pub struct Configs { - pub settings: Settings, - pub device: ConfigDevice, - pub window: ConfigWindow, -} - -impl Configs { - /// This should only be used directly to load fresh configs; - /// use `CONFIGS` instead to access the initial instance - pub fn load() -> Self { - Configs { - settings: Settings::load(), - device: ConfigDevice::load(), - window: ConfigWindow::load(), - } - } - - pub fn store(self) -> Result<(), ConfyError> { - self.settings.store()?; - self.device.store()?; - self.window.store()?; - Ok(()) - } -} diff --git a/src/configs/types/mod.rs b/src/configs/types/mod.rs deleted file mode 100644 index 3810d5b3..00000000 --- a/src/configs/types/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod configs; diff --git a/src/gui/pages/types/settings_page.rs b/src/gui/pages/types/settings_page.rs index 2dbaf9ca..d2457507 100644 --- a/src/gui/pages/types/settings_page.rs +++ b/src/gui/pages/types/settings_page.rs @@ -3,11 +3,13 @@ use crate::translations::translations_3::general_translation; use crate::utils::types::icon::Icon; use crate::{Language, StyleType}; +use serde::{Deserialize, Serialize}; /// This enum defines the current settings page. -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Default)] pub enum SettingsPage { /// Settings Notifications page. + #[default] Notifications, /// Settings Appearance page. Appearance, diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 8da6a9be..093faf72 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -89,7 +89,6 @@ pub struct Sniffer { /// Reports if a newer release of the software is available on GitHub pub newer_release_available: Option, /// Network device to be analyzed, or PCAP file to be imported - /// TODO: Conf??? pub capture_source: CaptureSource, /// List of network devices pub my_devices: Vec, @@ -132,7 +131,7 @@ pub fn new(conf: Conf) -> Self { mmdb_asn, .. } = conf.settings.clone(); - let device = conf.device.to_my_device(); + let capture_source = CaptureSource::from_conf(&conf); Self { conf, current_capture_rx: (0, None), @@ -141,7 +140,7 @@ pub fn new(conf: Conf) -> Self { favorite_hosts: HashSet::new(), logged_notifications: (VecDeque::new(), 0), newer_release_available: None, - capture_source: CaptureSource::Device(device), + capture_source, my_devices: Vec::new(), pcap_error: None, dots_pulse: (".".to_string(), 0), diff --git a/src/gui/types/conf.rs b/src/gui/types/conf.rs index f42cd6a7..615690a9 100644 --- a/src/gui/types/conf.rs +++ b/src/gui/types/conf.rs @@ -7,9 +7,14 @@ use crate::networking::types::config_device::ConfigDevice; use crate::report::types::report_sort_type::ReportSortType; use crate::report::types::sort_type::SortType; +use crate::utils::error_logger::{ErrorLogger, Location}; +use crate::{SNIFFNET_LOWERCASE, location}; +use confy::ConfyError; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub static CONF: std::sync::LazyLock = std::sync::LazyLock::new(Conf::load); + +#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)] pub struct Conf { /// Parameters from settings pages pub settings: Settings, @@ -34,3 +39,46 @@ pub struct Conf { /// Import path for PCAP file pub import_pcap_path: String, } + +impl Conf { + const FILE_NAME: &'static str = "conf"; + + /// This should only be used directly to load fresh configurations; + /// use `CONF` instead to access the initial instance + #[cfg(not(test))] + pub fn load() -> Self { + if let Ok(conf) = confy::load::(SNIFFNET_LOWERCASE, Self::FILE_NAME) { + conf + } else { + let _ = confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, Conf::default()) + .log_err(location!()); + Conf::default() + } + } + + #[cfg(not(test))] + pub fn store(self) -> Result<(), ConfyError> { + confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, self).log_err(location!()) + } +} + +#[cfg(test)] +mod tests { + use crate::Settings; + use crate::gui::types::conf::Conf; + + impl Conf { + pub fn test_path() -> String { + format!("{}/{}.toml", env!("CARGO_MANIFEST_DIR"), Self::FILE_NAME) + } + + pub fn load() -> Self { + confy::load_path::(Settings::test_path()) + .unwrap_or_else(|_| Settings::default()) + } + + pub fn store(self) -> Result<(), confy::ConfyError> { + confy::store_path(Settings::test_path(), self) + } + } +} diff --git a/src/gui/types/config_window.rs b/src/gui/types/config_window.rs index 03bb74fc..3864f520 100644 --- a/src/gui/types/config_window.rs +++ b/src/gui/types/config_window.rs @@ -1,7 +1,3 @@ -#[cfg(not(test))] -use crate::utils::error_logger::{ErrorLogger, Location}; -#[cfg(not(test))] -use crate::{SNIFFNET_LOWERCASE, location}; use iced::window::Position; use iced::{Point, Size}; use serde::{Deserialize, Serialize}; @@ -30,23 +26,6 @@ impl ConfigWindow { const MIN_SIZE_X: f32 = 100.0; const MIN_SIZE_Y: f32 = 100.0; - const FILE_NAME: &'static str = "window"; - #[cfg(not(test))] - pub fn load() -> Self { - if let Ok(window) = confy::load::(SNIFFNET_LOWERCASE, Self::FILE_NAME) { - window - } else { - let _ = confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, ConfigWindow::default()) - .log_err(location!()); - ConfigWindow::default() - } - } - - #[cfg(not(test))] - pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, self).log_err(location!()) - } - pub fn thumbnail_size(factor: f64) -> SizeTuple { Self::THUMBNAIL_SIZE.scale_and_check(factor) } @@ -142,23 +121,3 @@ fn scale_and_check(self, factor: f64) -> PositionTuple { PositionTuple(x, y) } } - -#[cfg(test)] -mod tests { - use crate::ConfigWindow; - - impl ConfigWindow { - pub fn test_path() -> String { - format!("{}/{}.toml", env!("CARGO_MANIFEST_DIR"), Self::FILE_NAME) - } - - pub fn load() -> Self { - confy::load_path::(ConfigWindow::test_path()) - .unwrap_or_else(|_| ConfigWindow::default()) - } - - pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store_path(ConfigWindow::test_path(), self) - } - } -} diff --git a/src/gui/types/export_pcap.rs b/src/gui/types/export_pcap.rs index 93420d8b..52896fc6 100644 --- a/src/gui/types/export_pcap.rs +++ b/src/gui/types/export_pcap.rs @@ -1,5 +1,7 @@ +use serde::{Deserialize, Serialize}; use std::path::PathBuf; +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct ExportPcap { enabled: bool, file_name: String, diff --git a/src/gui/types/filters.rs b/src/gui/types/filters.rs index f5076430..cf545778 100644 --- a/src/gui/types/filters.rs +++ b/src/gui/types/filters.rs @@ -1,4 +1,6 @@ -#[derive(Default)] +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)] pub struct Filters { expanded: bool, bpf: String, diff --git a/src/gui/types/settings.rs b/src/gui/types/settings.rs index 7dac7151..187d9d90 100644 --- a/src/gui/types/settings.rs +++ b/src/gui/types/settings.rs @@ -2,11 +2,7 @@ use crate::gui::styles::types::gradient_type::GradientType; use crate::notifications::types::notifications::Notifications; -#[cfg(not(test))] -use crate::utils::error_logger::{ErrorLogger, Location}; use crate::{Language, StyleType}; -#[cfg(not(test))] -use crate::{SNIFFNET_LOWERCASE, location}; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct Settings { @@ -21,26 +17,6 @@ pub struct Settings { pub style: StyleType, } -impl Settings { - const FILE_NAME: &'static str = "settings"; - - #[cfg(not(test))] - pub fn load() -> Self { - if let Ok(settings) = confy::load::(SNIFFNET_LOWERCASE, Self::FILE_NAME) { - settings - } else { - let _ = confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, Settings::default()) - .log_err(location!()); - Settings::default() - } - } - - #[cfg(not(test))] - pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, self).log_err(location!()) - } -} - impl Default for Settings { fn default() -> Self { Settings { @@ -55,23 +31,3 @@ fn default() -> Self { } } } - -#[cfg(test)] -mod tests { - use crate::Settings; - - impl Settings { - pub fn test_path() -> String { - format!("{}/{}.toml", env!("CARGO_MANIFEST_DIR"), Self::FILE_NAME) - } - - pub fn load() -> Self { - confy::load_path::(Settings::test_path()) - .unwrap_or_else(|_| Settings::default()) - } - - pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store_path(Settings::test_path(), self) - } - } -} diff --git a/src/main.rs b/src/main.rs index 51434b6f..c424db58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,14 +24,13 @@ use translations::types::language::Language; use utils::formatted_strings::print_cli_welcome_message; -use crate::configs::types::configs::{CONFIGS, Configs}; use crate::gui::sniffer::FONT_FAMILY_NAME; use crate::gui::styles::style_constants::{ICONS_BYTES, SARASA_MONO_BOLD_BYTES, SARASA_MONO_BYTES}; +use crate::gui::types::conf::CONF; use crate::gui::types::config_window::{ConfigWindow, ToPosition, ToSize}; mod chart; mod cli; -mod configs; mod countries; mod gui; mod mmdb; @@ -60,7 +59,7 @@ pub fn main() -> iced::Result { _gag2 = gag2; } - let configs = CONFIGS.clone(); + let conf = CONF.clone(); let boot_task_chain = handle_cli_args(); #[cfg(debug_assertions)] @@ -76,7 +75,7 @@ pub fn main() -> iced::Result { print_cli_welcome_message(); - let ConfigWindow { size, position, .. } = configs.window; + let ConfigWindow { size, position, .. } = conf.window; application(SNIFFNET_TITLECASE, Sniffer::update, Sniffer::view) .settings(Settings { @@ -112,5 +111,5 @@ pub fn main() -> iced::Result { .subscription(Sniffer::subscription) .theme(Sniffer::theme) .scale_factor(Sniffer::scale_factor) - .run_with(move || (Sniffer::new(configs), boot_task_chain)) + .run_with(move || (Sniffer::new(conf), boot_task_chain)) } diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index 0b1964e3..ee31363c 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -1,3 +1,4 @@ +use crate::gui::types::conf::Conf; use crate::gui::types::filters::Filters; use crate::networking::types::my_device::MyDevice; use crate::networking::types::my_link_type::MyLinkType; @@ -5,6 +6,7 @@ use crate::translations::translations_4::capture_file_translation; use crate::translations::types::language::Language; use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat}; +use serde::{Deserialize, Serialize}; pub enum CaptureContext { Live(Live), @@ -155,6 +157,19 @@ pub enum CaptureSource { } impl CaptureSource { + pub fn from_conf(conf: &Conf) -> Self { + match conf.capture_source_picklist { + CaptureSourcePicklist::Device => { + let device = conf.device.to_my_device(); + Self::Device(device) + } + CaptureSourcePicklist::File => { + let path = conf.import_pcap_path.clone(); + Self::File(MyPcapImport::new(path)) + } + } + } + pub fn title(&self, language: Language) -> &str { match self { Self::Device(_) => network_adapter_translation(language), @@ -222,7 +237,7 @@ pub fn new(path: String) -> Self { } } -#[derive(Clone, Eq, PartialEq, Debug, Copy, Default)] +#[derive(Clone, Eq, PartialEq, Debug, Copy, Default, Serialize, Deserialize)] pub enum CaptureSourcePicklist { #[default] Device, diff --git a/src/networking/types/config_device.rs b/src/networking/types/config_device.rs index e5dacb78..ad2445ab 100644 --- a/src/networking/types/config_device.rs +++ b/src/networking/types/config_device.rs @@ -1,8 +1,4 @@ use crate::networking::types::my_device::MyDevice; -#[cfg(not(test))] -use crate::utils::error_logger::{ErrorLogger, Location}; -#[cfg(not(test))] -use crate::{SNIFFNET_LOWERCASE, location}; use pcap::{Device, DeviceFlags}; use serde::{Deserialize, Serialize}; @@ -28,24 +24,6 @@ fn default() -> Self { } impl ConfigDevice { - const FILE_NAME: &'static str = "device"; - - #[cfg(not(test))] - pub fn load() -> Self { - if let Ok(device) = confy::load::(SNIFFNET_LOWERCASE, Self::FILE_NAME) { - device - } else { - let _ = confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, ConfigDevice::default()) - .log_err(location!()); - ConfigDevice::default() - } - } - - #[cfg(not(test))] - pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, self).log_err(location!()) - } - pub fn to_my_device(&self) -> MyDevice { for device in Device::list().unwrap_or_default() { if device.name.eq(&self.device_name) { @@ -61,23 +39,3 @@ pub fn to_my_device(&self) -> MyDevice { MyDevice::from_pcap_device(standard_device) } } - -#[cfg(test)] -mod tests { - use crate::networking::types::config_device::ConfigDevice; - - impl ConfigDevice { - pub fn test_path() -> String { - format!("{}/{}.toml", env!("CARGO_MANIFEST_DIR"), Self::FILE_NAME) - } - - pub fn load() -> Self { - confy::load_path::(ConfigDevice::test_path()) - .unwrap_or_else(|_| ConfigDevice::default()) - } - - pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store_path(ConfigDevice::test_path(), self) - } - } -} diff --git a/src/report/types/report_sort_type.rs b/src/report/types/report_sort_type.rs index ab0679bf..da23bbfb 100644 --- a/src/report/types/report_sort_type.rs +++ b/src/report/types/report_sort_type.rs @@ -1,13 +1,13 @@ use std::fmt::Debug; -use iced::widget::Text; - use crate::gui::styles::button::ButtonType; use crate::gui::styles::types::style_type::StyleType; use crate::report::types::sort_type::SortType; +use iced::widget::Text; +use serde::{Deserialize, Serialize}; /// Struct representing the possible kinds of sort for displayed relevant connections. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct ReportSortType { pub data_sort: SortType, } diff --git a/src/report/types/sort_type.rs b/src/report/types/sort_type.rs index 2dec0be5..b991a0e2 100644 --- a/src/report/types/sort_type.rs +++ b/src/report/types/sort_type.rs @@ -1,10 +1,10 @@ -use iced::widget::Text; - use crate::gui::styles::button::ButtonType; use crate::gui::styles::types::style_type::StyleType; use crate::utils::types::icon::Icon; +use iced::widget::Text; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub enum SortType { Ascending, Descending, From 691fca8be5c332f5bb6be911057c9512773eda08 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 28 Aug 2025 16:34:48 +0200 Subject: [PATCH 54/74] update deps --- .github/workflows/docker.yml | 2 +- .github/workflows/package.yml | 24 +- .github/workflows/rust.yml | 2 +- Cargo.lock | 495 +++++++++++++++++----------------- Cargo.toml | 16 +- 5 files changed, 265 insertions(+), 274 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7427a43b..a7693ffa 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Extract version from Cargo.toml id: cargo-version diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 86589308..3fa67ab0 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Linux dependencies if: matrix.os == 'ubuntu' @@ -126,7 +126,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies run: apt-get update -y && apt-get install -y curl build-essential @@ -139,7 +139,7 @@ jobs: - name: Install packaging tools run: cargo install cargo-deb - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: build-ubuntu-${{ matrix.target }} path: target/ @@ -179,13 +179,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies run: apt-get update -y && apt-get install -y git build-essential graphicsmagick-imagemagick-compat wget file desktop-file-utils libfuse2 - name: Download Debian package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: deb-${{ matrix.arch }} path: /target/ @@ -231,7 +231,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies run: dnf update -y && dnf install -y @development-tools patchelf @@ -244,7 +244,7 @@ jobs: - name: Install packaging tools run: cargo install cargo-generate-rpm - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: build-ubuntu-${{ matrix.target }} path: target/ @@ -278,7 +278,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -288,7 +288,7 @@ jobs: cargo install toml-cli brew install create-dmg - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: build-macos-${{ matrix.target }} path: target/ @@ -342,7 +342,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies shell: powershell @@ -363,7 +363,7 @@ jobs: - name: Install packaging tools run: cargo install cargo-wix - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: build-windows-${{ matrix.target }} path: target/ @@ -384,7 +384,7 @@ jobs: if-no-files-found: error - name: Sign package artifacts - uses: signpath/github-action-submit-signing-request@v1.2 + uses: signpath/github-action-submit-signing-request@v1.3 with: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '3b533e02-73c3-4908-a018-d09a34498a6a' diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 85f91c52..82704275 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -32,7 +32,7 @@ jobs: - os: windows steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy diff --git a/Cargo.lock b/Cargo.lock index 39ec831a..c68b0b6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "addr2line" @@ -80,12 +80,12 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alsa" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +checksum = "bdc00893e7a970727e9304671b2c88577b4cfe53dc64019fdfdf9683573a09c4" dependencies = [ "alsa-sys", - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -107,7 +107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.1", + "bitflags 2.9.3", "cc", "cesu8", "jni", @@ -247,7 +247,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.9.0", + "zbus 5.10.0", ] [[package]] @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" dependencies = [ "async-task", "concurrent-queue", @@ -365,7 +365,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -394,13 +394,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -453,9 +453,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bit_field" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" @@ -465,9 +465,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "block" @@ -499,7 +499,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" dependencies = [ - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -544,7 +544,7 @@ checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -565,7 +565,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "log", "polling", "rustix 0.38.44", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.32" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "jobserver", "libc", @@ -604,9 +604,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -634,9 +634,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.44" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -656,14 +656,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -791,7 +791,7 @@ checksum = "f29222b549d4e3ded127989d523da9e928918d0d0d7f7c1690b439d0d538bae9" dependencies = [ "directories", "serde", - "thiserror 2.0.14", + "thiserror 2.0.16", "toml 0.8.23", ] @@ -840,7 +840,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -864,7 +864,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation 0.10.1", "libc", ] @@ -889,7 +889,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "fontdb 0.16.2", "log", "rangemap", @@ -1019,7 +1019,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "libloading 0.8.8", "winapi", ] @@ -1048,9 +1048,9 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] name = "data-url" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "dconf_rs" @@ -1127,10 +1127,10 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.6.1", "libc", - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -1141,7 +1141,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1161,13 +1161,13 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "dns-lookup" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91adf1f5ae09290d87cca8f4f0a8e49bcc30672993eb8aa11a5c9d8872d16a98" +checksum = "853d5bcf0b73bd5e6d945b976288621825c7166e9f06c5a035ae1aaf42d1b64f" dependencies = [ "cfg-if", "libc", - "socket2 0.6.0", + "socket2", "windows-sys 0.60.2", ] @@ -1195,7 +1195,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytemuck", "drm-ffi", "drm-fourcc", @@ -1267,7 +1267,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1516,7 +1516,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1533,9 +1533,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1610,7 +1610,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1772,7 +1772,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "gpu-alloc-types", ] @@ -1782,7 +1782,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -1804,7 +1804,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "gpu-descriptor-types", "hashbrown 0.14.5", ] @@ -1815,7 +1815,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -1888,7 +1888,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "com", "libc", "libloading 0.8.8", @@ -1969,19 +1969,21 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -2038,7 +2040,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2", "system-configuration", "tokio", "tower-service", @@ -2091,7 +2093,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0013a238275494641bf8f1732a23a808196540dc67b22ff97099c044ae4c8a1c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytes", "dark-light", "glam", @@ -2139,7 +2141,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba25a18cfa6d5cc160aca7e1b34f73ccdff21680fa8702168c09739767b6c66f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytemuck", "cosmic-text", "half", @@ -2205,7 +2207,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15708887133671d2bcc6c1d01d1f176f43a64d6cdc3b2bf893396c3ee498295f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytemuck", "futures", "glam", @@ -2345,9 +2347,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -2390,9 +2392,9 @@ checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -2409,11 +2411,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -2485,9 +2487,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -2609,7 +2611,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "libc", "redox_syscall 0.5.17", ] @@ -2746,7 +2748,7 @@ dependencies = [ "log", "memchr", "serde", - "thiserror 2.0.14", + "thiserror 2.0.16", ] [[package]] @@ -2757,9 +2759,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -2779,7 +2781,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -2817,9 +2819,9 @@ dependencies = [ [[package]] name = "mutate_once" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" [[package]] name = "naga" @@ -2828,7 +2830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ "bit-set", - "bitflags 2.9.1", + "bitflags 2.9.3", "codespan-reporting", "hexf-parse", "indexmap", @@ -2864,7 +2866,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -2903,7 +2905,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -2916,7 +2918,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -2941,7 +2943,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3003,7 +3005,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3034,9 +3036,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ "objc2-encode", ] @@ -3047,7 +3049,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -3063,9 +3065,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.6.1", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-foundation 0.3.1", ] @@ -3075,9 +3077,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cbe18d879e20a4aea544f8befe38bcf52255eb63d3f23eca2842f3319e4c07" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "libc", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-audio", "objc2-core-audio-types", "objc2-core-foundation", @@ -3090,7 +3092,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -3115,7 +3117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca44961e888e19313b808f23497073e3f6b3c22bb485056674c8b49f3b025c82" dependencies = [ "dispatch2", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-core-audio-types", "objc2-core-foundation", ] @@ -3126,8 +3128,8 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f1cc99bb07ad2ddb6527ddf83db6a15271bb036b3eb94b801cd44fdc666ee1" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.9.3", + "objc2 0.6.2", ] [[package]] @@ -3136,7 +3138,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3148,9 +3150,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "dispatch2", - "objc2 0.6.1", + "objc2 0.6.2", ] [[package]] @@ -3189,7 +3191,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "dispatch", "libc", @@ -3202,8 +3204,8 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.1", + "bitflags 2.9.3", + "objc2 0.6.2", "objc2-core-foundation", ] @@ -3225,7 +3227,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3237,7 +3239,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -3260,7 +3262,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -3292,7 +3294,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -3335,7 +3337,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg-if", "foreign-types 0.3.2", "libc", @@ -3352,7 +3354,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3429,7 +3431,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3462,7 +3464,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3542,9 +3544,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -3558,22 +3560,22 @@ dependencies = [ [[package]] name = "phf" -version = "0.12.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_shared 0.12.1", + "phf_shared 0.13.1", "serde", ] [[package]] name = "phf_codegen" -version = "0.12.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator 0.12.1", - "phf_shared 0.12.1", + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] @@ -3588,12 +3590,12 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.12.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared 0.12.1", + "phf_shared 0.13.1", ] [[package]] @@ -3606,7 +3608,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3620,9 +3622,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.12.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", ] @@ -3650,7 +3652,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3781,9 +3783,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -3796,7 +3798,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "version_check", "yansi", ] @@ -3827,9 +3829,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases 0.2.1", @@ -3838,8 +3840,8 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.5.10", - "thiserror 2.0.14", + "socket2", + "thiserror 2.0.16", "tokio", "tracing", "web-time", @@ -3847,9 +3849,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", @@ -3860,7 +3862,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.14", + "thiserror 2.0.16", "tinyvec", "tracing", "web-time", @@ -3868,16 +3870,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3974,9 +3976,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -3984,9 +3986,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -4026,7 +4028,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -4048,14 +4050,14 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.14", + "thiserror 2.0.16", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -4065,9 +4067,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -4076,9 +4078,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "relative-path" @@ -4094,9 +4096,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", @@ -4163,7 +4165,7 @@ dependencies = [ "dispatch2", "js-sys", "log", - "objc2 0.6.1", + "objc2 0.6.2", "objc2-app-kit 0.3.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -4242,7 +4244,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.104", + "syn 2.0.106", "unicode-ident", ] @@ -4289,7 +4291,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "errno 0.3.13", "libc", "linux-raw-sys 0.4.15", @@ -4302,7 +4304,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "errno 0.3.13", "libc", "linux-raw-sys 0.9.4", @@ -4372,7 +4374,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytemuck", "libm", "smallvec", @@ -4400,9 +4402,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" dependencies = [ "sdd", ] @@ -4453,7 +4455,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -4499,14 +4501,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -4522,7 +4524,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4584,7 +4586,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4671,7 +4673,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "calloop", "calloop-wayland-source", "cursor-icon", @@ -4725,9 +4727,9 @@ dependencies = [ "iced", "maxminddb", "pcap", - "phf 0.12.1", + "phf 0.13.1", "phf_codegen", - "phf_shared 0.12.1", + "phf_shared 0.13.1", "plotters", "plotters-iced", "reqwest", @@ -4744,16 +4746,6 @@ dependencies = [ "winres", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -4802,7 +4794,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -4933,9 +4925,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -4959,7 +4951,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4977,7 +4969,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4994,15 +4986,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5025,11 +5017,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.14", + "thiserror-impl 2.0.16", ] [[package]] @@ -5040,18 +5032,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5116,9 +5108,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -5142,7 +5134,7 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", "windows-sys 0.59.0", ] @@ -5155,7 +5147,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5301,7 +5293,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "bytes", "futures-util", "http", @@ -5344,7 +5336,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5480,9 +5472,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -5603,7 +5595,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -5638,7 +5630,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5687,7 +5679,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "rustix 1.0.8", "wayland-backend", "wayland-scanner", @@ -5699,7 +5691,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "cursor-icon", "wayland-backend", ] @@ -5721,7 +5713,7 @@ version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "wayland-backend", "wayland-client", "wayland-scanner", @@ -5733,7 +5725,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5746,7 +5738,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5844,7 +5836,7 @@ checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.9.1", + "bitflags 2.9.3", "cfg_aliases 0.1.1", "codespan-reporting", "indexmap", @@ -5872,7 +5864,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.1", + "bitflags 2.9.3", "block", "cfg_aliases 0.1.1", "core-graphics-types 0.1.3", @@ -5913,7 +5905,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "js-sys", "web-sys", ] @@ -5942,11 +5934,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6029,7 +6021,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6040,7 +6032,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6418,7 +6410,7 @@ dependencies = [ "ahash 0.8.12", "android-activity", "atomic-waker", - "bitflags 2.9.1", + "bitflags 2.9.3", "block2 0.5.1", "bytemuck", "calloop", @@ -6463,9 +6455,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -6494,7 +6486,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", ] [[package]] @@ -6557,7 +6549,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.3", "dlib", "log", "once_cell", @@ -6614,7 +6606,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -6658,9 +6650,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.9.0" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" +checksum = "67a073be99ace1adc48af593701c8015cd9817df372e14a1a6b0ee8f8bf043be" dependencies = [ "async-broadcast", "async-executor", @@ -6682,11 +6674,11 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys 0.59.0", + "windows-sys 0.60.2", "winnow", - "zbus_macros 5.9.0", + "zbus_macros 5.10.0", "zbus_names 4.2.0", - "zvariant 5.6.0", + "zvariant 5.7.0", ] [[package]] @@ -6698,23 +6690,23 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zvariant_utils 2.1.0", ] [[package]] name = "zbus_macros" -version = "5.9.0" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" +checksum = "0e80cd713a45a49859dcb648053f63265f4f2851b6420d47a958e5697c68b131" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zbus_names 4.2.0", - "zvariant 5.6.0", - "zvariant_utils 3.2.0", + "zvariant 5.7.0", + "zvariant_utils 3.2.1", ] [[package]] @@ -6737,7 +6729,7 @@ dependencies = [ "serde", "static_assertions", "winnow", - "zvariant 5.6.0", + "zvariant 5.7.0", ] [[package]] @@ -6763,7 +6755,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6783,7 +6775,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -6823,7 +6815,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6850,17 +6842,17 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" dependencies = [ "endi", "enumflags2", "serde", "url", "winnow", - "zvariant_derive 5.6.0", - "zvariant_utils 3.2.0", + "zvariant_derive 5.7.0", + "zvariant_utils 3.2.1", ] [[package]] @@ -6872,21 +6864,21 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "zvariant_utils 2.1.0", ] [[package]] name = "zvariant_derive" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", - "zvariant_utils 3.2.0", + "syn 2.0.106", + "zvariant_utils 3.2.1", ] [[package]] @@ -6897,19 +6889,18 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "zvariant_utils" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" dependencies = [ "proc-macro2", "quote", "serde", - "static_assertions", - "syn 2.0.104", + "syn 2.0.106", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 266bc1f1..98a243e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,14 +47,14 @@ maxminddb = "0.26.0" confy = "1.0.0" serde = { version = "1.0.219", default-features = false, features = ["derive"] } rodio = { version = "0.21.1", default-features = false, features = ["mp3", "playback"] } -dns-lookup = "2.1.0" +dns-lookup = "3.0.0" toml = "0.9.5" ctrlc = { version = "3.4.7", features = ["termination"] } rfd = "0.15.4" -phf = "0.12.1" -phf_shared = "0.12.1" +phf = "0.13.1" +phf_shared = "0.13.1" splines = "5.0.0" -clap = { version = "4.5.44", features = ["derive"] } +clap = { version = "4.5.46", features = ["derive"] } tokio = { version = "1.47.1", features = ["macros"] } async-channel = "2.5.0" @@ -62,10 +62,10 @@ async-channel = "2.5.0" gag = "1.0.0" [target.'cfg(not(target_arch = "powerpc64"))'.dependencies] -reqwest = { version = "0.12.22", default-features = false, features = ["json", "rustls-tls"] } +reqwest = { version = "0.12.23", default-features = false, features = ["json", "rustls-tls"] } [target.'cfg(target_arch = "powerpc64")'.dependencies] -reqwest = { version = "0.12.22", features = ["json"] } +reqwest = { version = "0.12.23", features = ["json"] } #─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── @@ -77,8 +77,8 @@ serial_test = { version = "3.2.0", default-features = false } #─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── [build-dependencies] -phf_codegen = "0.12.1" -phf_shared = "0.12.1" +phf_codegen = "0.13.1" +phf_shared = "0.13.1" rustrict = { version = "0.7.36", default-features = false, features = ["censor"] } [target."cfg(windows)".build-dependencies] From eb013e0eb686384f7e7b426b3341748327f472e8 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Thu, 28 Aug 2025 23:37:39 +0200 Subject: [PATCH 55/74] fix tests --- src/cli/mod.rs | 45 ++- src/gui/pages/inspect_page.rs | 7 +- src/gui/sniffer.rs | 430 +++++++++++++-------------- src/gui/types/conf.rs | 12 +- src/gui/types/export_pcap.rs | 6 +- src/gui/types/filters.rs | 4 +- src/gui/types/message.rs | 4 +- src/main.rs | 5 +- src/report/get_report_entries.rs | 2 +- src/report/types/mod.rs | 1 - src/report/types/report_sort_type.rs | 78 ----- 11 files changed, 255 insertions(+), 339 deletions(-) delete mode 100644 src/report/types/report_sort_type.rs diff --git a/src/cli/mod.rs b/src/cli/mod.rs index f42ba192..887a4367 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -70,20 +70,26 @@ pub fn handle_cli_args() -> Task { mod tests { use serial_test::serial; - use crate::configs::types::config_window::{PositionTuple, SizeTuple}; + use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::types::custom_palette::ExtraStyles; use crate::gui::styles::types::gradient_type::GradientType; + use crate::gui::types::conf::Conf; + use crate::gui::types::config_window::{PositionTuple, SizeTuple}; + use crate::gui::types::export_pcap::ExportPcap; + 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::notifications::types::notifications::Notifications; - use crate::{ConfigDevice, ConfigWindow, Language, Settings, Sniffer, StyleType}; - - use super::*; + use crate::report::types::sort_type::SortType; + use crate::{ConfigWindow, Language, Sniffer, StyleType}; #[test] #[serial] fn test_restore_default_configs() { // initial configs stored are the default ones - assert_eq!(Configs::load(), Configs::default()); - let modified_configs = Configs { + assert_eq!(Conf::load(), Conf::default()); + let modified_conf = Conf { settings: Settings { color_gradient: GradientType::Wild, language: Language::ZH, @@ -109,19 +115,34 @@ fn test_restore_default_configs() { size: SizeTuple(452.0, 870.0), thumbnail_position: PositionTuple(20.0, 20.0), }, + capture_source_picklist: CaptureSourcePicklist::File, + report_sort_type: SortType::Ascending, + host_sort_type: SortType::Descending, + service_sort_type: SortType::Neutral, + filters: Filters { + bpf: "tcp".to_string(), + expanded: true, + }, + import_pcap_path: "whole_day.pcapng".to_string(), + export_pcap: ExportPcap { + enabled: true, + file_name: "sniffnet.pcap".to_string(), + directory: "home".to_string(), + }, + last_opened_setting: SettingsPage::General, }; // we want to be sure that modified config is different from defaults - assert_ne!(Configs::default(), modified_configs); + assert_ne!(Conf::default(), modified_conf); //store modified configs - modified_configs.clone().store().unwrap(); + modified_conf.clone().store().unwrap(); // assert they've been stored - assert_eq!(Configs::load(), modified_configs); + assert_eq!(Conf::load(), modified_conf); // restore defaults - Configs::default().store().unwrap(); + Conf::default().store().unwrap(); // assert that defaults are stored - assert_eq!(Configs::load(), Configs::default()); + assert_eq!(Conf::load(), Conf::default()); // only needed because it will delete config files via its Drop implementation - Sniffer::new(Configs::default()); + Sniffer::new(Conf::default()); } } diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index 5c78278c..c2cdc0f5 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -31,13 +31,14 @@ use crate::report::get_report_entries::get_searched_entries; use crate::report::types::report_col::ReportCol; use crate::report::types::search_parameters::{FilterInputType, SearchParameters}; +use crate::report::types::sort_type::SortType; use crate::translations::translations_2::{ administrative_entity_translation, country_translation, domain_name_translation, no_search_results_translation, only_show_favorites_translation, showing_results_translation, }; use crate::translations::translations_3::filter_by_host_translation; use crate::utils::types::icon::Icon; -use crate::{Language, ReportSortType, RunningPage, Sniffer, StyleType}; +use crate::{Language, RunningPage, Sniffer, StyleType}; /// Computes the body of gui inspect page pub fn inspect_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { @@ -184,7 +185,7 @@ fn report_header_row( language: Language, search_params: &SearchParameters, font: Font, - sort_type: ReportSortType, + sort_type: SortType, data_repr: DataRepr, ) -> Row<'_, Message, StyleType> { let mut ret_val = Row::new().padding([0, 2]).align_y(Alignment::Center); @@ -270,7 +271,7 @@ fn title_report_col_display( } } -fn sort_arrows<'a>(active_sort_type: ReportSortType) -> Container<'a, Message, StyleType> { +fn sort_arrows<'a>(active_sort_type: SortType) -> Container<'a, Message, StyleType> { Container::new( button( active_sort_type diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 093faf72..ee4d7634 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -308,6 +308,7 @@ pub fn update(&mut self, message: Message) -> Task { Message::OpenSettings(settings_page) => { if self.modal.is_none() { self.settings_page = Some(settings_page); + self.conf.last_opened_setting = settings_page; } } Message::OpenLastSettings => { @@ -810,10 +811,7 @@ fn add_or_remove_favorite(&mut self, host: &Host, add: bool) { } fn close_settings(&mut self) { - if let Some(page) = self.settings_page { - self.conf.last_opened_setting = page; - self.settings_page = None; - } + self.settings_page = None; } /// Don't update adjustments to threshold immediately: @@ -885,11 +883,13 @@ fn switch_page(&mut self, next: bool) { match (self.running_page, self.settings_page, self.modal.is_none()) { (_, Some(current_setting), true) => { // Settings opened - if next { - self.settings_page = Some(current_setting.next()); + let new_setting = if next { + current_setting.next() } else { - self.settings_page = Some(current_setting.previous()); - } + current_setting.previous() + }; + self.settings_page = Some(new_setting); + self.conf.last_opened_setting = new_setting; } ( RunningPage::Inspect | RunningPage::Notifications | RunningPage::Overview, @@ -1059,14 +1059,20 @@ mod tests { use serial_test::{parallel, serial}; - use crate::configs::types::config_window::{PositionTuple, SizeTuple}; use crate::countries::types::country::Country; use crate::gui::components::types::my_modal::MyModal; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::types::custom_palette::ExtraStyles; use crate::gui::styles::types::gradient_type::GradientType; + use crate::gui::types::conf::Conf; + use crate::gui::types::config_window::{PositionTuple, SizeTuple}; + use crate::gui::types::export_pcap::ExportPcap; + use crate::gui::types::filters::Filters; use crate::gui::types::message::Message; + use crate::gui::types::settings::Settings; use crate::gui::types::timing_events::TimingEvents; + use crate::networking::types::capture_context::CaptureSourcePicklist; + use crate::networking::types::config_device::ConfigDevice; use crate::networking::types::data_info::DataInfo; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; @@ -1079,30 +1085,15 @@ mod tests { }; use crate::notifications::types::sound::Sound; use crate::report::types::sort_type::SortType; - use crate::{ - ByteMultiple, ConfigDevice, ConfigWindow, Configs, Language, ReportSortType, RunningPage, - Settings, Sniffer, StyleType, - }; + use crate::{ByteMultiple, ConfigWindow, Language, RunningPage, Sniffer, StyleType}; // helpful to clean up files generated from tests impl Drop for Sniffer { fn drop(&mut self) { - let settings_path_str = Settings::test_path(); - let settings_path = Path::new(&settings_path_str); - if settings_path.exists() { - remove_file(Settings::test_path()).unwrap(); - } - - let device_path_str = ConfigDevice::test_path(); - let device_path = Path::new(&device_path_str); - if device_path.exists() { - remove_file(ConfigDevice::test_path()).unwrap(); - } - - let window_path_str = ConfigWindow::test_path(); - let window_path = Path::new(&window_path_str); - if window_path.exists() { - remove_file(ConfigWindow::test_path()).unwrap(); + let conf_path_str = Conf::test_path(); + let conf_path = Path::new(&conf_path_str); + if conf_path.exists() { + remove_file(Conf::test_path()).unwrap(); } } } @@ -1110,7 +1101,7 @@ fn drop(&mut self) { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_update_chart_kind() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); assert_eq!(sniffer.traffic_chart.data_repr, DataRepr::Bytes); sniffer.update(Message::DataReprSelection(DataRepr::Packets)); @@ -1126,104 +1117,87 @@ fn test_correctly_update_chart_kind() { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_update_report_sort_kind() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); - let sort = ReportSortType { - data_sort: SortType::Neutral, - }; + let sort = SortType::Neutral; - assert_eq!(sniffer.report_sort_type, sort); + assert_eq!(sniffer.conf.report_sort_type, sort); sniffer.update(Message::ReportSortSelection(sort.next_sort())); - assert_eq!( - sniffer.report_sort_type, - ReportSortType { - data_sort: SortType::Descending, - } - ); + assert_eq!(sniffer.conf.report_sort_type, SortType::Descending); sniffer.update(Message::ReportSortSelection(sort.next_sort().next_sort())); - assert_eq!( - sniffer.report_sort_type, - ReportSortType { - data_sort: SortType::Ascending, - } - ); + assert_eq!(sniffer.conf.report_sort_type, SortType::Ascending); sniffer.update(Message::ReportSortSelection( sort.next_sort().next_sort().next_sort(), )); - assert_eq!( - sniffer.report_sort_type, - ReportSortType { - data_sort: SortType::Neutral, - } - ); + assert_eq!(sniffer.conf.report_sort_type, SortType::Neutral); } #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_update_host_sort_kind() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); let mut sort = SortType::Neutral; - assert_eq!(sniffer.host_sort_type, sort); + assert_eq!(sniffer.conf.host_sort_type, sort); sort = sort.next_sort(); sniffer.update(Message::HostSortSelection(sort)); - assert_eq!(sniffer.host_sort_type, SortType::Descending); + assert_eq!(sniffer.conf.host_sort_type, SortType::Descending); sort = sort.next_sort(); sniffer.update(Message::HostSortSelection(sort)); - assert_eq!(sniffer.host_sort_type, SortType::Ascending); + assert_eq!(sniffer.conf.host_sort_type, SortType::Ascending); sort = sort.next_sort(); sniffer.update(Message::HostSortSelection(sort)); - assert_eq!(sniffer.host_sort_type, SortType::Neutral); + assert_eq!(sniffer.conf.host_sort_type, SortType::Neutral); } #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_update_service_sort_kind() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); let mut sort = SortType::Neutral; - assert_eq!(sniffer.service_sort_type, sort); + assert_eq!(sniffer.conf.service_sort_type, sort); sort = sort.next_sort(); sniffer.update(Message::ServiceSortSelection(sort)); - assert_eq!(sniffer.service_sort_type, SortType::Descending); + assert_eq!(sniffer.conf.service_sort_type, SortType::Descending); sort = sort.next_sort(); sniffer.update(Message::ServiceSortSelection(sort)); - assert_eq!(sniffer.service_sort_type, SortType::Ascending); + assert_eq!(sniffer.conf.service_sort_type, SortType::Ascending); sort = sort.next_sort(); sniffer.update(Message::ServiceSortSelection(sort)); - assert_eq!(sniffer.service_sort_type, SortType::Neutral); + assert_eq!(sniffer.conf.service_sort_type, SortType::Neutral); } #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_update_style() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); sniffer.update(Message::Style(StyleType::MonAmour)); - assert_eq!(sniffer.configs.settings.style, StyleType::MonAmour); + assert_eq!(sniffer.conf.settings.style, StyleType::MonAmour); sniffer.update(Message::Style(StyleType::Day)); - assert_eq!(sniffer.configs.settings.style, StyleType::Day); + assert_eq!(sniffer.conf.settings.style, StyleType::Day); sniffer.update(Message::Style(StyleType::Night)); - assert_eq!(sniffer.configs.settings.style, StyleType::Night); + assert_eq!(sniffer.conf.settings.style, StyleType::Night); sniffer.update(Message::Style(StyleType::DeepSea)); - assert_eq!(sniffer.configs.settings.style, StyleType::DeepSea); + assert_eq!(sniffer.conf.settings.style, StyleType::DeepSea); sniffer.update(Message::Style(StyleType::DeepSea)); - assert_eq!(sniffer.configs.settings.style, StyleType::DeepSea); + assert_eq!(sniffer.conf.settings.style, StyleType::DeepSea); } #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_dots_pulse_update() { // every kind of message will the integer, but only Periodic will update the string - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); assert_eq!(sniffer.dots_pulse, (".".to_string(), 0)); @@ -1249,7 +1223,7 @@ fn test_dots_pulse_update() { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_modify_favorite_connections() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); // remove 1 sniffer.update(Message::AddOrRemoveFavorite( Host { @@ -1430,16 +1404,22 @@ fn test_modify_favorite_connections() { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_show_and_hide_modal_and_settings() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Notifications); + assert_eq!( + sniffer.conf.last_opened_setting, + SettingsPage::Notifications + ); // open settings sniffer.update(Message::OpenLastSettings); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, Some(SettingsPage::Notifications)); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Notifications); + assert_eq!( + sniffer.conf.last_opened_setting, + SettingsPage::Notifications + ); // switch settings page sniffer.update(Message::OpenSettings(SettingsPage::Appearance)); assert_eq!(sniffer.modal, None); @@ -1451,17 +1431,17 @@ fn test_show_and_hide_modal_and_settings() { sniffer.update(Message::ShowModal(MyModal::Quit)); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, Some(SettingsPage::General)); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Notifications); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::General); // close settings sniffer.update(Message::CloseSettings); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::General); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::General); // reopen settings sniffer.update(Message::OpenLastSettings); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, Some(SettingsPage::General)); - assert_eq!(sniffer.last_opened_setting, SettingsPage::General); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::General); // switch settings page sniffer.update(Message::OpenSettings(SettingsPage::Appearance)); assert_eq!(sniffer.modal, None); @@ -1470,66 +1450,66 @@ fn test_show_and_hide_modal_and_settings() { sniffer.update(Message::CloseSettings); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // open clear all modal sniffer.update(Message::ShowModal(MyModal::ClearAll)); assert_eq!(sniffer.modal, Some(MyModal::ClearAll)); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // try opening settings with clear all modal opened sniffer.update(Message::OpenLastSettings); assert_eq!(sniffer.modal, Some(MyModal::ClearAll)); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // try opening quit modal with clear all modal opened sniffer.update(Message::ShowModal(MyModal::Quit)); assert_eq!(sniffer.modal, Some(MyModal::ClearAll)); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // close clear all modal sniffer.update(Message::HideModal); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // open quit modal sniffer.update(Message::ShowModal(MyModal::Quit)); assert_eq!(sniffer.modal, Some(MyModal::Quit)); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // try opening settings with clear all modal opened sniffer.update(Message::OpenLastSettings); assert_eq!(sniffer.modal, Some(MyModal::Quit)); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // try opening clear all modal with quit modal opened sniffer.update(Message::ShowModal(MyModal::ClearAll)); assert_eq!(sniffer.modal, Some(MyModal::Quit)); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // close quit modal sniffer.update(Message::HideModal); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.last_opened_setting, SettingsPage::Appearance); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); } #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_update_language() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); - assert_eq!(sniffer.configs.settings.language, Language::EN); + assert_eq!(sniffer.conf.settings.language, Language::EN); assert_eq!(sniffer.traffic_chart.language, Language::EN); sniffer.update(Message::LanguageSelection(Language::IT)); - assert_eq!(sniffer.configs.settings.language, Language::IT); + assert_eq!(sniffer.conf.settings.language, Language::IT); assert_eq!(sniffer.traffic_chart.language, Language::IT); sniffer.update(Message::LanguageSelection(Language::IT)); - assert_eq!(sniffer.configs.settings.language, Language::IT); + assert_eq!(sniffer.conf.settings.language, Language::IT); assert_eq!(sniffer.traffic_chart.language, Language::IT); sniffer.update(Message::LanguageSelection(Language::ZH)); - assert_eq!(sniffer.configs.settings.language, Language::ZH); + assert_eq!(sniffer.conf.settings.language, Language::ZH); assert_eq!(sniffer.traffic_chart.language, Language::ZH); } @@ -1551,7 +1531,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { // Simulate an update to apply the settings sniffer.update(Message::Periodic); } - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); let bytes_notification_init = DataNotification { data_repr: DataRepr::Bytes, @@ -1596,36 +1576,36 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { }; // initial default state - assert_eq!(sniffer.configs.settings.notifications.volume, 60); - assert_eq!(sniffer.configs.settings.notifications.volume, 60); + assert_eq!(sniffer.conf.settings.notifications.volume, 60); + assert_eq!(sniffer.conf.settings.notifications.volume, 60); assert_eq!( - sniffer.configs.settings.notifications.data_notification, + sniffer.conf.settings.notifications.data_notification, bytes_notification_init ); assert_eq!( - sniffer.configs.settings.notifications.favorite_notification, + sniffer.conf.settings.notifications.favorite_notification, fav_notification_init ); // change volume sniffer.update(Message::ChangeVolume(95)); - assert_eq!(sniffer.configs.settings.notifications.volume, 95); + assert_eq!(sniffer.conf.settings.notifications.volume, 95); assert_eq!( - sniffer.configs.settings.notifications.data_notification, + sniffer.conf.settings.notifications.data_notification, bytes_notification_init, ); assert_eq!( - sniffer.configs.settings.notifications.favorite_notification, + sniffer.conf.settings.notifications.favorite_notification, fav_notification_init, ); assert_eq!( - sniffer.configs.settings.notifications.data_notification, + sniffer.conf.settings.notifications.data_notification, bytes_notification_init ); assert_eq!( - sniffer.configs.settings.notifications.favorite_notification, + sniffer.conf.settings.notifications.favorite_notification, fav_notification_init ); @@ -1637,7 +1617,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { // Verify that toggling threshold is applied immediately assert_eq!( - sniffer.configs.settings.notifications.data_notification, + sniffer.conf.settings.notifications.data_notification, bytes_notification_toggled_on, ); @@ -1649,19 +1629,19 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { // Verify adjusted threshold is not applied before timeout expires, // and rest is applied immediately assert_eq!( - sniffer.configs.settings.notifications.data_notification, + sniffer.conf.settings.notifications.data_notification, bytes_notification_sound_off_only, ); expire_notifications_timeout(&mut sniffer); - assert_eq!(sniffer.configs.settings.notifications.volume, 95); + assert_eq!(sniffer.conf.settings.notifications.volume, 95); assert_eq!( - sniffer.configs.settings.notifications.data_notification, + sniffer.conf.settings.notifications.data_notification, bytes_notification_adjusted_threshold_sound_off ); assert_eq!( - sniffer.configs.settings.notifications.favorite_notification, + sniffer.conf.settings.notifications.favorite_notification, fav_notification_init, ); @@ -1674,18 +1654,18 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { // Verify threshold is not applied before timeout expires, // and rest is applied immediately assert_eq!( - sniffer.configs.settings.notifications.favorite_notification, + sniffer.conf.settings.notifications.favorite_notification, fav_notification_new, ); // And the rest is intact - assert_eq!(sniffer.configs.settings.notifications.volume, 95); + assert_eq!(sniffer.conf.settings.notifications.volume, 95); assert_eq!( - sniffer.configs.settings.notifications.data_notification, + sniffer.conf.settings.notifications.data_notification, bytes_notification_adjusted_threshold_sound_off ); assert_eq!( - sniffer.configs.settings.notifications.favorite_notification, + sniffer.conf.settings.notifications.favorite_notification, fav_notification_new ); } @@ -1693,7 +1673,7 @@ fn expire_notifications_timeout(sniffer: &mut Sniffer) { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_clear_all_notifications() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); sniffer.logged_notifications.0 = VecDeque::from([LoggedNotification::DataThresholdExceeded( DataThresholdExceeded { @@ -1720,7 +1700,7 @@ fn test_clear_all_notifications() { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_correctly_switch_running_and_settings_pages() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); // initial status assert_eq!(sniffer.settings_page, None); @@ -1729,18 +1709,31 @@ fn test_correctly_switch_running_and_settings_pages() { // nothing changes sniffer.update(Message::SwitchPage(true)); assert_eq!(sniffer.settings_page, None); + assert_eq!( + sniffer.conf.last_opened_setting, + SettingsPage::Notifications + ); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.running_page, RunningPage::Init); // switch settings sniffer.update(Message::OpenLastSettings); assert_eq!(sniffer.settings_page, Some(SettingsPage::Notifications)); + assert_eq!( + sniffer.conf.last_opened_setting, + SettingsPage::Notifications + ); assert_eq!(sniffer.running_page, RunningPage::Init); sniffer.update(Message::SwitchPage(false)); assert_eq!(sniffer.settings_page, Some(SettingsPage::General)); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::General); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.running_page, RunningPage::Init); sniffer.update(Message::SwitchPage(true)); assert_eq!(sniffer.settings_page, Some(SettingsPage::Notifications)); + assert_eq!( + sniffer.conf.last_opened_setting, + SettingsPage::Notifications + ); assert_eq!(sniffer.modal, None); assert_eq!(sniffer.running_page, RunningPage::Init); sniffer.update(Message::CloseSettings); @@ -1767,9 +1760,14 @@ fn test_correctly_switch_running_and_settings_pages() { sniffer.update(Message::OpenLastSettings); assert_eq!(sniffer.running_page, RunningPage::Inspect); assert_eq!(sniffer.settings_page, Some(SettingsPage::Notifications)); + assert_eq!( + sniffer.conf.last_opened_setting, + SettingsPage::Notifications + ); sniffer.update(Message::SwitchPage(true)); assert_eq!(sniffer.running_page, RunningPage::Inspect); assert_eq!(sniffer.settings_page, Some(SettingsPage::Appearance)); + assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // focus the window and try to switch => nothing changes sniffer.update(Message::WindowFocused); @@ -1780,37 +1778,21 @@ fn test_correctly_switch_running_and_settings_pages() { #[test] #[serial] // needed to not collide with other tests generating configs files - fn test_config_settings() { - let path_string = Settings::test_path(); + fn test_conf() { + let path_string = Conf::test_path(); let path = Path::new(&path_string); assert!(!path.exists()); - let mut sniffer = Sniffer::new(Configs::load()); + let mut sniffer = Sniffer::new(Conf::load()); assert!(path.exists()); // check that the current settings are the default ones - let settings_start = sniffer.configs.settings.clone(); - assert_eq!( - settings_start, - Settings { - color_gradient: GradientType::None, - language: Language::EN, - scale_factor: 1.0, - mmdb_country: "".to_string(), - mmdb_asn: "".to_string(), - style_path: "".to_string(), - notifications: Notifications { - volume: 60, - data_notification: Default::default(), - favorite_notification: Default::default() - }, - style: StyleType::Custom(ExtraStyles::A11yDark) - } - ); + let conf_start = sniffer.conf.clone(); + assert_eq!(conf_start, Conf::default(),); - // change some configs by sending messages + // change some conf by sending messages sniffer.update(Message::GradientsSelection(GradientType::Wild)); sniffer.update(Message::LanguageSelection(Language::ZH)); sniffer.update(Message::ChangeScaleFactor(0.0)); @@ -1822,64 +1804,21 @@ fn test_config_settings() { ))); sniffer.update(Message::Style(StyleType::Custom(ExtraStyles::DraculaDark))); sniffer.update(Message::ChangeVolume(100)); - - // quit the app by sending a CloseRequested message - sniffer.update(Message::Quit); - - assert!(path.exists()); - - // check that updated configs are inherited by a new sniffer instance - let settings_end = Sniffer::new(Configs::load()).configs.settings.clone(); - assert_eq!( - settings_end, - Settings { - color_gradient: GradientType::Wild, - language: Language::ZH, - scale_factor: 1.0, - mmdb_country: "countrymmdb".to_string(), - mmdb_asn: "asnmmdb".to_string(), - style_path: format!( - "{}/resources/themes/catppuccin.toml", - env!("CARGO_MANIFEST_DIR") - ), - notifications: Notifications { - volume: 100, - data_notification: Default::default(), - favorite_notification: Default::default() - }, - style: StyleType::Custom(ExtraStyles::DraculaDark) - } - ); - } - - #[test] - #[serial] // needed to not collide with other tests generating configs files - fn test_config_window() { - let path_string = ConfigWindow::test_path(); - let path = Path::new(&path_string); - - assert!(!path.exists()); - - let mut sniffer = Sniffer::new(Configs::load()); - - assert!(path.exists()); - - // check that the current window properties are the default ones - let window_start = sniffer.configs.window; - assert_eq!( - window_start, - ConfigWindow { - position: PositionTuple(0.0, 0.0), - size: SizeTuple(1190.0, 670.0), - thumbnail_position: PositionTuple(0.0, 0.0), - } - ); - - // change window properties by sending messages sniffer.update(Message::WindowMoved(-10.0, 555.0)); sniffer.update(Message::WindowResized(1000.0, 999.0)); sniffer.thumbnail = true; sniffer.update(Message::WindowMoved(40.0, 40.0)); + sniffer.update(Message::SetCaptureSource(CaptureSourcePicklist::File)); + sniffer.update(Message::ToggleFilters); + sniffer.update(Message::BpfFilter("tcp or udp".to_string())); + sniffer.update(Message::ReportSortSelection(SortType::Ascending)); + sniffer.update(Message::HostSortSelection(SortType::Descending)); + sniffer.update(Message::ServiceSortSelection(SortType::Descending)); + sniffer.update(Message::OpenSettings(SettingsPage::Appearance)); + sniffer.update(Message::ToggleExportPcap); + sniffer.update(Message::OutputPcapFile("test.cap".to_string())); + sniffer.update(Message::OutputPcapDir("/".to_string())); + sniffer.update(Message::SetPcapImport("/test.pcap".to_string())); // quit the app by sending a CloseRequested message sniffer.update(Message::Quit); @@ -1887,13 +1826,48 @@ fn test_config_window() { assert!(path.exists()); // check that updated configs are inherited by a new sniffer instance - let window_end = Sniffer::new(Configs::load()).configs.window.clone(); + let conf_end = Sniffer::new(Conf::load()).conf.clone(); assert_eq!( - window_end, - ConfigWindow { - position: PositionTuple(-10.0, 555.0), - size: SizeTuple(1000.0, 999.0), - thumbnail_position: PositionTuple(40.0, 40.0), + conf_end, + Conf { + settings: Settings { + color_gradient: GradientType::Wild, + language: Language::ZH, + scale_factor: 1.0, + mmdb_country: "countrymmdb".to_string(), + mmdb_asn: "asnmmdb".to_string(), + style_path: format!( + "{}/resources/themes/catppuccin.toml", + env!("CARGO_MANIFEST_DIR") + ), + notifications: Notifications { + volume: 100, + data_notification: Default::default(), + favorite_notification: Default::default() + }, + style: StyleType::Custom(ExtraStyles::DraculaDark), + }, + window: ConfigWindow { + position: PositionTuple(-10.0, 555.0), + size: SizeTuple(1000.0, 999.0), + thumbnail_position: PositionTuple(40.0, 40.0), + }, + device: ConfigDevice::default(), + capture_source_picklist: CaptureSourcePicklist::File, + filters: Filters { + expanded: true, + bpf: "tcp or udp".to_string(), + }, + report_sort_type: SortType::Ascending, + host_sort_type: SortType::Descending, + service_sort_type: SortType::Descending, + last_opened_setting: SettingsPage::Appearance, + export_pcap: ExportPcap { + enabled: true, + file_name: "test.cap".to_string(), + directory: "/".to_string() + }, + import_pcap_path: "/test.pcap".to_string(), } ); } @@ -1901,95 +1875,95 @@ fn test_config_window() { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_window_resized() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); assert!(!sniffer.thumbnail); - let factor = sniffer.configs.settings.scale_factor; + let factor = sniffer.conf.settings.scale_factor; assert_eq!(factor, 1.0); - assert_eq!(sniffer.configs.window.size, SizeTuple(1190.0, 670.0)); + assert_eq!(sniffer.conf.window.size, SizeTuple(1190.0, 670.0)); assert_eq!( ConfigWindow::thumbnail_size(factor), SizeTuple(360.0, 222.0) ); sniffer.update(Message::WindowResized(850.0, 600.0)); - assert_eq!(sniffer.configs.window.size, SizeTuple(850.0, 600.0)); + assert_eq!(sniffer.conf.window.size, SizeTuple(850.0, 600.0)); sniffer.update(Message::ChangeScaleFactor(0.369)); - let factor = sniffer.configs.settings.scale_factor; + let factor = sniffer.conf.settings.scale_factor; assert_eq!(factor, 1.5); assert_eq!( ConfigWindow::thumbnail_size(factor), SizeTuple(540.0, 333.0) ); sniffer.update(Message::WindowResized(1000.0, 800.0)); - assert_eq!(sniffer.configs.window.size, SizeTuple(1500.0, 1200.0)); + assert_eq!(sniffer.conf.window.size, SizeTuple(1500.0, 1200.0)); sniffer.update(Message::ChangeScaleFactor(-0.631)); - let factor = sniffer.configs.settings.scale_factor; + let factor = sniffer.conf.settings.scale_factor; assert_eq!(factor, 0.5); assert_eq!( ConfigWindow::thumbnail_size(factor), SizeTuple(180.0, 111.0) ); sniffer.update(Message::WindowResized(1000.0, 800.0)); - assert_eq!(sniffer.configs.window.size, SizeTuple(500.0, 400.0)); + assert_eq!(sniffer.conf.window.size, SizeTuple(500.0, 400.0)); } #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_window_moved() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); assert!(!sniffer.thumbnail); - assert_eq!(sniffer.configs.settings.scale_factor, 1.0); - assert_eq!(sniffer.configs.window.position, PositionTuple(0.0, 0.0)); + assert_eq!(sniffer.conf.settings.scale_factor, 1.0); + assert_eq!(sniffer.conf.window.position, PositionTuple(0.0, 0.0)); assert_eq!( - sniffer.configs.window.thumbnail_position, + sniffer.conf.window.thumbnail_position, PositionTuple(0.0, 0.0) ); sniffer.update(Message::WindowMoved(850.0, 600.0)); - assert_eq!(sniffer.configs.window.position, PositionTuple(850.0, 600.0)); + assert_eq!(sniffer.conf.window.position, PositionTuple(850.0, 600.0)); assert_eq!( - sniffer.configs.window.thumbnail_position, + sniffer.conf.window.thumbnail_position, PositionTuple(0.0, 0.0) ); sniffer.thumbnail = true; sniffer.update(Message::WindowMoved(400.0, 600.0)); - assert_eq!(sniffer.configs.window.position, PositionTuple(850.0, 600.0)); + assert_eq!(sniffer.conf.window.position, PositionTuple(850.0, 600.0)); assert_eq!( - sniffer.configs.window.thumbnail_position, + sniffer.conf.window.thumbnail_position, PositionTuple(400.0, 600.0) ); sniffer.update(Message::ChangeScaleFactor(0.369)); - assert_eq!(sniffer.configs.settings.scale_factor, 1.5); + assert_eq!(sniffer.conf.settings.scale_factor, 1.5); sniffer.update(Message::WindowMoved(20.0, 40.0)); - assert_eq!(sniffer.configs.window.position, PositionTuple(850.0, 600.0)); + assert_eq!(sniffer.conf.window.position, PositionTuple(850.0, 600.0)); assert_eq!( - sniffer.configs.window.thumbnail_position, + sniffer.conf.window.thumbnail_position, PositionTuple(30.0, 60.0) ); sniffer.thumbnail = false; sniffer.update(Message::WindowMoved(-20.0, 300.0)); - assert_eq!(sniffer.configs.window.position, PositionTuple(-30.0, 450.0)); + assert_eq!(sniffer.conf.window.position, PositionTuple(-30.0, 450.0)); assert_eq!( - sniffer.configs.window.thumbnail_position, + sniffer.conf.window.thumbnail_position, PositionTuple(30.0, 60.0) ); sniffer.update(Message::ChangeScaleFactor(-0.631)); - assert_eq!(sniffer.configs.settings.scale_factor, 0.5); + assert_eq!(sniffer.conf.settings.scale_factor, 0.5); sniffer.update(Message::WindowMoved(500.0, -100.0)); - assert_eq!(sniffer.configs.window.position, PositionTuple(250.0, -50.0)); + assert_eq!(sniffer.conf.window.position, PositionTuple(250.0, -50.0)); assert_eq!( - sniffer.configs.window.thumbnail_position, + sniffer.conf.window.thumbnail_position, PositionTuple(30.0, 60.0) ); sniffer.thumbnail = true; sniffer.update(Message::WindowMoved(-2.0, -34.0)); - assert_eq!(sniffer.configs.window.position, PositionTuple(250.0, -50.0)); + assert_eq!(sniffer.conf.window.position, PositionTuple(250.0, -50.0)); assert_eq!( - sniffer.configs.window.thumbnail_position, + sniffer.conf.window.thumbnail_position, PositionTuple(-1.0, -17.0) ); } @@ -1997,7 +1971,7 @@ fn test_window_moved() { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_toggle_thumbnail() { - let mut sniffer = Sniffer::new(Configs::default()); + let mut sniffer = Sniffer::new(Conf::default()); assert!(!sniffer.thumbnail); assert!(!sniffer.traffic_chart.thumbnail); @@ -2024,21 +1998,21 @@ fn test_toggle_thumbnail() { #[test] #[parallel] // needed to not collide with other tests generating configs files fn test_scale_factor_shortcut() { - let mut sniffer = Sniffer::new(Configs::default()); - assert_eq!(sniffer.configs.settings.scale_factor, 1.0); + let mut sniffer = Sniffer::new(Conf::default()); + assert_eq!(sniffer.conf.settings.scale_factor, 1.0); sniffer.update(Message::ScaleFactorShortcut(true)); - assert_eq!(sniffer.configs.settings.scale_factor, 1.1); + assert_eq!(sniffer.conf.settings.scale_factor, 1.1); sniffer.update(Message::ScaleFactorShortcut(false)); - assert_eq!(sniffer.configs.settings.scale_factor, 1.0); + assert_eq!(sniffer.conf.settings.scale_factor, 1.0); sniffer.update(Message::ScaleFactorShortcut(false)); - assert_eq!(sniffer.configs.settings.scale_factor, 0.9); + assert_eq!(sniffer.conf.settings.scale_factor, 0.9); for _ in 0..100 { sniffer.update(Message::ScaleFactorShortcut(true)); } assert_eq!( - format!("{:.2}", sniffer.configs.settings.scale_factor), + format!("{:.2}", sniffer.conf.settings.scale_factor), "3.00".to_string() ); @@ -2046,7 +2020,7 @@ fn test_scale_factor_shortcut() { sniffer.update(Message::ScaleFactorShortcut(false)); } assert_eq!( - format!("{:.2}", sniffer.configs.settings.scale_factor), + format!("{:.2}", sniffer.conf.settings.scale_factor), "0.30".to_string() ); } diff --git a/src/gui/types/conf.rs b/src/gui/types/conf.rs index 615690a9..2d82653b 100644 --- a/src/gui/types/conf.rs +++ b/src/gui/types/conf.rs @@ -5,10 +5,12 @@ use crate::gui::types::settings::Settings; use crate::networking::types::capture_context::CaptureSourcePicklist; use crate::networking::types::config_device::ConfigDevice; -use crate::report::types::report_sort_type::ReportSortType; use crate::report::types::sort_type::SortType; +#[cfg(not(test))] use crate::utils::error_logger::{ErrorLogger, Location}; +#[cfg(not(test))] use crate::{SNIFFNET_LOWERCASE, location}; +#[cfg(not(test))] use confy::ConfyError; use serde::{Deserialize, Serialize}; @@ -27,7 +29,7 @@ pub struct Conf { /// BPF filter program to be applied to the capture pub filters: Filters, /// Report sort type (inspect page) - pub report_sort_type: ReportSortType, + pub report_sort_type: SortType, /// Host sort type (overview page) pub host_sort_type: SortType, /// Service sort type (overview page) @@ -64,7 +66,6 @@ pub fn store(self) -> Result<(), ConfyError> { #[cfg(test)] mod tests { - use crate::Settings; use crate::gui::types::conf::Conf; impl Conf { @@ -73,12 +74,11 @@ pub fn test_path() -> String { } pub fn load() -> Self { - confy::load_path::(Settings::test_path()) - .unwrap_or_else(|_| Settings::default()) + confy::load_path::(Conf::test_path()).unwrap_or_else(|_| Conf::default()) } pub fn store(self) -> Result<(), confy::ConfyError> { - confy::store_path(Settings::test_path(), self) + confy::store_path(Conf::test_path(), self) } } } diff --git a/src/gui/types/export_pcap.rs b/src/gui/types/export_pcap.rs index 52896fc6..9cd79743 100644 --- a/src/gui/types/export_pcap.rs +++ b/src/gui/types/export_pcap.rs @@ -3,9 +3,9 @@ #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct ExportPcap { - enabled: bool, - file_name: String, - directory: String, + pub(crate) enabled: bool, + pub(crate) file_name: String, + pub(crate) directory: String, } impl ExportPcap { diff --git a/src/gui/types/filters.rs b/src/gui/types/filters.rs index cf545778..e56b88ea 100644 --- a/src/gui/types/filters.rs +++ b/src/gui/types/filters.rs @@ -2,8 +2,8 @@ #[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)] pub struct Filters { - expanded: bool, - bpf: String, + pub(crate) expanded: bool, + pub(crate) bpf: String, } impl Filters { diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs index 34455cc5..07689920 100644 --- a/src/gui/types/message.rs +++ b/src/gui/types/message.rs @@ -14,7 +14,7 @@ use crate::report::types::sort_type::SortType; use crate::utils::types::file_info::FileInfo; use crate::utils::types::web_page::WebPage; -use crate::{Language, ReportSortType, StyleType}; +use crate::{Language, StyleType}; #[derive(Debug, Clone)] /// Messages types that permit reacting to application interactions/subscriptions @@ -34,7 +34,7 @@ pub enum Message { /// Select data representation to use DataReprSelection(DataRepr), /// Select report sort type to be displayed (inspect page) - ReportSortSelection(ReportSortType), + ReportSortSelection(SortType), /// Select host sort type to be displayed (overview page) HostSortSelection(SortType), /// Select service sort type to be displayed (overview page) diff --git a/src/main.rs b/src/main.rs index c424db58..e7066d6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use iced::advanced::graphics::image::image_rs::ImageFormat; #[cfg(target_os = "linux")] use iced::window::settings::PlatformSpecific; -use iced::{Font, Pixels, Settings, application, window}; +use iced::{Font, Pixels, application, window}; use chart::types::traffic_chart::TrafficChart; use cli::handle_cli_args; @@ -20,7 +20,6 @@ use networking::types::ip_version::IpVersion; use networking::types::protocol::Protocol; use networking::types::service::Service; -use report::types::report_sort_type::ReportSortType; use translations::types::language::Language; use utils::formatted_strings::print_cli_welcome_message; @@ -78,7 +77,7 @@ pub fn main() -> iced::Result { let ConfigWindow { size, position, .. } = conf.window; application(SNIFFNET_TITLECASE, Sniffer::update, Sniffer::view) - .settings(Settings { + .settings(iced::Settings { // id needed for Linux Wayland; should match StartupWMClass in .desktop file; see issue #292 id: Some(String::from(SNIFFNET_LOWERCASE)), fonts: vec![ diff --git a/src/report/get_report_entries.rs b/src/report/get_report_entries.rs index 52624291..1f6646c4 100644 --- a/src/report/get_report_entries.rs +++ b/src/report/get_report_entries.rs @@ -50,7 +50,7 @@ pub fn get_searched_entries( all_results.sort_by(|&(_, a), &(_, b)| { a.compare( b, - sniffer.conf.report_sort_type.data_sort, + sniffer.conf.report_sort_type, sniffer.traffic_chart.data_repr, ) }); diff --git a/src/report/types/mod.rs b/src/report/types/mod.rs index ebbc80a9..588104e0 100644 --- a/src/report/types/mod.rs +++ b/src/report/types/mod.rs @@ -1,4 +1,3 @@ pub mod report_col; -pub mod report_sort_type; pub mod search_parameters; pub mod sort_type; diff --git a/src/report/types/report_sort_type.rs b/src/report/types/report_sort_type.rs deleted file mode 100644 index da23bbfb..00000000 --- a/src/report/types/report_sort_type.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::fmt::Debug; - -use crate::gui::styles::button::ButtonType; -use crate::gui::styles::types::style_type::StyleType; -use crate::report::types::sort_type::SortType; -use iced::widget::Text; -use serde::{Deserialize, Serialize}; - -/// Struct representing the possible kinds of sort for displayed relevant connections. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] -pub struct ReportSortType { - pub data_sort: SortType, -} - -impl ReportSortType { - pub fn next_sort(self) -> Self { - Self { - data_sort: self.data_sort.next_sort(), - } - } - - pub fn icon<'a>(self) -> Text<'a, StyleType> { - self.data_sort.icon() - } - - pub fn button_type(self) -> ButtonType { - self.data_sort.button_type() - } -} - -#[cfg(test)] -mod tests { - use crate::report::types::report_sort_type::ReportSortType; - use crate::report::types::sort_type::SortType; - - #[test] - fn test_next_report_sort() { - let mut sort = ReportSortType::default(); - assert_eq!( - sort, - ReportSortType { - data_sort: SortType::Neutral, - } - ); - - sort = sort.next_sort(); - assert_eq!( - sort, - ReportSortType { - data_sort: SortType::Descending, - } - ); - - sort = sort.next_sort(); - assert_eq!( - sort, - ReportSortType { - data_sort: SortType::Ascending, - } - ); - - sort = sort.next_sort(); - assert_eq!( - sort, - ReportSortType { - data_sort: SortType::Neutral, - } - ); - - sort = sort.next_sort(); - assert_eq!( - sort, - ReportSortType { - data_sort: SortType::Descending, - } - ); - } -} From 8591aed6440530bb9ce543af68b9ae275eea8d22 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 29 Aug 2025 00:56:23 +0200 Subject: [PATCH 56/74] save last opened running page in configurations --- src/cli/mod.rs | 2 + src/gui/components/header.rs | 3 +- src/gui/pages/overview_page.rs | 92 +++++++++++---------- src/gui/pages/settings_general_page.rs | 4 +- src/gui/pages/types/running_page.rs | 12 +-- src/gui/sniffer.rs | 110 +++++++++++++++---------- src/gui/types/conf.rs | 3 + 7 files changed, 128 insertions(+), 98 deletions(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 887a4367..033afb8b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -70,6 +70,7 @@ pub fn handle_cli_args() -> Task { mod tests { use serial_test::serial; + use crate::gui::pages::types::running_page::RunningPage; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::styles::types::custom_palette::ExtraStyles; use crate::gui::styles::types::gradient_type::GradientType; @@ -130,6 +131,7 @@ fn test_restore_default_configs() { directory: "home".to_string(), }, last_opened_setting: SettingsPage::General, + last_opened_page: RunningPage::Inspect, }; // we want to be sure that modified config is different from defaults assert_ne!(Conf::default(), modified_conf); diff --git a/src/gui/components/header.rs b/src/gui/components/header.rs index fd087efd..5f0b980a 100644 --- a/src/gui/components/header.rs +++ b/src/gui/components/header.rs @@ -6,7 +6,6 @@ use iced::{Alignment, Font, Length}; use crate::gui::components::tab::notifications_badge; -use crate::gui::pages::types::running_page::RunningPage; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::sniffer::Sniffer; use crate::gui::styles::button::ButtonType; @@ -42,7 +41,7 @@ pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { } let last_opened_setting = sniffer.conf.last_opened_setting; - let is_running = sniffer.running_page.ne(&RunningPage::Init); + let is_running = sniffer.running_page.is_some(); let logo = Icon::Sniffnet .to_text() diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index d433d3e3..41ed59df 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -62,57 +62,65 @@ pub fn overview_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let mut body = Column::new(); let mut tab_and_body = Column::new().height(Length::Fill); - let dots = &sniffer.dots_pulse.0; + // some packets are there! + let tabs = get_pages_tabs( + RunningPage::Overview, + font, + font_headers, + language, + sniffer.unread_notifications, + ); + tab_and_body = tab_and_body.push(tabs); - if let Some(error) = sniffer.pcap_error.as_ref() { - // pcap threw an ERROR! - body = body_pcap_error(error, dots, language, font); - } else { - // NO pcap error detected - let tot_packets = sniffer - .info_traffic - .tot_data_info - .tot_data(DataRepr::Packets); + let container_chart = container_chart(sniffer, font); - if tot_packets == 0 { - // no packets observed at all - body = body_no_packets(&sniffer.capture_source, font, language, dots); - } else { - // some packets are there! - let tabs = get_pages_tabs( - RunningPage::Overview, - font, - font_headers, - language, - sniffer.unread_notifications, - ); - tab_and_body = tab_and_body.push(tabs); + let container_info = col_info(sniffer); - let container_chart = container_chart(sniffer, font); + let container_report = row_report(sniffer); - let container_info = col_info(sniffer); - - let container_report = row_report(sniffer); - - body = body - .width(Length::Fill) - .padding(10) + body = body + .width(Length::Fill) + .padding(10) + .spacing(10) + .align_x(Alignment::Center) + .push( + Row::new() + .height(280) .spacing(10) - .align_x(Alignment::Center) - .push( - Row::new() - .height(280) - .spacing(10) - .push(container_info) - .push(container_chart), - ) - .push(container_report); - } - } + .push(container_info) + .push(container_chart), + ) + .push(container_report); Container::new(Column::new().push(tab_and_body.push(body))).height(Length::Fill) } +pub fn waiting_page(sniffer: &Sniffer) -> Option> { + let Settings { + style, language, .. + } = sniffer.conf.settings; + let font = style.get_extension().font; + + let dots = &sniffer.dots_pulse.0; + + let tot_packets = sniffer + .info_traffic + .tot_data_info + .tot_data(DataRepr::Packets); + + let body = if let Some(error) = sniffer.pcap_error.as_ref() { + // pcap threw an ERROR! + body_pcap_error(error, dots, language, font) + } else if tot_packets == 0 { + // no packets observed at all + body_no_packets(&sniffer.capture_source, font, language, dots) + } else { + return None; + }; + + Some(Container::new(Column::new().push(body)).height(Length::Fill)) +} + fn body_no_packets<'a>( cs: &CaptureSource, font: Font, diff --git a/src/gui/pages/settings_general_page.rs b/src/gui/pages/settings_general_page.rs index e54a7a28..902b9ad0 100644 --- a/src/gui/pages/settings_general_page.rs +++ b/src/gui/pages/settings_general_page.rs @@ -26,7 +26,7 @@ use crate::utils::types::file_info::FileInfo; use crate::utils::types::icon::Icon; use crate::utils::types::web_page::WebPage; -use crate::{Language, RunningPage, Sniffer, StyleType}; +use crate::{Language, Sniffer, StyleType}; pub fn settings_general_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> { let Settings { @@ -66,7 +66,7 @@ fn column_all_general_setting(sniffer: &Sniffer, font: Font) -> Column<'_, Messa .. } = sniffer.conf.settings.clone(); - let is_editable = sniffer.running_page.eq(&RunningPage::Init); + let is_editable = sniffer.running_page.is_none(); let mut column = Column::new() .align_x(Alignment::Center) diff --git a/src/gui/pages/types/running_page.rs b/src/gui/pages/types/running_page.rs index a37817c8..f0693964 100644 --- a/src/gui/pages/types/running_page.rs +++ b/src/gui/pages/types/running_page.rs @@ -3,13 +3,13 @@ use crate::translations::translations_2::inspect_translation; use crate::utils::types::icon::Icon; use crate::{Language, StyleType}; +use serde::{Deserialize, Serialize}; -/// This enum defines the current GUI page. -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +/// This enum defines the current running page. +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Default)] pub enum RunningPage { - /// Initial page. - Init, /// Overview page. + #[default] Overview, /// Inspect page. Inspect, @@ -29,7 +29,6 @@ pub fn get_tab_label(&self, language: Language) -> &str { RunningPage::Overview => overview_translation(language), RunningPage::Inspect => inspect_translation(language), RunningPage::Notifications => notifications_translation(language), - RunningPage::Init => "", } } @@ -38,7 +37,6 @@ pub fn next(self) -> Self { RunningPage::Overview => RunningPage::Inspect, RunningPage::Inspect => RunningPage::Notifications, RunningPage::Notifications => RunningPage::Overview, - RunningPage::Init => RunningPage::Init, } } @@ -47,7 +45,6 @@ pub fn previous(self) -> Self { RunningPage::Overview => RunningPage::Notifications, RunningPage::Inspect => RunningPage::Overview, RunningPage::Notifications => RunningPage::Inspect, - RunningPage::Init => RunningPage::Init, } } @@ -56,7 +53,6 @@ pub fn icon<'a>(self) -> iced::widget::Text<'a, StyleType> { RunningPage::Overview => Icon::Overview, RunningPage::Inspect => Icon::Inspect, RunningPage::Notifications => Icon::Notification, - RunningPage::Init => Icon::Sniffnet, } .to_text() } diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index ee4d7634..bd73ed90 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -1,5 +1,6 @@ //! Module defining the application structure: messages, updates, subscriptions. +use crate::gui::pages::overview_page::waiting_page; use async_channel::Receiver; use iced::Event::{Keyboard, Window}; use iced::keyboard::key::Named; @@ -102,8 +103,8 @@ pub struct Sniffer { pub modal: Option, /// Currently displayed settings page; None if settings is closed pub settings_page: Option, - /// Defines the current running page - pub running_page: RunningPage, + /// Defines the current running page; None if initial page + pub running_page: Option, /// Number of unread notifications pub unread_notifications: usize, /// Search parameters of inspect page @@ -147,7 +148,7 @@ pub fn new(conf: Conf) -> Self { traffic_chart: TrafficChart::new(style, language), modal: None, settings_page: None, - running_page: RunningPage::Init, + running_page: None, unread_notifications: 0, search: SearchParameters::default(), page_number: 1, @@ -318,7 +319,8 @@ pub fn update(&mut self, message: Message) -> Task { } Message::CloseSettings => self.close_settings(), Message::ChangeRunningPage(running_page) => { - self.running_page = running_page; + self.running_page = Some(running_page); + self.conf.last_opened_page = running_page; if running_page.eq(&RunningPage::Notifications) { self.unread_notifications = 0; } @@ -358,7 +360,8 @@ pub fn update(&mut self, message: Message) -> Task { self.host_data_states.update_states(¶meters); self.page_number = 1; - self.running_page = RunningPage::Inspect; + self.running_page = Some(RunningPage::Inspect); + self.conf.last_opened_page = RunningPage::Inspect; self.search = parameters; } Message::UpdatePageNumber(increment) => { @@ -371,7 +374,9 @@ pub fn update(&mut self, message: Message) -> Task { } } Message::ArrowPressed(increment) => { - if self.running_page.eq(&RunningPage::Inspect) + if self + .running_page + .is_some_and(|p| p.eq(&RunningPage::Inspect)) && self.settings_page.is_none() && self.modal.is_none() { @@ -460,7 +465,10 @@ pub fn update(&mut self, message: Message) -> Task { window::change_level(window_id, Level::AlwaysOnTop), ]) } else { - if self.running_page.eq(&RunningPage::Notifications) { + if self + .running_page + .is_some_and(|p| p.eq(&RunningPage::Notifications)) + { self.unread_notifications = 0; } let mut commands = vec![ @@ -484,7 +492,7 @@ pub fn update(&mut self, message: Message) -> Task { } } Message::CtrlTPressed => { - if self.running_page.ne(&RunningPage::Init) + if self.running_page.is_some() && self.settings_page.is_none() && self.modal.is_none() && !self.timing_events.was_just_thumbnail_enter() @@ -553,10 +561,18 @@ pub fn view(&self) -> Element<'_, Message, StyleType> { thumbnail_page(self) } else { match self.running_page { - RunningPage::Init => initial_page(self), - RunningPage::Overview => overview_page(self), - RunningPage::Inspect => inspect_page(self), - RunningPage::Notifications => notifications_page(self), + None => initial_page(self), + Some(running_page) => { + if let Some(waiting_page) = waiting_page(self) { + waiting_page + } else { + match running_page { + RunningPage::Overview => overview_page(self), + RunningPage::Inspect => inspect_page(self), + RunningPage::Notifications => notifications_page(self), + } + } + } } }; @@ -664,7 +680,11 @@ fn refresh_data(&mut self, mut msg: InfoTraffic, no_more_packets: bool) { &self.favorite_hosts, &self.capture_source, ); - if self.thumbnail || self.running_page.ne(&RunningPage::Notifications) { + if self.thumbnail + || self + .running_page + .is_some_and(|p| p.ne(&RunningPage::Notifications)) + { self.unread_notifications += emitted_notifications; } self.traffic_chart.update_charts_data(&msg, no_more_packets); @@ -703,7 +723,7 @@ fn start(&mut self) -> Task { let capture_context = CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.conf.filters); self.pcap_error = capture_context.error().map(ToString::to_string); - self.running_page = RunningPage::Overview; + self.running_page = Some(self.conf.last_opened_page); if capture_context.error().is_none() { // no pcap error @@ -759,7 +779,7 @@ fn reset(&mut self) { self.traffic_chart = TrafficChart::new(style, language); self.modal = None; self.settings_page = None; - self.running_page = RunningPage::Init; + self.running_page = None; self.unread_notifications = 0; self.search = SearchParameters::default(); self.page_number = 1; @@ -891,20 +911,21 @@ fn switch_page(&mut self, next: bool) { self.settings_page = Some(new_setting); self.conf.last_opened_setting = new_setting; } - ( - RunningPage::Inspect | RunningPage::Notifications | RunningPage::Overview, - None, - true, - ) => { + (Some(current_page), None, true) => { // Running with no overlays if self.info_traffic.tot_data_info.tot_data(DataRepr::Packets) > 0 { // Running with no overlays and some packets - self.running_page = if next { - self.running_page.next() + let new_page = if next { + current_page.next() } else { - self.running_page.previous() + current_page.previous() }; - if self.running_page.eq(&RunningPage::Notifications) { + self.running_page = Some(new_page); + self.conf.last_opened_page = new_page; + if self + .running_page + .is_some_and(|p| p.eq(&RunningPage::Notifications)) + { self.unread_notifications = 0; } } @@ -914,10 +935,7 @@ fn switch_page(&mut self, next: bool) { } fn shortcut_return(&mut self) -> Task { - if self.running_page.eq(&RunningPage::Init) - && self.settings_page.is_none() - && self.modal.is_none() - { + if self.running_page.is_none() && self.settings_page.is_none() && self.modal.is_none() { return Task::done(Message::Start); } else if self.modal.eq(&Some(MyModal::Reset)) { return Task::done(Message::Reset); @@ -940,7 +958,7 @@ fn shortcut_esc(&mut self) -> Task { // also called when the backspace shortcut is pressed fn reset_button_pressed(&mut self) -> Task { - if self.running_page.ne(&RunningPage::Init) { + if self.running_page.is_some() { let tot_packets = self.info_traffic.tot_data_info.tot_data(DataRepr::Packets); return if tot_packets == 0 && self.settings_page.is_none() { Task::done(Message::Reset) @@ -953,7 +971,7 @@ fn reset_button_pressed(&mut self) -> Task { fn quit_wrapper(&mut self) -> Task { let tot_packets = self.info_traffic.tot_data_info.tot_data(DataRepr::Packets); - if self.running_page.eq(&RunningPage::Init) || tot_packets == 0 { + if self.running_page.is_none() || tot_packets == 0 { Task::done(Message::Quit) } else if self.thumbnail { // TODO: uncomment once issue #653 is fixed @@ -968,7 +986,9 @@ fn quit_wrapper(&mut self) -> Task { } fn shortcut_ctrl_d(&mut self) -> Task { - if self.running_page.eq(&RunningPage::Notifications) + if self + .running_page + .is_some_and(|p| p.eq(&RunningPage::Notifications)) && !self.logged_notifications.0.is_empty() { return Task::done(Message::ShowModal(MyModal::ClearAll)); @@ -1705,7 +1725,7 @@ fn test_correctly_switch_running_and_settings_pages() { // initial status assert_eq!(sniffer.settings_page, None); assert_eq!(sniffer.modal, None); - assert_eq!(sniffer.running_page, RunningPage::Init); + assert!(sniffer.running_page.is_none()); // nothing changes sniffer.update(Message::SwitchPage(true)); assert_eq!(sniffer.settings_page, None); @@ -1714,7 +1734,7 @@ fn test_correctly_switch_running_and_settings_pages() { SettingsPage::Notifications ); assert_eq!(sniffer.modal, None); - assert_eq!(sniffer.running_page, RunningPage::Init); + assert!(sniffer.running_page.is_none()); // switch settings sniffer.update(Message::OpenLastSettings); assert_eq!(sniffer.settings_page, Some(SettingsPage::Notifications)); @@ -1722,12 +1742,12 @@ fn test_correctly_switch_running_and_settings_pages() { sniffer.conf.last_opened_setting, SettingsPage::Notifications ); - assert_eq!(sniffer.running_page, RunningPage::Init); + assert!(sniffer.running_page.is_none()); sniffer.update(Message::SwitchPage(false)); assert_eq!(sniffer.settings_page, Some(SettingsPage::General)); assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::General); assert_eq!(sniffer.modal, None); - assert_eq!(sniffer.running_page, RunningPage::Init); + assert!(sniffer.running_page.is_none()); sniffer.update(Message::SwitchPage(true)); assert_eq!(sniffer.settings_page, Some(SettingsPage::Notifications)); assert_eq!( @@ -1735,18 +1755,18 @@ fn test_correctly_switch_running_and_settings_pages() { SettingsPage::Notifications ); assert_eq!(sniffer.modal, None); - assert_eq!(sniffer.running_page, RunningPage::Init); + assert!(sniffer.running_page.is_none()); sniffer.update(Message::CloseSettings); assert_eq!(sniffer.settings_page, None); - assert_eq!(sniffer.running_page, RunningPage::Init); + assert!(sniffer.running_page.is_none()); // change state to running - sniffer.running_page = RunningPage::Overview; + sniffer.running_page = Some(RunningPage::Overview); assert_eq!(sniffer.settings_page, None); assert_eq!(sniffer.modal, None); - assert_eq!(sniffer.running_page, RunningPage::Overview); + assert_eq!(sniffer.running_page, Some(RunningPage::Overview)); // switch with closed setting and no packets received => nothing changes sniffer.update(Message::SwitchPage(true)); - assert_eq!(sniffer.running_page, RunningPage::Overview); + assert_eq!(sniffer.running_page, Some(RunningPage::Overview)); assert_eq!(sniffer.settings_page, None); // switch with closed setting and some packets received => change running page sniffer @@ -1754,25 +1774,25 @@ fn test_correctly_switch_running_and_settings_pages() { .tot_data_info .add_packet(0, TrafficDirection::Outgoing); sniffer.update(Message::SwitchPage(true)); - assert_eq!(sniffer.running_page, RunningPage::Inspect); + assert_eq!(sniffer.running_page, Some(RunningPage::Inspect)); assert_eq!(sniffer.settings_page, None); // switch with opened settings => change settings sniffer.update(Message::OpenLastSettings); - assert_eq!(sniffer.running_page, RunningPage::Inspect); + assert_eq!(sniffer.running_page, Some(RunningPage::Inspect)); assert_eq!(sniffer.settings_page, Some(SettingsPage::Notifications)); assert_eq!( sniffer.conf.last_opened_setting, SettingsPage::Notifications ); sniffer.update(Message::SwitchPage(true)); - assert_eq!(sniffer.running_page, RunningPage::Inspect); + assert_eq!(sniffer.running_page, Some(RunningPage::Inspect)); assert_eq!(sniffer.settings_page, Some(SettingsPage::Appearance)); assert_eq!(sniffer.conf.last_opened_setting, SettingsPage::Appearance); // focus the window and try to switch => nothing changes sniffer.update(Message::WindowFocused); sniffer.update(Message::SwitchPage(true)); - assert_eq!(sniffer.running_page, RunningPage::Inspect); + assert_eq!(sniffer.running_page, Some(RunningPage::Inspect)); assert_eq!(sniffer.settings_page, Some(SettingsPage::Appearance)); } @@ -1819,6 +1839,7 @@ fn test_conf() { sniffer.update(Message::OutputPcapFile("test.cap".to_string())); sniffer.update(Message::OutputPcapDir("/".to_string())); sniffer.update(Message::SetPcapImport("/test.pcap".to_string())); + sniffer.update(Message::ChangeRunningPage(RunningPage::Notifications)); // quit the app by sending a CloseRequested message sniffer.update(Message::Quit); @@ -1862,6 +1883,7 @@ fn test_conf() { host_sort_type: SortType::Descending, service_sort_type: SortType::Descending, last_opened_setting: SettingsPage::Appearance, + last_opened_page: RunningPage::Notifications, export_pcap: ExportPcap { enabled: true, file_name: "test.cap".to_string(), diff --git a/src/gui/types/conf.rs b/src/gui/types/conf.rs index 2d82653b..fd0d82f8 100644 --- a/src/gui/types/conf.rs +++ b/src/gui/types/conf.rs @@ -1,3 +1,4 @@ +use crate::gui::pages::types::running_page::RunningPage; use crate::gui::pages::types::settings_page::SettingsPage; use crate::gui::types::config_window::ConfigWindow; use crate::gui::types::export_pcap::ExportPcap; @@ -36,6 +37,8 @@ pub struct Conf { pub service_sort_type: SortType, /// Remembers the last opened setting page pub last_opened_setting: SettingsPage, + /// Remembers the last opened running page + pub last_opened_page: RunningPage, /// Information about PCAP file export pub export_pcap: ExportPcap, /// Import path for PCAP file From 0348eff5ecfd094cdf464d5c66b1d29cf2295928 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 29 Aug 2025 17:40:05 +0200 Subject: [PATCH 57/74] use serde default to allow deserializing missing Conf fields --- src/gui/types/conf.rs | 4 ++-- src/gui/types/config_window.rs | 1 + src/gui/types/export_pcap.rs | 1 + src/gui/types/filters.rs | 1 + src/gui/types/settings.rs | 1 + src/networking/types/config_device.rs | 1 + src/notifications/types/notifications.rs | 3 +++ 7 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/gui/types/conf.rs b/src/gui/types/conf.rs index fd0d82f8..34417cc8 100644 --- a/src/gui/types/conf.rs +++ b/src/gui/types/conf.rs @@ -18,6 +18,7 @@ pub static CONF: std::sync::LazyLock = std::sync::LazyLock::new(Conf::load); #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)] +#[serde(default)] pub struct Conf { /// Parameters from settings pages pub settings: Settings, @@ -55,8 +56,7 @@ pub fn load() -> Self { if let Ok(conf) = confy::load::(SNIFFNET_LOWERCASE, Self::FILE_NAME) { conf } else { - let _ = confy::store(SNIFFNET_LOWERCASE, Self::FILE_NAME, Conf::default()) - .log_err(location!()); + let _ = Conf::default().store(); Conf::default() } } diff --git a/src/gui/types/config_window.rs b/src/gui/types/config_window.rs index 3864f520..fa855d3c 100644 --- a/src/gui/types/config_window.rs +++ b/src/gui/types/config_window.rs @@ -8,6 +8,7 @@ pub struct SizeTuple(pub f32, pub f32); #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Debug)] +#[serde(default)] pub struct ConfigWindow { pub position: PositionTuple, pub size: SizeTuple, diff --git a/src/gui/types/export_pcap.rs b/src/gui/types/export_pcap.rs index 9cd79743..dd463170 100644 --- a/src/gui/types/export_pcap.rs +++ b/src/gui/types/export_pcap.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[serde(default)] pub struct ExportPcap { pub(crate) enabled: bool, pub(crate) file_name: String, diff --git a/src/gui/types/filters.rs b/src/gui/types/filters.rs index e56b88ea..e23e9f15 100644 --- a/src/gui/types/filters.rs +++ b/src/gui/types/filters.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)] +#[serde(default)] pub struct Filters { pub(crate) expanded: bool, pub(crate) bpf: String, diff --git a/src/gui/types/settings.rs b/src/gui/types/settings.rs index 187d9d90..d6b7aa33 100644 --- a/src/gui/types/settings.rs +++ b/src/gui/types/settings.rs @@ -5,6 +5,7 @@ use crate::{Language, StyleType}; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[serde(default)] pub struct Settings { pub color_gradient: GradientType, pub language: Language, diff --git a/src/networking/types/config_device.rs b/src/networking/types/config_device.rs index ad2445ab..c3d993f7 100644 --- a/src/networking/types/config_device.rs +++ b/src/networking/types/config_device.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[serde(default)] pub struct ConfigDevice { pub device_name: String, } diff --git a/src/notifications/types/notifications.rs b/src/notifications/types/notifications.rs index f7d6b5fe..7a9c9d05 100644 --- a/src/notifications/types/notifications.rs +++ b/src/notifications/types/notifications.rs @@ -6,6 +6,7 @@ /// Used to contain the notifications configuration set by the user #[derive(Clone, Serialize, Deserialize, Copy, PartialEq, Debug)] +#[serde(default)] pub struct Notifications { pub volume: u8, pub data_notification: DataNotification, @@ -32,6 +33,7 @@ pub enum Notification { } #[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy)] +#[serde(default)] pub struct DataNotification { /// Data representation pub data_repr: DataRepr, @@ -101,6 +103,7 @@ pub fn from(value: &str, existing: Option) -> Self { } #[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy)] +#[serde(default)] pub struct FavoriteNotification { /// Flag to determine if this notification is enabled pub notify_on_favorite: bool, From 6941d8d3c9b6f46cc2c4fa9e98ea39dd1791fbfc Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 29 Aug 2025 17:54:22 +0200 Subject: [PATCH 58/74] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a67adb4..7a3bf12b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ## [UNRELEASED] - Added _bits_ data representation ([#936](https://github.com/GyulyVGC/sniffnet/pull/936) — fixes [#506](https://github.com/GyulyVGC/sniffnet/issues/506)) - An AppImage of Sniffnet is now available ([#859](https://github.com/GyulyVGC/sniffnet/pull/859) — fixes [#900](https://github.com/GyulyVGC/sniffnet/issues/900)) - Added Dutch translation 🇳🇱 ([#854](https://github.com/GyulyVGC/sniffnet/pull/854)) +- Improved configurations persistence across different runs of the app ([#938](https://github.com/GyulyVGC/sniffnet/pull/938) — fixes [#507](https://github.com/GyulyVGC/sniffnet/issues/507)) - The Windows Installer is now signed with a code signing certificate provided by the [SignPath Foundation](https://signpath.org/) ([#897](https://github.com/GyulyVGC/sniffnet/pull/897) — fixes [#894](https://github.com/GyulyVGC/sniffnet/issues/894)) - Updated some of the existing translations to v1.4: - German ([#833](https://github.com/GyulyVGC/sniffnet/pull/833)) From 24d7f221b32c29edd4209fcdd6c42ec6d0723e13 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Fri, 29 Aug 2025 23:31:05 +0200 Subject: [PATCH 59/74] set capture source picklist to device when starting from CLI with -a flag --- src/cli/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 033afb8b..138a88cc 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,6 +1,7 @@ use crate::SNIFFNET_LOWERCASE; use crate::gui::types::conf::{CONF, Conf}; use crate::gui::types::message::Message; +use crate::networking::types::capture_context::CaptureSourcePicklist; use crate::utils::formatted_strings::APP_VERSION; use clap::Parser; use iced::{Task, window}; @@ -58,7 +59,12 @@ pub fn handle_cli_args() -> Task { .map(Message::StartApp) .chain(Task::done(Message::Periodic)); if let Some(adapter) = args.adapter { + // TODO: check if this works once #653 is fixed + // currently the link type and device name aren't displayed properly when starting from CLI boot_task_chain = boot_task_chain + .chain(Task::done(Message::SetCaptureSource( + CaptureSourcePicklist::Device, + ))) .chain(Task::done(Message::DeviceSelection(adapter))) .chain(Task::done(Message::Start)); } From f7b2b4183f5a445fddbda64af5ef3240cf64d278 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 30 Aug 2025 00:41:59 +0200 Subject: [PATCH 60/74] adding Linus SLL link type (WIP) --- src/networking/parse_packets.rs | 29 ++++++++++------------------ src/networking/types/my_link_type.rs | 4 ++++ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index 17b85c75..f6265210 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -25,9 +25,7 @@ use crate::utils::types::timestamp::Timestamp; use async_channel::Sender; use dns_lookup::lookup_addr; -use etherparse::err::ip::{HeaderError, LaxHeaderSliceError}; -use etherparse::err::{Layer, LenError}; -use etherparse::{LaxPacketHeaders, LenSource}; +use etherparse::LaxPacketHeaders; use pcap::{Address, Device, Packet}; use std::collections::HashMap; use std::net::IpAddr; @@ -95,7 +93,7 @@ pub fn parse_packets( } } Ok(packet) => { - if let Ok(headers) = get_sniffable_headers(&packet, my_link_type) { + if let Some(headers) = get_sniffable_headers(&packet, my_link_type) { #[allow(clippy::useless_conversion)] let secs = i64::from(packet.header.ts.tv_sec); #[allow(clippy::useless_conversion)] @@ -281,27 +279,22 @@ pub fn parse_packets( fn get_sniffable_headers<'a>( packet: &'a Packet, my_link_type: MyLinkType, -) -> Result, LaxHeaderSliceError> { +) -> Option> { match my_link_type { MyLinkType::Ethernet(_) | MyLinkType::Unsupported(_) | MyLinkType::NotYetAssigned => { - LaxPacketHeaders::from_ethernet(packet).map_err(LaxHeaderSliceError::Len) + LaxPacketHeaders::from_ethernet(packet).ok() } MyLinkType::RawIp(_) | MyLinkType::IPv4(_) | MyLinkType::IPv6(_) => { - LaxPacketHeaders::from_ip(packet) + LaxPacketHeaders::from_ip(packet).ok() } + MyLinkType::LinuxSll(_) => LaxPacketHeaders::from_linux_sll(packet).ok(), MyLinkType::Null(_) | MyLinkType::Loop(_) => from_null(packet), } } -fn from_null(packet: &[u8]) -> Result, LaxHeaderSliceError> { +fn from_null(packet: &[u8]) -> Option> { if packet.len() <= 4 { - return Err(LaxHeaderSliceError::Len(LenError { - required_len: 4, - len: packet.len(), - len_source: LenSource::Slice, - layer: Layer::Ethernet2Header, - layer_start_offset: 0, - })); + return None; } let is_valid_af_inet = { @@ -322,11 +315,9 @@ fn matches(value: u32) -> bool { }; if is_valid_af_inet { - LaxPacketHeaders::from_ip(&packet[4..]) + LaxPacketHeaders::from_ip(&packet[4..]).ok() } else { - Err(LaxHeaderSliceError::Content( - HeaderError::UnsupportedIpVersion { version_number: 0 }, - )) + None } } diff --git a/src/networking/types/my_link_type.rs b/src/networking/types/my_link_type.rs index 4730523b..b12ec1ac 100644 --- a/src/networking/types/my_link_type.rs +++ b/src/networking/types/my_link_type.rs @@ -12,6 +12,7 @@ pub enum MyLinkType { Loop(Linktype), IPv4(Linktype), IPv6(Linktype), + LinuxSll(Linktype), Unsupported(Linktype), #[default] NotYetAssigned, @@ -30,6 +31,8 @@ pub fn from_pcap_link_type(link_type: Linktype) -> Self { Linktype::LOOP => Self::Loop(link_type), Linktype::IPV4 => Self::IPv4(link_type), Linktype::IPV6 => Self::IPv6(link_type), + Linktype::LINUX_SLL => Self::LinuxSll(link_type), + // TODO: also add Linktype::LINUX_SLL2 (???) _ => Self::Unsupported(link_type), } } @@ -42,6 +45,7 @@ pub fn full_print_on_one_line(self, language: Language) -> String { | Self::Loop(l) | Self::IPv4(l) | Self::IPv6(l) + | Self::LinuxSll(l) | Self::Unsupported(l) => { format!( "{}: {} ({})", From c195e73a5ccc529ed8cbe631f529df66300550f9 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 30 Aug 2025 10:43:33 +0200 Subject: [PATCH 61/74] don't show 'no addresses' warning when link type is Linux SLL --- src/gui/pages/overview_page.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 41ed59df..5b48613a 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -23,6 +23,7 @@ use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; +use crate::networking::types::my_link_type::MyLinkType; use crate::networking::types::service::Service; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; use crate::report::types::search_parameters::SearchParameters; @@ -144,7 +145,7 @@ fn body_no_packets<'a>( .align_x(Alignment::Center) .font(font), ) - } else if cs.get_addresses().is_empty() { + } else if cs.get_addresses().is_empty() && !matches!(link_type, MyLinkType::LinuxSll(_)) { ( Icon::Warning.to_text().size(60), no_addresses_translation(language, &cs_info) From 68beb02ed584863637b81d3c0801a59535c0538f Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 30 Aug 2025 11:29:11 +0200 Subject: [PATCH 62/74] use all interfaces addresses when link type is Linux SLL --- src/gui/sniffer.rs | 7 +------ src/networking/parse_packets.rs | 9 ++------- src/networking/types/capture_context.rs | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index bd73ed90..91354cf4 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -799,13 +799,8 @@ fn set_device(&mut self, name: &str) { fn fetch_devices(&mut self) { self.my_devices.clear(); + self.capture_source.set_addresses(); for dev in Device::list().log_err(location!()).unwrap_or_default() { - if matches!(&self.capture_source, CaptureSource::Device(_)) - && dev.name.eq(&self.capture_source.get_name()) - { - // refresh active addresses - self.capture_source.set_addresses(dev.addresses.clone()); - } let my_dev = MyDevice::from_pcap_device(dev); self.my_devices.push(my_dev); } diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index f6265210..b1802dd7 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -26,7 +26,7 @@ use async_channel::Sender; use dns_lookup::lookup_addr; use etherparse::LaxPacketHeaders; -use pcap::{Address, Device, Packet}; +use pcap::{Address, Packet}; use std::collections::HashMap; use std::net::IpAddr; use std::sync::{Arc, Mutex}; @@ -422,12 +422,7 @@ fn maybe_send_tick_run_live( new_hosts_to_send.lock().unwrap().drain(..).collect(), false, )); - for dev in Device::list().log_err(location!()).unwrap_or_default() { - if dev.name.eq(&cs.get_name()) { - cs.set_addresses(dev.addresses); - break; - } - } + cs.set_addresses(); } } diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index ee31363c..11c10268 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -1,11 +1,13 @@ use crate::gui::types::conf::Conf; use crate::gui::types::filters::Filters; +use crate::location; use crate::networking::types::my_device::MyDevice; use crate::networking::types::my_link_type::MyLinkType; use crate::translations::translations::network_adapter_translation; use crate::translations::translations_4::capture_file_translation; use crate::translations::types::language::Language; -use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat}; +use crate::utils::error_logger::{ErrorLogger, Location}; +use pcap::{Active, Address, Capture, Device, Error, Packet, Savefile, Stat}; use serde::{Deserialize, Serialize}; pub enum CaptureContext { @@ -184,9 +186,18 @@ pub fn get_addresses(&self) -> &Vec
{ } } - pub fn set_addresses(&mut self, addresses: Vec
) { - if let Self::Device(device) = self { - device.set_addresses(addresses); + pub fn set_addresses(&mut self) { + if let Self::Device(my_device) = self { + let mut addresses = Vec::new(); + for dev in Device::list().log_err(location!()).unwrap_or_default() { + if matches!(my_device.get_link_type(), MyLinkType::LinuxSll(_)) { + addresses.extend(dev.addresses); + } else if dev.name.eq(my_device.get_name()) { + addresses.extend(dev.addresses); + break; + } + } + my_device.set_addresses(addresses); } } From 57179b5122c401a4ab70a34c368e415f5ea7b54a Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 30 Aug 2025 11:57:45 +0200 Subject: [PATCH 63/74] set capture source addresses also right before starting capture --- src/gui/sniffer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 91354cf4..e30aea97 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -731,6 +731,7 @@ fn start(&mut self) -> Task { let mmdb_readers = self.mmdb_readers.clone(); self.capture_source .set_link_type(capture_context.my_link_type()); + self.capture_source.set_addresses(); let capture_source = self.capture_source.clone(); self.traffic_chart .change_capture_source(matches!(capture_source, CaptureSource::Device(_))); From 1224ce765833f2809a5706517491a708ee93dcd3 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 30 Aug 2025 14:22:05 +0200 Subject: [PATCH 64/74] correctly parse link layer header in presence of Linux SLL packets --- src/networking/manage_packets.rs | 37 +++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index c34b328d..a8120356 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use etherparse::{EtherType, LaxPacketHeaders, LinkHeader, NetHeaders, TransportHeader}; +use etherparse::{ + ArpHardwareId, EtherType, LaxPacketHeaders, LinkHeader, NetHeaders, TransportHeader, +}; use pcap::Address; use crate::networking::types::address_port_pair::AddressPortPair; @@ -81,13 +83,28 @@ fn analyze_link_header( mac_address2: &mut Option, exchanged_bytes: &mut u128, ) { - if let Some(LinkHeader::Ethernet2(header)) = link_header { - *exchanged_bytes += 14; - *mac_address1 = Some(mac_from_dec_to_hex(header.source)); - *mac_address2 = Some(mac_from_dec_to_hex(header.destination)); - } else { - *mac_address1 = None; - *mac_address2 = None; + match link_header { + Some(LinkHeader::Ethernet2(header)) => { + *exchanged_bytes += 14; + *mac_address1 = Some(mac_from_dec_to_hex(header.source)); + *mac_address2 = Some(mac_from_dec_to_hex(header.destination)); + } + Some(LinkHeader::LinuxSll(header)) => { + *exchanged_bytes += 16; + *mac_address1 = if header.sender_address_valid_length == 6 + && header.arp_hrd_type == ArpHardwareId::ETHERNET + && let Ok(sender) = header.sender_address[0..6].try_into() + { + Some(mac_from_dec_to_hex(sender)) + } else { + None + }; + *mac_address2 = None; + } + None => { + *mac_address1 = None; + *mac_address2 = None; + } } } @@ -151,7 +168,7 @@ fn analyze_network_header( *arp_type = ArpType::from_etherparse(arp_packet.operation); true } - _ => false, + None => false, } } @@ -192,7 +209,7 @@ fn analyze_transport_header( *icmp_type = IcmpTypeV6::from_etherparse(&icmpv6_header.icmp_type); true } - _ => false, + None => false, } } From bc4cdc8e6043fbe27ffa3711759e23ce6a2352d7 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 30 Aug 2025 22:45:35 +0200 Subject: [PATCH 65/74] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a3bf12b..95a39dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ # Changelog ## [UNRELEASED] - Enhanced traffic filtering capabilities: Berkeley Packet Filter ([#937](https://github.com/GyulyVGC/sniffnet/pull/937) — fixes [#810](https://github.com/GyulyVGC/sniffnet/issues/810)) +- Added support for `Linux SLL` link type, enabling to monitor the `any` interface on Linux ([#945](https://github.com/GyulyVGC/sniffnet/pull/945)) - Added _bits_ data representation ([#936](https://github.com/GyulyVGC/sniffnet/pull/936) — fixes [#506](https://github.com/GyulyVGC/sniffnet/issues/506)) - An AppImage of Sniffnet is now available ([#859](https://github.com/GyulyVGC/sniffnet/pull/859) — fixes [#900](https://github.com/GyulyVGC/sniffnet/issues/900)) - Added Dutch translation 🇳🇱 ([#854](https://github.com/GyulyVGC/sniffnet/pull/854)) From a9982c8c95b993da29ef050c766b33d13d118962 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sat, 30 Aug 2025 23:54:41 +0200 Subject: [PATCH 66/74] fix clippy lints --- src/translations/translations_2.rs | 44 ++++++------------------------ src/translations/translations_3.rs | 6 ++-- 2 files changed, 12 insertions(+), 38 deletions(-) diff --git a/src/translations/translations_2.rs b/src/translations/translations_2.rs index da62c7e0..684b947b 100644 --- a/src/translations/translations_2.rs +++ b/src/translations/translations_2.rs @@ -1,5 +1,3 @@ -#![allow(clippy::match_same_arms, clippy::match_wildcard_for_single_variants)] - use crate::Language; pub fn new_version_available_translation(language: Language) -> &'static str { @@ -55,7 +53,6 @@ pub fn inspect_translation(language: Language) -> &'static str { Language::ID => "Memeriksa", Language::NL => "Inspecteren", Language::EL => "Επιθεώρηση", - _ => "Inspect", } } @@ -84,7 +81,6 @@ pub fn connection_details_translation(language: Language) -> &'static str { Language::ID => "Rincian koneksi", Language::NL => "Verbindingsdetails", Language::EL => "Λεπτομέρειες σύνδεσης", - _ => "Connection details", } } @@ -96,7 +92,7 @@ pub fn dropped_translation(language: Language) -> &'static str { Language::RU => "Потеряно", Language::SV => "Tappade", Language::FI => "Pudotetut", - Language::DE => "Verloren", + Language::DE | Language::NL => "Verloren", Language::TR => "Düşen", // Language::FA => "رها شده", Language::ES | Language::PT => "Perdidos", @@ -111,9 +107,7 @@ pub fn dropped_translation(language: Language) -> &'static str { Language::UZ => "Yig'ilgan", Language::VI => "Mất", Language::ID => "Dihapus", - Language::NL => "Verloren", Language::EL => "Απορριμμένα", - _ => "Dropped", } } @@ -142,7 +136,6 @@ pub fn data_representation_translation(language: Language) -> &'static str { Language::ID => "Penyajian ulang data", Language::NL => "Gegevensweergave", Language::EL => "Αναπαράσταση δεδομένων", - _ => "Data representation", } } @@ -171,7 +164,6 @@ pub fn host_translation(language: Language) -> &'static str { Language::ID => "Jaringan asal", Language::NL => "Netwerk host", Language::EL => "Κόμβος δικτύου", - _ => "Network host", } } @@ -200,7 +192,6 @@ pub fn only_top_30_items_translation(language: Language) -> &'static str { Language::ID => "Hanya 30 teratas yang ditampilkan disini", Language::NL => "Alleen de bovenste 30 items worden hier weergegeven", Language::EL => "Εμφανίζονται μόνο τα κορυφαία 30 στοιχεία", - _ => "Only the top 30 items are displayed here", } } @@ -255,7 +246,6 @@ pub fn local_translation(language: Language) -> &'static str { Language::ID => "Jaringan lokal", Language::NL => "Lokaal netwerk", Language::EL => "Τοπικό δίκτυο", - _ => "Local network", } } @@ -284,7 +274,6 @@ pub fn unknown_translation(language: Language) -> &'static str { Language::ID => "Lokasi tidak diketahui", Language::NL => "Onbekende locatie", Language::EL => "Άγνωστη τοποθεσία", - _ => "Unknown location", } } @@ -313,7 +302,6 @@ pub fn your_network_adapter_translation(language: Language) -> &'static str { Language::ID => "Adaptor jaringan kamu", Language::NL => "Uw netwerkadapter", Language::EL => "Ο προσαρμογέας δικτύου σας", - _ => "Your network adapter", } } @@ -342,7 +330,6 @@ pub fn socket_address_translation(language: Language) -> &'static str { Language::ID => "Alamat sambungan", Language::NL => "Socket adres", Language::EL => "Διεύθυνση υποδοχής", - _ => "Socket address", } } @@ -371,13 +358,12 @@ pub fn mac_address_translation(language: Language) -> &'static str { Language::ID => "Alamat MAC", Language::NL => "MAC-adres", Language::EL => "Διεύθυνση MAC", - _ => "MAC address", } } pub fn source_translation(language: Language) -> &'static str { match language { - Language::EN => "Source", + Language::EN | Language::FR => "Source", Language::IT => "Sorgente", Language::RU => "Источник", Language::SV => "Källa", @@ -392,7 +378,6 @@ pub fn source_translation(language: Language) -> &'static str { Language::UK => "Джерело", Language::RO => "Sursă", Language::PL => "Źródło", - Language::FR => "Source", Language::JA => "送信元", Language::UZ => "Manba", Language::PT => "Fonte", @@ -400,13 +385,12 @@ pub fn source_translation(language: Language) -> &'static str { Language::ID => "Asal", Language::NL => "Bron", Language::EL => "Πηγή", - _ => "Source", } } pub fn destination_translation(language: Language) -> &'static str { match language { - Language::EN | Language::SV => "Destination", + Language::EN | Language::SV | Language::FR => "Destination", Language::IT => "Destinazione", Language::RU => "Получатель", Language::FI => "Määränpää", @@ -420,14 +404,12 @@ pub fn destination_translation(language: Language) -> &'static str { Language::UK => "Призначення", Language::RO => "Destinație", Language::PL => "Miejsce docelowe", - Language::FR => "Destination", Language::JA => "送信先", Language::UZ => "Qabul qiluvchi", Language::VI => "Đích", Language::ID => "Tujuan", Language::NL => "Bestemming", Language::EL => "Προορισμός", - _ => "Destination", } } @@ -443,8 +425,7 @@ pub fn fqdn_translation(language: Language) -> &'static str { // Language::FA => "نام دامنه جامع الشرایط", Language::ES => "Nombre de dominio completo", Language::KO => "절대 도메인 네임", - Language::ZH | Language::JA => "FQDN", - Language::ZH_TW => "FQDN", + Language::ZH | Language::JA | Language::ZH_TW => "FQDN", Language::UK => "Повністю визначене доменне ім'я", Language::RO => "Nume de domeniu complet calificat", Language::PL => "Pełna nazwa domeny", @@ -455,7 +436,6 @@ pub fn fqdn_translation(language: Language) -> &'static str { Language::ID => "Nama domain yang memenuhi syarat", Language::NL => "Volledig gekwalificeerde domeinnaam", Language::EL => "Πλήρως προσδιορισμένο όνομα τομέα", - _ => "Fully qualified domain name", } } @@ -484,7 +464,6 @@ pub fn administrative_entity_translation(language: Language) -> &'static str { Language::ID => "Nama System Otomatis", Language::NL => "Naam van het autonome systeem", Language::EL => "Όνομα αυτόνομου συστήματος", - _ => "Autonomous System name", } } @@ -513,7 +492,6 @@ pub fn transmitted_data_translation(language: Language) -> &'static str { Language::ID => "Data terkirim", Language::NL => "Verzonden gegevens", Language::EL => "Μεταδιδόμενα δεδομένα", - _ => "Transmitted data", } } @@ -522,9 +500,8 @@ pub fn country_translation(language: Language) -> &'static str { Language::EN => "Country", Language::IT => "Paese", Language::RU => "Страна", - Language::SV => "Land", + Language::SV | Language::DE | Language::NL => "Land", Language::FI => "Maa", - Language::DE => "Land", Language::TR => "Ülke", // Language::FA => "کشور", Language::ES | Language::PT => "País", @@ -539,9 +516,7 @@ pub fn country_translation(language: Language) -> &'static str { Language::UZ => "Davlat", Language::VI => "Quốc gia", Language::ID => "Negara", - Language::NL => "Land", Language::EL => "Χώρα", - _ => "Country", } } @@ -570,7 +545,6 @@ pub fn domain_name_translation(language: Language) -> &'static str { Language::ID => "Nama Domain", Language::NL => "Domeinnaam", Language::EL => "Όνομα τομέα", - _ => "Domain name", } } @@ -599,7 +573,6 @@ pub fn only_show_favorites_translation(language: Language) -> &'static str { Language::ID => "Hanya tunjukkan favorit", Language::NL => "Toon alleen favorieten", Language::EL => "Εμφάνιση μόνο αγαπημένων", - _ => "Only show favorites", } } @@ -654,8 +627,9 @@ pub fn no_search_results_translation(language: Language) -> &'static str { Language::VI => "Không có kết quả nào theo các bộ lọc được chỉ định", Language::ID => "Tidak ada hasil berdasarkan filter pencarian spesifik", Language::NL => "Geen resultaten beschikbaar volgens de opgegeven zoekfilters", - Language::EL => "Δεν υπάρχουν διαθέσιμα αποτελέσματα σύμφωνα με τα καθορισμένα φίλτρα αναζήτησης", - _ => "No result available according to the specified search filters", + Language::EL => { + "Δεν υπάρχουν διαθέσιμα αποτελέσματα σύμφωνα με τα καθορισμένα φίλτρα αναζήτησης" + } } } @@ -691,7 +665,6 @@ pub fn showing_results_translation( format!("{start}-{end} van de {total} totale resultaten worden weergegeven") } Language::EL => format!("Εμφάνιση {start}-{end} από {total} συνολικά αποτελέσματα"), - _ => format!("Showing {start}-{end} of {total} total results"), } } @@ -720,6 +693,5 @@ pub fn color_gradients_translation(language: Language) -> &'static str { Language::ID => "Aplikasikan gradasi warna", Language::NL => "Kleurverlopen toepassen", Language::EL => "Εφαρμογή χρωματικών διαβαθμίσεων", - _ => "Apply color gradients", } } diff --git a/src/translations/translations_3.rs b/src/translations/translations_3.rs index 0b8d195c..23a0b1a4 100644 --- a/src/translations/translations_3.rs +++ b/src/translations/translations_3.rs @@ -1,4 +1,4 @@ -#![allow(clippy::match_same_arms)] +#![allow(clippy::match_same_arms, clippy::match_wildcard_for_single_variants)] use iced::widget::Text; @@ -112,7 +112,9 @@ pub fn params_not_editable_translation(language: Language) -> &'static str { Language::UK => "Наступні параметри не можна змінювати під час аналізу трафіку", Language::ID => "Parameter berikut tidak bisa diubah saat dianalisa", Language::NL => "De volgende parameters kunnen niet worden aangepast tijdens de analyse", - Language::EL => "Οι ακόλουθες παράμετροι δεν μπορούν να τροποποιηθούν κατά τη διάρκεια της ανάλυσης", + Language::EL => { + "Οι ακόλουθες παράμετροι δεν μπορούν να τροποποιηθούν κατά τη διάρκεια της ανάλυσης" + } _ => "The following parameters can't be modified during the analysis", } } From 42357ebd0b4df0005083a62103c1df423ec78011 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 31 Aug 2025 00:02:41 +0200 Subject: [PATCH 67/74] add missing greek characters --- resources/fonts/full/subset_characters.txt | 8 ++++++-- .../subset/sarasa-mono-sc-bold.subset.ttf | Bin 240972 -> 240932 bytes .../subset/sarasa-mono-sc-regular.subset.ttf | Bin 243840 -> 243796 bytes 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/fonts/full/subset_characters.txt b/resources/fonts/full/subset_characters.txt index 9e06465c..64a7260b 100644 --- a/resources/fonts/full/subset_characters.txt +++ b/resources/fonts/full/subset_characters.txt @@ -150,6 +150,7 @@ z Ț ț ʼ +Ά Έ Ή Ό @@ -158,15 +159,20 @@ z Γ Δ Ε +Θ Κ +Λ Μ Ν Ξ +Ο Π Ρ Σ Τ +Υ Φ +Χ ά έ ή @@ -333,10 +339,8 @@ z ừ ử ữ -• … → -⏎ ─ │ ╭ diff --git a/resources/fonts/subset/sarasa-mono-sc-bold.subset.ttf b/resources/fonts/subset/sarasa-mono-sc-bold.subset.ttf index 096c63618afd22c828abc0691c1e4e9226e7dc63..be9659205fe9f4760c68d833150f4fd237c94d33 100644 GIT binary patch delta 12952 zcmb7~30O_r|NlRqwf8P%rjUrtb4W5&luL$gGG%s+mn#t&%5*YS<|z&hl8BT_2br!h zGjXoC$=Hd6u4^7L^ndRYUHAKYzQ5n|f6nvTXRozB^ICiDeRf^WF1J0qoQ9Z)D&Qa! zx&PF)Th(J#Kan&w61^kby?XZ>Te0Z^v_BJ-i*xTcz|Af(K98iJ?L@W~z5CgPC#!HQ#6!>f9x86AAG9)#ynJ{it zkFgHJCi#)%oQ2-D6VOvFpz0hL+=O<$3BJ<B{izM%+~w(m2qBx9YfM8QDS= z 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 E1uQh?*8l(j diff --git a/resources/fonts/subset/sarasa-mono-sc-regular.subset.ttf b/resources/fonts/subset/sarasa-mono-sc-regular.subset.ttf index 60b6f9b419dc9034e46ede336c5bb4fff4ae6f4d..71dde4682203b7a797a103ccf284bcb68f4acc7d 100644 GIT binary patch delta 13183 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 XWj`*=)5JnH(3RYb^b09+cA@_Q>pBl< From 4c13a0accdddeed3ce639c7bf67abe844b01715d Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 31 Aug 2025 00:09:11 +0200 Subject: [PATCH 68/74] update CHANGELOG --- CHANGELOG.md | 1 + src/translations/types/language.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a3bf12b..746f037c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ## [UNRELEASED] - Simplified Chinese ([#838](https://github.com/GyulyVGC/sniffnet/pull/838)) - Japanese ([#849](https://github.com/GyulyVGC/sniffnet/pull/849)) - French ([#864](https://github.com/GyulyVGC/sniffnet/pull/864)) + - Greek ([#879](https://github.com/GyulyVGC/sniffnet/pull/879)) - Fix support for IPinfo's databases (the most recent version renamed the `country` field to `country_code`) ## [1.4.0] - 2025-06-27 diff --git a/src/translations/types/language.rs b/src/translations/types/language.rs index 5d8bc77a..2c7aba79 100644 --- a/src/translations/types/language.rs +++ b/src/translations/types/language.rs @@ -129,6 +129,7 @@ pub fn is_up_to_date(self) -> bool { | Language::ZH | Language::JA | Language::FR + | Language::EL ) } } From 3e19ff5ec67c5ca506878be12eae81840e2b4018 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 00:13:02 +0200 Subject: [PATCH 69/74] docs: add aris1009 as a contributor for translation (#946) * docs: update CONTRIBUTORS.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 21 +++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index e69e69f4..c2617241 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -758,6 +758,15 @@ "contributions": [ "platform" ] + }, + { + "login": "aris1009", + "name": "Aris Konstantoulas", + "avatar_url": "https://avatars.githubusercontent.com/u/25184469?v=4", + "profile": "https://github.com/aris1009", + "contributions": [ + "translation" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0be7fc41..64ae175b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -19,92 +19,93 @@ AmadeusGraves
AmadeusGraves

🌍 Angelos Bousis
Angelos Bousis

🌍 Antoine Colombier
Antoine Colombier

⚠️ 🌍 + Aris Konstantoulas
Aris Konstantoulas

🌍 BugsQuanti
BugsQuanti

🌍 Charpy
Charpy

🌍 - Christoph Wanja
Christoph Wanja

💵 + Christoph Wanja
Christoph Wanja

💵 Colin Delahunty
Colin Delahunty

⚠️ Cornelius Roemer
Cornelius Roemer

🤔 CosminPerRam
CosminPerRam

💻 Cristiano
Cristiano

💻 🤔 Cthulu201
Cthulu201

💵 Dinar Shagaliev
Dinar Shagaliev

🌍 - Dominic Kim
Dominic Kim

🌍 + Dominic Kim
Dominic Kim

🌍 Echo
Echo

💵 Embers-of-the-Fire
Embers-of-the-Fire

🌍 Francisco Salgueiro
Francisco Salgueiro

🌍 GNUser
GNUser

📖 📦 George Shuklin
George Shuklin

🌍 Giusy Digital
Giusy Digital

🐛 - Hiroki Tagato
Hiroki Tagato

📦 + Hiroki Tagato
Hiroki Tagato

📦 Hubert
Hubert

🌍 Hüseyin Fahri Uzun
Hüseyin Fahri Uzun

🌍 IPinfo
IPinfo

💵 Ilmi2
Ilmi2

💵 Jan Walter
Jan Walter

💵 Jauder Ho
Jauder Ho

🚇 - Joshua Megnauth
Joshua Megnauth

💻 🎨 + Joshua Megnauth
Joshua Megnauth

💻 🎨 Julian Schmid
Julian Schmid

💻 🤔 LiChenG-P
LiChenG-P

💻 Liam OBrien
Liam OBrien

🤔 Limdongju
Limdongju

🌍 Lion Rayonnant
Lion Rayonnant

🌍 Ludwig Stecher
Ludwig Stecher

🤔 💻 - Marc Gavilán
Marc Gavilán

🌍 + Marc Gavilán
Marc Gavilán

🌍 Marco Cadetg
Marco Cadetg

📦 Matthias Braun
Matthias Braun

📖 Michel Hansma
Michel Hansma

🎨 ️️️️♿️ Morgan Hill
Morgan Hill

🛡️ Muhammadali Hakimov
Muhammadali Hakimov

🌍 Nubi
Nubi

🌍 - Oleksii Filonenko
Oleksii Filonenko

🌍 + Oleksii Filonenko
Oleksii Filonenko

🌍 Orhun Parmaksız
Orhun Parmaksız

📖 📦 💵 Peter Dave Hello
Peter Dave Hello

🌍 Phil Clifford
Phil Clifford

📦 Quetzal-coalt
Quetzal-coalt

🌍 Ron
Ron

🤔 Safaraliev Maxim
Safaraliev Maxim

🌍 - Shawn Yeager
Shawn Yeager

💵 + Shawn Yeager
Shawn Yeager

💵 SignPath GmbH
SignPath GmbH

📦 The Artifex
The Artifex

🌍 📦 Trịnh Duy Hưng
Trịnh Duy Hưng

🌍 TyseEX
TyseEX

🐛 Victor Nilsson
Victor Nilsson

🌍 💻 Wang Zishi
Wang Zishi

🌍 - Yevhen
Yevhen

🌍 + Yevhen
Yevhen

🌍 Ylva
Ylva

🌍 ZEROF
ZEROF

💵 ZeroDot1
ZeroDot1

🎨 ️️️️♿️ clr
clr

📖 🌍 ervinpopescu
ervinpopescu

🌍 glitsj16
glitsj16

📦 - guilherme-demarchi
guilherme-demarchi

🌍 + guilherme-demarchi
guilherme-demarchi

🌍 hirotake111
hirotake111

🌍 islameehassan
islameehassan

💻 louis-ym4
louis-ym4

🎨 luca3s
luca3s

🌍 pia
pia

🌍 pin
pin

📦 - shu-kitamura
shu-kitamura

💻 🌍 + shu-kitamura
shu-kitamura

💻 🌍 starccy
starccy

💻 tiansheng li
tiansheng li

💵 vtiinanen
vtiinanen

🌍 From 5bea8af773af49b11baf7b574c75f2f0b9599b07 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 10:36:31 +0200 Subject: [PATCH 70/74] docs: add mmseng as a contributor for financial (#949) * docs: update CONTRIBUTORS.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ CONTRIBUTORS.md | 11 ++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index c2617241..adc2fb71 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -767,6 +767,15 @@ "contributions": [ "translation" ] + }, + { + "login": "mmseng", + "name": "Matt Seng", + "avatar_url": "https://avatars.githubusercontent.com/u/8218085?v=4", + "profile": "https://github.com/mmseng", + "contributions": [ + "financial" + ] } ], "contributorsPerLine": 7, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 64ae175b..0f0831f6 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -62,49 +62,50 @@ Marc Gavilán
Marc Gavilán

🌍 Marco Cadetg
Marco Cadetg

📦 + Matt Seng
Matt Seng

💵 Matthias Braun
Matthias Braun

📖 Michel Hansma
Michel Hansma

🎨 ️️️️♿️ Morgan Hill
Morgan Hill

🛡️ Muhammadali Hakimov
Muhammadali Hakimov

🌍 - Nubi
Nubi

🌍 + Nubi
Nubi

🌍 Oleksii Filonenko
Oleksii Filonenko

🌍 Orhun Parmaksız
Orhun Parmaksız

📖 📦 💵 Peter Dave Hello
Peter Dave Hello

🌍 Phil Clifford
Phil Clifford

📦 Quetzal-coalt
Quetzal-coalt

🌍 Ron
Ron

🤔 - Safaraliev Maxim
Safaraliev Maxim

🌍 + Safaraliev Maxim
Safaraliev Maxim

🌍 Shawn Yeager
Shawn Yeager

💵 SignPath GmbH
SignPath GmbH

📦 The Artifex
The Artifex

🌍 📦 Trịnh Duy Hưng
Trịnh Duy Hưng

🌍 TyseEX
TyseEX

🐛 Victor Nilsson
Victor Nilsson

🌍 💻 - Wang Zishi
Wang Zishi

🌍 + Wang Zishi
Wang Zishi

🌍 Yevhen
Yevhen

🌍 Ylva
Ylva

🌍 ZEROF
ZEROF

💵 ZeroDot1
ZeroDot1

🎨 ️️️️♿️ clr
clr

📖 🌍 ervinpopescu
ervinpopescu

🌍 - glitsj16
glitsj16

📦 + glitsj16
glitsj16

📦 guilherme-demarchi
guilherme-demarchi

🌍 hirotake111
hirotake111

🌍 islameehassan
islameehassan

💻 louis-ym4
louis-ym4

🎨 luca3s
luca3s

🌍 pia
pia

🌍 - pin
pin

📦 + pin
pin

📦 shu-kitamura
shu-kitamura

💻 🌍 starccy
starccy

💻 tiansheng li
tiansheng li

💵 From aafe1f883ef515ee8e2dcd9498ee55f4c1ab4ea2 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 1 Sep 2025 15:27:47 +0200 Subject: [PATCH 71/74] Revert "don't show 'no addresses' warning when link type is Linux SLL" This reverts commit c195e73a5ccc529ed8cbe631f529df66300550f9. --- src/gui/pages/overview_page.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 5b48613a..41ed59df 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -23,7 +23,6 @@ use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::data_representation::DataRepr; use crate::networking::types::host::Host; -use crate::networking::types::my_link_type::MyLinkType; use crate::networking::types::service::Service; use crate::report::get_report_entries::{get_host_entries, get_service_entries}; use crate::report::types::search_parameters::SearchParameters; @@ -145,7 +144,7 @@ fn body_no_packets<'a>( .align_x(Alignment::Center) .font(font), ) - } else if cs.get_addresses().is_empty() && !matches!(link_type, MyLinkType::LinuxSll(_)) { + } else if cs.get_addresses().is_empty() { ( Icon::Warning.to_text().size(60), no_addresses_translation(language, &cs_info) From 7d90920d174962a42248caaeda831130f1594fa5 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 1 Sep 2025 16:22:54 +0200 Subject: [PATCH 72/74] set PCAP immediate mode to false (otherwise timeout is ignored on some platform) --- src/networking/types/capture_context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index 11c10268..9dd19f07 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -135,7 +135,7 @@ fn from_source(source: &CaptureSource, pcap_out_path: Option<&String>) -> Result } else { 200 // limit stored packets slice dimension (to keep more in the buffer) }) - .immediate_mode(true) // parse packets ASAP + .immediate_mode(false) .timeout(150) // ensure UI is updated even if no packets are captured .open()?; Ok(Self::Live(cap)) From 5a61eb332b96f015d5c4c8d0c84e600736ce1b87 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 1 Sep 2025 22:21:00 +0200 Subject: [PATCH 73/74] manually extract protocol type from Linux SSL header --- src/networking/parse_packets.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index b1802dd7..390c0f0e 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -25,7 +25,7 @@ use crate::utils::types::timestamp::Timestamp; use async_channel::Sender; use dns_lookup::lookup_addr; -use etherparse::LaxPacketHeaders; +use etherparse::{EtherType, LaxPacketHeaders}; use pcap::{Address, Packet}; use std::collections::HashMap; use std::net::IpAddr; @@ -287,7 +287,7 @@ fn get_sniffable_headers<'a>( MyLinkType::RawIp(_) | MyLinkType::IPv4(_) | MyLinkType::IPv6(_) => { LaxPacketHeaders::from_ip(packet).ok() } - MyLinkType::LinuxSll(_) => LaxPacketHeaders::from_linux_sll(packet).ok(), + MyLinkType::LinuxSll(_) => from_linux_sll(packet), MyLinkType::Null(_) | MyLinkType::Loop(_) => from_null(packet), } } @@ -321,6 +321,20 @@ fn matches(value: u32) -> bool { } } +fn from_linux_sll(packet: &[u8]) -> Option> { + if packet.len() <= 16 { + return None; + } + + let protocol_type = u16::from_be_bytes([packet[14], packet[15]]); + let payload = &packet[16..]; + + Some(LaxPacketHeaders::from_ether_type( + EtherType(protocol_type), + payload, + )) +} + fn reverse_dns_lookup( resolutions_state: &Arc>, new_hosts_to_send: &Arc>>, From d5a767c0aab12a7aa405afb9b9d57c2b2f14bf4a Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Mon, 1 Sep 2025 22:38:20 +0200 Subject: [PATCH 74/74] add support for Linux SLL version 2 --- src/networking/parse_packets.rs | 16 +++++++++++----- src/networking/types/capture_context.rs | 5 ++++- src/networking/types/my_link_type.rs | 4 +++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index 390c0f0e..fb733279 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -287,7 +287,8 @@ fn get_sniffable_headers<'a>( MyLinkType::RawIp(_) | MyLinkType::IPv4(_) | MyLinkType::IPv6(_) => { LaxPacketHeaders::from_ip(packet).ok() } - MyLinkType::LinuxSll(_) => from_linux_sll(packet), + MyLinkType::LinuxSll(_) => from_linux_sll(packet, true), + MyLinkType::LinuxSll2(_) => from_linux_sll(packet, false), MyLinkType::Null(_) | MyLinkType::Loop(_) => from_null(packet), } } @@ -321,13 +322,18 @@ fn matches(value: u32) -> bool { } } -fn from_linux_sll(packet: &[u8]) -> Option> { - if packet.len() <= 16 { +fn from_linux_sll(packet: &[u8], is_v1: bool) -> Option> { + let header_len = if is_v1 { 16 } else { 20 }; + if packet.len() <= header_len { return None; } - let protocol_type = u16::from_be_bytes([packet[14], packet[15]]); - let payload = &packet[16..]; + let protocol_type = u16::from_be_bytes(if is_v1 { + [packet[14], packet[15]] + } else { + [packet[0], packet[1]] + }); + let payload = &packet[header_len..]; Some(LaxPacketHeaders::from_ether_type( EtherType(protocol_type), diff --git a/src/networking/types/capture_context.rs b/src/networking/types/capture_context.rs index 9dd19f07..66cc703c 100644 --- a/src/networking/types/capture_context.rs +++ b/src/networking/types/capture_context.rs @@ -190,7 +190,10 @@ pub fn set_addresses(&mut self) { if let Self::Device(my_device) = self { let mut addresses = Vec::new(); for dev in Device::list().log_err(location!()).unwrap_or_default() { - if matches!(my_device.get_link_type(), MyLinkType::LinuxSll(_)) { + if matches!( + my_device.get_link_type(), + MyLinkType::LinuxSll(_) | MyLinkType::LinuxSll2(_) + ) { addresses.extend(dev.addresses); } else if dev.name.eq(my_device.get_name()) { addresses.extend(dev.addresses); diff --git a/src/networking/types/my_link_type.rs b/src/networking/types/my_link_type.rs index b12ec1ac..cf5187f2 100644 --- a/src/networking/types/my_link_type.rs +++ b/src/networking/types/my_link_type.rs @@ -13,6 +13,7 @@ pub enum MyLinkType { IPv4(Linktype), IPv6(Linktype), LinuxSll(Linktype), + LinuxSll2(Linktype), Unsupported(Linktype), #[default] NotYetAssigned, @@ -32,7 +33,7 @@ pub fn from_pcap_link_type(link_type: Linktype) -> Self { Linktype::IPV4 => Self::IPv4(link_type), Linktype::IPV6 => Self::IPv6(link_type), Linktype::LINUX_SLL => Self::LinuxSll(link_type), - // TODO: also add Linktype::LINUX_SLL2 (???) + Linktype::LINUX_SLL2 => Self::LinuxSll2(link_type), _ => Self::Unsupported(link_type), } } @@ -46,6 +47,7 @@ pub fn full_print_on_one_line(self, language: Language) -> String { | Self::IPv4(l) | Self::IPv6(l) | Self::LinuxSll(l) + | Self::LinuxSll2(l) | Self::Unsupported(l) => { format!( "{}: {} ({})",