sniffnet 1.5.0

Application to comfortably monitor your network traffic
use std::net::IpAddr;

use crate::countries::country_utils::{get_computer_tooltip, get_flag_tooltip};
use crate::gui::components::button::button_hide;
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, TOOLTIP_DELAY};
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,
};
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;
use crate::networking::types::traffic_direction::TrafficDirection;
use crate::translations::translations::{
    address_translation, incoming_translation, outgoing_translation, packets_translation,
    protocol_translation,
};
use crate::translations::translations_2::{
    connection_details_translation, destination_translation, fqdn_translation,
    mac_address_translation, socket_address_translation, source_translation,
    transmitted_data_translation,
};
use crate::translations::translations_3::{
    copy_translation, messages_translation, service_translation,
};
use crate::translations::translations_5::program_translation;
use crate::utils::formatted_strings::{get_formatted_timestamp, get_socket_address};
use crate::utils::types::icon::Icon;
use crate::{Language, Protocol, Sniffer, StyleType};
use iced::alignment::Vertical;
use iced::widget::scrollable::Direction;
use iced::widget::tooltip::Position;
use iced::widget::{Column, Container, Row, Space, Text, Tooltip};
use iced::widget::{Scrollable, button};
use iced::{Alignment, Length, Padding};

pub fn connection_details_page(
    sniffer: &Sniffer,
    key: AddressPortPair,
) -> Container<'_, Message, StyleType> {
    Container::new(page_content(sniffer, &key))
}

fn page_content<'a>(sniffer: &Sniffer, key: &AddressPortPair) -> Container<'a, Message, StyleType> {
    let Settings {
        language,
        color_gradient,
        ..
    } = sniffer.conf.settings;
    let data_repr = sniffer.conf.data_repr;

    let info_traffic = &sniffer.info_traffic;
    let default_val = InfoAddressPortPair::default();
    let val = info_traffic.map.get(key).unwrap_or(&default_val);
    let address_to_lookup = get_address_to_lookup(key, val.traffic_direction);
    let host_option = sniffer.addresses_resolved.get(&address_to_lookup);
    let default_host = Host::default();
    let host_info_option = info_traffic
        .hosts
        .get(host_option.map_or(&default_host, |(_, h)| h))
        .copied();

    let header_and_content = Column::new()
        .width(Length::Fill)
        .push(page_header(color_gradient, language));

    let mut source_caption = Row::new().align_y(Alignment::Center).spacing(10).push(
        Text::new(source_translation(language))
            .size(FONT_SIZE_TITLE)
            .class(TextType::Title),
    );
    let mut dest_caption = Row::new().align_y(Alignment::Center).spacing(10).push(
        Text::new(destination_translation(language))
            .size(FONT_SIZE_TITLE)
            .class(TextType::Title),
    );
    let mut host_info_col = Column::new();
    if let Some((r_dns, host)) = host_option {
        host_info_col = get_host_info_col(r_dns, host, language);
        let host_info = host_info_option.unwrap_or_default();
        let flag = get_flag_tooltip(host.country, &host_info, language, false, 1.0);
        let computer = get_local_tooltip(sniffer, &address_to_lookup, key);
        if address_to_lookup.eq(&key.source) {
            source_caption = source_caption.push(flag);
            dest_caption = dest_caption.push(computer);
        } else {
            dest_caption = dest_caption.push(flag);
            source_caption = source_caption.push(computer);
        }
    }

    let mut source_col = get_src_or_dest_col(
        source_caption,
        &key.source,
        key.sport,
        val.mac_address1.as_ref(),
        language,
        &sniffer.timing_events,
    );
    let mut dest_col = get_src_or_dest_col(
        dest_caption,
        &key.dest,
        key.dport,
        val.mac_address2.as_ref(),
        language,
        &sniffer.timing_events,
    );

    if address_to_lookup.eq(&key.source) {
        source_col = source_col.push(host_info_col);
    } else {
        dest_col = dest_col.push(host_info_col);
    }

    let col_info = col_info(key, val, data_repr, language);

    let content = assemble_widgets(col_info, source_col, dest_col);

    Container::new(header_and_content.push(content))
        .width(1000)
        .height(500)
        .class(ContainerType::Modal)
}

