diff --git a/src/app.rs b/src/app.rs index f4c3b8a6..04a0f0c2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,14 +1,17 @@ +use std::os::macos::raw::stat; use crate::{get_app_count_string, InfoTraffic, Sniffer, Status}; use iced::{ Svg, alignment, button, scrollable, executor, Alignment, Application, Button, Column, Command, Container, Element, Length, Row, Settings, Subscription, Text, Color, Radio, Scrollable, PickList, pick_list, Font, widget}; use std::sync::{Arc, Mutex, Condvar}; use std::time::Duration; use pcap::Device; use crate::info_address_port_pair::{AppProtocol, TransProtocol}; -use crate::style::{Mode, FontSizeBody, FontSizeSubtitle, FontSizeTitle, icon_sun_moon}; +use crate::gui_initial_page::initial_page; +use crate::gui_run_page::run_page; +use crate::style::{Mode, FONT_SIZE_BODY, FONT_SIZE_SUBTITLE, FONT_SIZE_TITLE, icon_sun_moon}; #[derive(Debug, Clone)] -pub(crate) enum Message { +pub enum Message { Tick, AdapterSelection(String), IpVersionSelection(String), @@ -33,10 +36,12 @@ impl Application for Sniffer { ) } + fn title(&self) -> String { String::from("Sniffnet") } + fn update(&mut self, message: Message) -> Command { match message { Message::Tick => {} @@ -83,10 +88,10 @@ impl Application for Sniffer { }; } } - Command::none() } + fn subscription(&self) -> Subscription { match self.status_pair.0.lock().unwrap() { _ => { @@ -95,227 +100,32 @@ impl Application for Sniffer { } } + fn view(&mut self) -> Element { - let button_start = Button::new( - &mut self.start, - Text::new("Run!").vertical_alignment(alignment::Vertical::Center).horizontal_alignment(alignment::Horizontal::Center), - ) - .padding(10) - .height(Length::Units(80)) - .width(Length::Units(160)) - .style(self.style) - .on_press(Message::Start); + let status = *self.status_pair.0.lock().unwrap(); + let mode = self.style; - let button_reset = Button::new( - &mut self.reset, - Text::new("Reset").horizontal_alignment(alignment::Horizontal::Center), - ) - .padding(10) - .height(Length::Units(40)) - .width(Length::Units(80)) - .style(self.style) - .on_press(Message::Reset); - - let button_style = Button::new( - &mut self.mode, - // icon(match self.style { - // Mode::Night => {font_awesome::SUN} - // Mode::Day => {font_awesome::MOON} - icon_sun_moon(self.style) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .padding(10) - .height(Length::Units(40)) - .width(Length::Units(100)) - .style(self.style) - .on_press(Message::Style); - - let button_report = Button::new( - &mut self.report, - Text::new("Open network traffic report").horizontal_alignment(alignment::Horizontal::Center), - ) - .padding(10) - .height(Length::Units(40)) - .width(Length::Units(270)) - .style(self.style) - .on_press(Message::OpenReport); - - let logo = Svg::from_path("./resources/sniffnet_logo.svg", ); - - let header = Row::new() - .height(Length::FillPortion(3)) - .align_items(Alignment::Center) - .push(Column::new().width(Length::FillPortion(1))) - .push(Column::new().width(Length::FillPortion(6)).push(logo)) - .push(Row::new().width(Length::FillPortion(1)).align_items(Alignment::Center).push(button_style)); - - let mut dev_str_list = vec![]; - for dev in Device::list().expect("Error retrieving device list\r\n") { - let mut dev_str = String::new(); - match dev.desc { - None => { - dev_str.push_str(&format!("{}", dev.name)); - } - Some(description) => { - dev_str.push_str(&format!("{}\n{}", dev.name, description)); - } + let body = match status { + Status::Init => { + initial_page(self) } - let num_addresses = dev.addresses.len(); - match num_addresses { - 0 => {}, - 1 => {dev_str.push_str("\nAddress:");}, - _ => {dev_str.push_str("\nAddresses:");} + Status::Running => { + run_page(self) } + Status::Pause => {Column::new()} + Status::Stop => {Column::new()} + }; - let mut x = 0; - for addr in dev.addresses { - x += 1; - let address_string = addr.addr.to_string(); - // if x == num_addresses { - // dev_str.push_str(&format!("{}", address_string)); - // } - //else { - dev_str.push_str(&format!("\n {}", address_string)); - //} - } - dev_str_list.push((dev.name, dev_str)); - } - - let col_adapter = Column::new() - .padding(20) - .spacing(10) - .height(Length::Fill) - .width(Length::FillPortion(4)) - .push(Text::new("Select network adapter to inspect").size(FontSizeTitle)) - .push(dev_str_list.iter().fold( - Scrollable::new(&mut self.scroll).style(self.style).padding(10).spacing(20).height(Length::FillPortion(8)), - |scroll, adapter| { - scroll.push(Radio::new( - &adapter.0, - &adapter.1, - Some(&self.device.clone().lock().unwrap().name), - |name| Message::AdapterSelection(name.to_string()), - ).size(15).style(self.style)) - }, - )); - - let col_space = Column::new() - .padding(20) - .spacing(10) - .width(Length::FillPortion(1)); - - let filtri = self.filters.lock().unwrap(); - let ip_active = &*filtri.ip; - let col_ip_radio = Column::new().spacing(10) - .push(Text::new("IP version").size(FontSizeSubtitle)) - .push(Radio::new( - "ipv4", - "IPv4", - Some(ip_active), - |version| Message::IpVersionSelection(version.to_string()) - ).size(15).style(self.style)) - .push(Radio::new( - "ipv6", - "IPv6", - Some(ip_active), - |version| Message::IpVersionSelection(version.to_string()) - ).size(15).style(self.style)) - .push(Radio::new( - "no filter", - "both", - Some(ip_active), - |version| Message::IpVersionSelection(version.to_string()) - ).size(15).style(self.style)); - let col_ip = Column::new() - .spacing(10) - .width(Length::FillPortion(1)) - .push(col_ip_radio); - - let transport_active = filtri.transport; - let col_transport_radio = Column::new().spacing(10) - .push(Text::new("Transport protocol").size(FontSizeSubtitle)) - .push(Radio::new( - TransProtocol::TCP, - "TCP", - Some(transport_active), - |protocol| Message::TransportProtocolSelection(protocol) - ).size(15).style(self.style)) - .push(Radio::new( - TransProtocol::UDP, - "UDP", - Some(transport_active), - |protocol| Message::TransportProtocolSelection(protocol) - ).size(15).style(self.style)) - .push(Radio::new( - TransProtocol::Other, - "both", - Some(transport_active), - |protocol| Message::TransportProtocolSelection(protocol) - ).size(15).style(self.style)); - let col_transport = Column::new() - .align_items(Alignment::Center) - .spacing(10) - .width(Length::FillPortion(2)) - .push(col_transport_radio) - .push(Row::new().height(Length::FillPortion(2))) - .push(button_start) - .push(Row::new().height(Length::FillPortion(1))); - - let app_active = filtri.application; - let picklist_app = PickList::new( - &mut self.app, - &AppProtocol::ALL[..], - Some(app_active), - |protocol| Message::AppProtocolSelection(protocol), + Container::new( + Column::new() + .push(body) ) - .placeholder("Select application protocol") - .style(self.style); - let mut col_app = Column::new() - .width(Length::FillPortion(2)) - .spacing(10) - .push(iced::Text::new("Application protocol").size(FontSizeSubtitle)) - .push(picklist_app); - - let filters = Column::new().width(Length::FillPortion(6)).padding(20).spacing(20) - .push(Row::new().push(Text::new("Select filters to be applied on network traffic").size(FontSizeTitle))) - .push(Row::new().height(Length::FillPortion(3)).push(col_ip).push(col_transport).push(col_app)); - - - let sniffer = self.info_traffic.lock().unwrap(); - - let mut col_packets = Column::new() - .width(Length::FillPortion(1)) - .align_items(Alignment::Center) - .spacing(20) - .push(iced::Text::new(std::env::current_dir().unwrap().to_str().unwrap())) - .push(iced::Text::new(sniffer.all_packets.to_string())); - if sniffer.tot_received_packets + sniffer.tot_sent_packets > 0 { - col_packets = col_packets - .push(iced::Text::new("Packets count per application protocol")) - .push(iced::Text::new(get_app_count_string(sniffer.app_protocols.clone(), sniffer.tot_received_packets + sniffer.tot_sent_packets))); - } - col_packets = col_packets.push(button_reset).push(button_report); - - let mut body = Row::new().height(Length::FillPortion(9)); - - match *self.status_pair.0.lock().unwrap() { - Status::Init => {body = body - .push(col_adapter) - .push(col_space) - .push(filters);} - Status::Running => {body = body - .push(col_packets);} - Status::Pause => {} - Status::Stop => {} - } - - Container::new(Column::new().push(header).push(body)) .width(Length::Fill) .height(Length::Fill) .center_x() .center_y() - .style(self.style) + .style(mode) .into() } diff --git a/src/gui_initial_page.rs b/src/gui_initial_page.rs new file mode 100644 index 00000000..b3d93ead --- /dev/null +++ b/src/gui_initial_page.rs @@ -0,0 +1,178 @@ +use iced::{alignment, Alignment, Button, Column, Length, PickList, Radio, Renderer, Row, Scrollable, Svg, Text}; +use pcap::Device; +use crate::app::Message; +use crate::{AppProtocol, FONT_SIZE_SUBTITLE, FONT_SIZE_TITLE, icon_sun_moon, Sniffer, TransProtocol}; + +pub fn initial_page(sniffer: &mut Sniffer) -> Column { + + let logo = Svg::from_path("./resources/sniffnet_logo.svg"); + + let button_style = Button::new( + &mut sniffer.mode, + icon_sun_moon(sniffer.style) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .padding(10) + .height(Length::Units(40)) + .width(Length::Units(100)) + .style(sniffer.style) + .on_press(Message::Style); + + let header = Row::new() + .height(Length::FillPortion(3)) + .align_items(Alignment::Center) + .push(Column::new().width(Length::FillPortion(1))) + .push(Column::new().width(Length::FillPortion(6)).push(logo)) + .push(Row::new().width(Length::FillPortion(1)).align_items(Alignment::Center).push(button_style)); + + let button_start = Button::new( + &mut sniffer.start, + Text::new("Run!").vertical_alignment(alignment::Vertical::Center).horizontal_alignment(alignment::Horizontal::Center), + ) + .padding(10) + .height(Length::Units(80)) + .width(Length::Units(160)) + .style(sniffer.style) + .on_press(Message::Start); + + let mut dev_str_list = vec![]; + for dev in Device::list().expect("Error retrieving device list\r\n") { + let mut dev_str = String::new(); + match dev.desc { + None => { + dev_str.push_str(&format!("{}", dev.name)); + } + Some(description) => { + dev_str.push_str(&format!("{}\n{}", dev.name, description)); + } + } + let num_addresses = dev.addresses.len(); + match num_addresses { + 0 => {}, + 1 => {dev_str.push_str("\nAddress:");}, + _ => {dev_str.push_str("\nAddresses:");} + } + + let mut x = 0; + for addr in dev.addresses { + x += 1; + let address_string = addr.addr.to_string(); + // if x == num_addresses { + // dev_str.push_str(&format!("{}", address_string)); + // } + //else { + dev_str.push_str(&format!("\n {}", address_string)); + //} + } + dev_str_list.push((dev.name, dev_str)); + } + + let col_adapter = Column::new() + .padding(20) + .spacing(10) + .height(Length::Fill) + .width(Length::FillPortion(4)) + .push(Text::new("Select network adapter to inspect").size(FONT_SIZE_TITLE)) + .push(dev_str_list.iter().fold( + Scrollable::new(&mut sniffer.scroll).style(sniffer.style).padding(10).spacing(20).height(Length::FillPortion(8)), + |scroll, adapter| { + scroll.push(Radio::new( + &adapter.0, + &adapter.1, + Some(&sniffer.device.clone().lock().unwrap().name), + |name| Message::AdapterSelection(name.to_string()), + ).size(15).style(sniffer.style)) + }, + )); + + let col_space = Column::new() + .padding(20) + .spacing(10) + .width(Length::FillPortion(1)); + + let filtri = sniffer.filters.lock().unwrap(); + let ip_active = &*filtri.ip; + let col_ip_radio = Column::new().spacing(10) + .push(Text::new("IP version").size(FONT_SIZE_SUBTITLE)) + .push(Radio::new( + "ipv4", + "IPv4", + Some(ip_active), + |version| Message::IpVersionSelection(version.to_string()) + ).size(15).style(sniffer.style)) + .push(Radio::new( + "ipv6", + "IPv6", + Some(ip_active), + |version| Message::IpVersionSelection(version.to_string()) + ).size(15).style(sniffer.style)) + .push(Radio::new( + "no filter", + "both", + Some(ip_active), + |version| Message::IpVersionSelection(version.to_string()) + ).size(15).style(sniffer.style)); + let col_ip = Column::new() + .spacing(10) + .width(Length::FillPortion(1)) + .push(col_ip_radio); + + let transport_active = filtri.transport; + let col_transport_radio = Column::new().spacing(10) + .push(Text::new("Transport protocol").size(FONT_SIZE_SUBTITLE)) + .push(Radio::new( + TransProtocol::TCP, + "TCP", + Some(transport_active), + |protocol| Message::TransportProtocolSelection(protocol) + ).size(15).style(sniffer.style)) + .push(Radio::new( + TransProtocol::UDP, + "UDP", + Some(transport_active), + |protocol| Message::TransportProtocolSelection(protocol) + ).size(15).style(sniffer.style)) + .push(Radio::new( + TransProtocol::Other, + "both", + Some(transport_active), + |protocol| Message::TransportProtocolSelection(protocol) + ).size(15).style(sniffer.style)); + let col_transport = Column::new() + .align_items(Alignment::Center) + .spacing(10) + .width(Length::FillPortion(2)) + .push(col_transport_radio) + .push(Row::new().height(Length::FillPortion(2))) + .push(button_start) + .push(Row::new().height(Length::FillPortion(1))); + + let app_active = filtri.application; + let picklist_app = PickList::new( + &mut sniffer.app, + &AppProtocol::ALL[..], + Some(app_active), + |protocol| Message::AppProtocolSelection(protocol), + ) + .placeholder("Select application protocol") + .style(sniffer.style); + let mut col_app = Column::new() + .width(Length::FillPortion(2)) + .spacing(10) + .push(iced::Text::new("Application protocol").size(FONT_SIZE_SUBTITLE)) + .push(picklist_app); + + let filters = Column::new().width(Length::FillPortion(6)).padding(20).spacing(20) + .push(Row::new().push(Text::new("Select filters to be applied on network traffic").size(FONT_SIZE_TITLE))) + .push(Row::new().height(Length::FillPortion(3)).push(col_ip).push(col_transport).push(col_app)); + + let body = Row::new().height(Length::FillPortion(9)) + .push(col_adapter) + .push(col_space) + .push(filters); + + Column::new() + .push(header) + .push(body) + +} \ No newline at end of file diff --git a/src/gui_run_page.rs b/src/gui_run_page.rs new file mode 100644 index 00000000..3ddee65f --- /dev/null +++ b/src/gui_run_page.rs @@ -0,0 +1,69 @@ +use iced::{alignment, Alignment, Button, Column, Length, Row, Svg, Text}; +use crate::app::Message; +use crate::{get_app_count_string, icon_sun_moon, Sniffer}; + +pub fn run_page(sniffer: &mut Sniffer) -> Column { + + let logo = Svg::from_path("./resources/sniffnet_logo.svg"); + + let button_style = Button::new( + &mut sniffer.mode, + icon_sun_moon(sniffer.style) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .padding(10) + .height(Length::Units(40)) + .width(Length::Units(100)) + .style(sniffer.style) + .on_press(Message::Style); + + let header = Row::new() + .height(Length::FillPortion(3)) + .align_items(Alignment::Center) + .push(Column::new().width(Length::FillPortion(1))) + .push(Column::new().width(Length::FillPortion(6)).push(logo)) + .push(Row::new().width(Length::FillPortion(1)).align_items(Alignment::Center).push(button_style)); + + let button_reset = Button::new( + &mut sniffer.reset, + Text::new("Reset").horizontal_alignment(alignment::Horizontal::Center), + ) + .padding(10) + .height(Length::Units(40)) + .width(Length::Units(80)) + .style(sniffer.style) + .on_press(Message::Reset); + + let button_report = Button::new( + &mut sniffer.report, + Text::new("Open network traffic report").horizontal_alignment(alignment::Horizontal::Center), + ) + .padding(10) + .height(Length::Units(40)) + .width(Length::Units(270)) + .style(sniffer.style) + .on_press(Message::OpenReport); + + let sniffer_lock = sniffer.info_traffic.lock().unwrap(); + + let mut col_packets = Column::new() + .width(Length::FillPortion(1)) + .align_items(Alignment::Center) + .spacing(20) + .push(iced::Text::new(std::env::current_dir().unwrap().to_str().unwrap())) + .push(iced::Text::new(sniffer_lock.all_packets.to_string())); + if sniffer_lock.tot_received_packets + sniffer_lock.tot_sent_packets > 0 { + col_packets = col_packets + .push(iced::Text::new("Packets count per application protocol")) + .push(iced::Text::new(get_app_count_string(sniffer_lock.app_protocols.clone(), sniffer_lock.tot_received_packets + sniffer_lock.tot_sent_packets))); + } + col_packets = col_packets.push(button_reset).push(button_report); + + let body = Row::new().height(Length::FillPortion(9)) + .push(col_packets); + + Column::new() + .push(header) + .push(body) + +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 81e72395..87629240 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ mod thread_write_report_functions; mod info_traffic; mod style; mod app; +mod gui_initial_page; +mod gui_run_page; use std::fs::File; use std::io::prelude::*; @@ -34,7 +36,7 @@ use iced::futures::FutureExt; use iced_style::pane_grid::Line; use indexmap::IndexMap; use crate::info_traffic::InfoTraffic; -use style::{Mode, FontSizeBody, FontSizeSubtitle, FontSizeTitle, icon_sun_moon}; +use style::{Mode, FONT_SIZE_BODY, FONT_SIZE_SUBTITLE, FONT_SIZE_TITLE, icon_sun_moon}; pub struct Filters { @@ -44,7 +46,7 @@ pub struct Filters { } -struct Sniffer { +pub struct Sniffer { info_traffic: Arc>, device: Arc>, filters: Arc>, @@ -60,7 +62,7 @@ struct Sniffer { /// This enum represents the sniffing process status. -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] pub enum Status { /// Sniffnet has just been launched/restarted Init, @@ -129,7 +131,7 @@ pub fn main() { style: Mode::Night }, default_font: Some(include_bytes!("../fonts/CourierPrimeSans.ttf")), - default_text_size: FontSizeBody, + default_text_size: FONT_SIZE_BODY, text_multithreading: false, antialiasing: false, diff --git a/src/style.rs b/src/style.rs index 39e5ab29..bd1f9c1e 100644 --- a/src/style.rs +++ b/src/style.rs @@ -3,19 +3,44 @@ use iced::container::{Style, StyleSheet}; use iced_style::scrollable::{Scrollbar, Scroller}; use crate::app::Message; + +pub const ICONS: Font = Font::External { + name: "Icons", + bytes: include_bytes!("../fonts/icons.ttf"), +}; + + +pub const COURIER_PRIME: Font = Font::External { + name: "CourierPrimeSans", + bytes: include_bytes!("../fonts/CourierPrimeSans.ttf"), +}; + +pub const COURIER_PRIME_ITALIC: Font = Font::External { + name: "CourierPrimeSans", + bytes: include_bytes!("../fonts/CourierPrimeSansItalic.ttf"), +}; + +pub const FONT_SIZE_BODY: u16 = 16; +pub const FONT_SIZE_SUBTITLE: u16 = 18; +pub const FONT_SIZE_TITLE: u16 = 22; + + #[derive(Copy, Eq, PartialEq)] pub enum Mode { Night, Day } + impl Clone for Mode { fn clone(&self) -> Self { *self } } + impl StyleSheet for Mode { + fn style(&self) -> Style { Style { text_color: match self { @@ -31,9 +56,12 @@ impl StyleSheet for Mode { border_color: Default::default() } } + } + impl pick_list::StyleSheet for Mode { + fn menu(&self) -> iced_style::menu::Style { iced_style::menu::Style { text_color: match self { @@ -103,6 +131,7 @@ impl pick_list::StyleSheet for Mode { } } + impl button::StyleSheet for Mode { fn hovered(&self) -> iced_style::button::Style { @@ -181,8 +210,10 @@ impl iced_style::radio::StyleSheet for Mode { text_color: None } } + } + impl iced_style::scrollable::StyleSheet for Mode { fn active(&self) -> Scrollbar { @@ -229,10 +260,6 @@ impl iced_style::scrollable::StyleSheet for Mode { } -pub const ICONS: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../fonts/icons.ttf"), -}; pub fn icon(unicode: char) -> Text { Text::new(unicode.to_string()) @@ -242,6 +269,7 @@ pub fn icon(unicode: char) -> Text { .size(20) } + pub fn icon_sun_moon(style: Mode) -> Text { match style { Mode::Night => { @@ -259,18 +287,4 @@ pub fn icon_sun_moon(style: Mode) -> Text { .size(20) } } -} - -pub const CourierPrime: Font = Font::External { - name: "CourierPrimeSans", - bytes: include_bytes!("../fonts/CourierPrimeSans.ttf"), -}; - -pub const CourierPrimeItalic: Font = Font::External { - name: "CourierPrimeSans", - bytes: include_bytes!("../fonts/CourierPrimeSansItalic.ttf"), -}; - -pub const FontSizeBody: u16 = 16; -pub const FontSizeSubtitle: u16 = 18; -pub const FontSizeTitle: u16 = 22; \ No newline at end of file +} \ No newline at end of file