fn page_header<'a>(
    color_gradient: GradientType,
    language: Language,
) -> Container<'a, Message, StyleType> {
    Container::new(
        Row::new()
            .push(Space::new().width(Length::Fill))
            .push(
                Text::new(connection_details_translation(language))
                    .size(FONT_SIZE_TITLE)
                    .width(Length::FillPortion(6))
                    .align_x(Alignment::Center),
            )
            .push(
                Container::new(button_hide(Message::HideModal, language))
                    .width(Length::Fill)
                    .align_x(Alignment::Center),
            ),
    )
    .align_x(Alignment::Center)
    .align_y(Alignment::Center)
    .height(40.0)
    .width(Length::Fill)
    .class(ContainerType::Gradient(color_gradient))
}

fn col_info<'a>(
    key: &AddressPortPair,
    val: &InfoAddressPortPair,
    data_repr: DataRepr,
    language: Language,
) -> Column<'a, Message, StyleType> {
    let is_icmp = key.protocol.eq(&Protocol::ICMP);
    let is_arp = key.protocol.eq(&Protocol::ARP);

    let mut ret_val = Column::new()
        .spacing(10)
        .padding(Padding::new(20.0).right(10).left(40))
        .width(Length::FillPortion(2))
        .push(Space::new().height(Length::Fill))
        .push(
            Row::new()
                .spacing(8)
                .align_y(Vertical::Center)
                .push(Icon::Clock.to_text())
                .push(Text::new(format!(
                    "{}\n{}",
                    get_formatted_timestamp(val.initial_timestamp),
                    get_formatted_timestamp(val.final_timestamp)
                ))),
        )
        .push(TextType::highlighted_subtitle_with_desc(
            protocol_translation(language),
            &key.protocol.to_string(),
        ));

    if !is_icmp && !is_arp {
        ret_val = ret_val
            .push(TextType::highlighted_subtitle_with_desc(
                service_translation(language),
                &val.service.to_string(),
            ))
            .push(TextType::highlighted_subtitle_with_desc(
                program_translation(language),
                &val.program.to_string(),
            ));
    }

    ret_val = ret_val.push(TextType::highlighted_subtitle_with_desc(
        &format!(
            "{} ({})",
            transmitted_data_translation(language),
            if val.traffic_direction.eq(&TrafficDirection::Outgoing) {
                outgoing_translation(language).to_lowercase()
            } else {
                incoming_translation(language).to_lowercase()
            }
        ),
        &(data_repr.formatted_string(val.transmitted_data(data_repr))
            + if data_repr == DataRepr::Packets {
                format!(" {}", packets_translation(language))
            } else {
                String::new()
            }
            .as_ref()),
    ));

    if is_icmp || is_arp {
        ret_val = ret_val.push(
            Column::new()
                .push(
                    Text::new(format!("{}:", messages_translation(language)))
                        .class(TextType::Subtitle),
                )
                .push(Scrollable::with_direction(
                    Column::new()
                        .padding(Padding::ZERO.right(10).bottom(10))
                        .push(Text::new(if is_icmp {
                            IcmpType::pretty_print_types(&val.icmp_types)
                        } else {
                            ArpType::pretty_print_types(&val.arp_types)
                        })),
                    Direction::Both {
                        vertical: ScrollbarType::properties(),
                        horizontal: ScrollbarType::properties(),
                    },
                )),
        );
    }

    ret_val = ret_val.push(Space::new().height(Length::Fill));

    ret_val
}

fn get_host_info_col<'a>(
    r_dns: &str,
    host: &Host,
    language: Language,
) -> Column<'a, Message, StyleType> {
    let mut host_info_col = Column::new().spacing(4);
    if r_dns.parse::<IpAddr>().is_err() || (!host.asn.name.is_empty() && !host.asn.code.is_empty())
    {
        host_info_col = host_info_col.push(RuleType::Standard.horizontal(10));
    }
    if r_dns.parse::<IpAddr>().is_err() {
        host_info_col = host_info_col.push(TextType::highlighted_subtitle_with_desc(
            fqdn_translation(language),
            r_dns,
        ));
    }
    if !host.asn.name.is_empty() && !host.asn.code.is_empty() {
        host_info_col = host_info_col.push(TextType::highlighted_subtitle_with_desc(
            "ASN",
            &format!("{} ({})", host.asn.name, host.asn.code),
        ));
    }
    host_info_col
}

fn get_local_tooltip<'a>(
    sniffer: &Sniffer,
    address_to_lookup: &IpAddr,
    key: &AddressPortPair,
) -> Tooltip<'a, Message, StyleType> {
    let Settings { language, .. } = sniffer.conf.settings;

    let local_address = if address_to_lookup.eq(&key.source) {
        &key.dest
    } else {
        &key.source
    };
    let my_interface_addresses = sniffer.capture_source.get_addresses();
    get_computer_tooltip(
        is_my_address(local_address, my_interface_addresses),
        is_local_connection(local_address, my_interface_addresses),
        is_bogon(local_address),
        get_traffic_type(
            if address_to_lookup.eq(&key.source) {
                &key.dest
            } else {
                &key.source
            },
            my_interface_addresses,
            TrafficDirection::Outgoing,
        ),
        language,
    )
}

fn get_src_or_dest_col<'a>(
    caption: Row<'a, Message, StyleType>,
    ip: &IpAddr,
    port: Option<u16>,
    mac: Option<&String>,
    language: Language,
    timing_events: &TimingEvents,
) -> Column<'a, Message, StyleType> {
    let address_caption = if port.is_some() {
        socket_address_translation(language)
    } else {
        address_translation(language)
    };

    let mac_str = if let Some(val) = mac { val } else { "-" };

    Column::new()
        .spacing(4)
        .push(
            Container::new(caption)
                .width(Length::Fill)
                .align_x(Alignment::Center),
        )
        .push(RuleType::Standard.horizontal(10))
        .push(
            Row::new()
                .spacing(10)
                .align_y(Alignment::End)
                .push(TextType::highlighted_subtitle_with_desc(
                    address_caption,
                    &get_socket_address(ip, port),
                ))
                .push(get_button_copy(language, ip, timing_events)),
        )
        .push(TextType::highlighted_subtitle_with_desc(
            mac_address_translation(language),
            mac_str,
        ))
}

fn assemble_widgets<'a>(
    col_info: Column<'a, Message, StyleType>,
    source_col: Column<'a, Message, StyleType>,
    dest_col: Column<'a, Message, StyleType>,
) -> Row<'a, Message, StyleType> {
    let [source_container, dest_container] = [source_col, dest_col].map(|col| {
        Container::new(col)
            .padding(7)
            .width(Length::Fill)
            .class(ContainerType::BorderedRound)
    });
    Row::new()
        .padding([0, 10])
        .spacing(10)
        .align_y(Alignment::Center)
        .width(Length::Fill)
        .height(Length::Fill)
        .push(col_info)
        .push(
            Column::new()
                .width(Length::FillPortion(3))
                .align_x(Alignment::Center)
                .spacing(5)
                .push(Space::new().height(Length::Fill))
                .push(source_container)
                .push(Icon::ArrowsDown.to_text())
                .push(dest_container)
                .push(Space::new().height(Length::Fill)),
        )
}

fn get_button_copy<'a>(
    language: Language,
    ip: &IpAddr,
    timing_events: &TimingEvents,
) -> Tooltip<'a, Message, StyleType> {
    let icon = if timing_events.was_just_copy_ip(ip) {
        Text::new("").size(14)
    } else {
        Icon::Copy.to_text().size(12)
    };

    let content = button(icon.align_x(Alignment::Center).align_y(Alignment::Center))
        .padding(0)
        .height(25)
        .width(25)
        .on_press(Message::CopyIp(*ip));

    Tooltip::new(
        content,
        Text::new(format!("{} (IP)", copy_translation(language))),
        Position::Right,
    )
    .gap(5)
    .class(ContainerType::Tooltip)
    .delay(TOOLTIP_DELAY)
}