use std::collections::HashMap;
use std::fmt::Display;
use std::net::Ipv4Addr;
use std::time::Instant;
pub use crate::stats::speed::Speed;
pub type StatsMap = HashMap<StatKey, StatValues>;
#[derive(Debug, Clone)]
pub struct TimedSpeed {
pub speed: Speed,
pub timestamp: Instant,
pub duration_secs: f64,
}
impl TimedSpeed {
pub fn new(speed: Speed, duration_secs: f64) -> Self {
Self {
speed,
timestamp: Instant::now(),
duration_secs,
}
}
pub fn accumulate(&self) -> SpeedAccumulator {
SpeedAccumulator::from_timed_speed(self)
}
}
#[derive(Debug, Clone, Default)]
pub struct SpeedAccumulator {
speed_sum: Speed,
total_duration_secs: f64,
}
impl SpeedAccumulator {
pub fn from_timed_speed(ts: &TimedSpeed) -> Self {
Self {
speed_sum: ts.speed,
total_duration_secs: ts.duration_secs,
}
}
pub fn add(&mut self, ts: &TimedSpeed) {
self.speed_sum += ts.speed;
self.total_duration_secs += ts.duration_secs;
}
pub fn finalize(self) -> Option<Speed> {
if self.total_duration_secs > 0.0 {
Some(Speed::new(
(self.speed_sum.input as f64 / self.total_duration_secs) as u128,
(self.speed_sum.output as f64 / self.total_duration_secs) as u128,
))
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct StatItem {
pub key: StatKey,
pub value: StatValues,
}
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]
pub struct StatKey {
pub src_port: u16,
pub dst_port: u16,
pub src_ip: Ipv4Addr,
pub dst_ip: Ipv4Addr,
pub direction: Direction,
pub protocol: u8,
pub tcp_syn: bool,
pub tcp_ack: bool,
pub tcp_fin: bool,
pub tcp_rst: bool,
}
impl Display for StatKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{} -> {}:{} ({})",
self.src_ip, self.src_port, self.dst_ip, self.dst_port, self.direction
)
}
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum Direction {
None,
Incoming,
Local,
Outgoing,
Internet,
}
impl Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Direction::None => write!(f, "None"),
Direction::Outgoing => write!(f, "Out"),
Direction::Incoming => write!(f, "In"),
Direction::Local => write!(f, "Local"),
Direction::Internet => write!(f, "Internet"),
}
}
}
#[derive(Debug, Clone)]
pub struct StatValues {
pub size: u128,
pub last_timestamp: Option<u64>,
pub last_seq: Option<u32>,
pub last_ack: Option<u32>,
}
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
pub struct IpPair {
pub src_ip: Ipv4Addr,
pub dst_ip: Ipv4Addr,
pub is_local: bool,
pub protocol: u8,
}
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
pub struct ConnectionDetails {
pub src_ip: Ipv4Addr,
pub dst_ip: Ipv4Addr,
pub src_port: u16,
pub dst_port: u16,
pub is_local: bool,
pub direction: Direction,
pub protocol: u8,
}
impl ConnectionDetails {
pub fn new(
src_ip: Ipv4Addr,
dst_ip: Ipv4Addr,
src_port: u16,
dst_port: u16,
is_local: bool,
direction: Direction,
protocol: u8,
) -> Self {
Self {
src_ip,
dst_ip,
src_port,
dst_port,
is_local,
direction,
protocol,
}
}
pub fn from_ip_pair_and_ports(pair: &IpPair, src_port: u16, dst_port: u16) -> Self {
Self {
src_ip: pair.src_ip,
dst_ip: pair.dst_ip,
src_port,
dst_port,
is_local: pair.is_local,
direction: Direction::None,
protocol: pair.protocol,
}
}
pub fn as_ip_pair(&self) -> IpPair {
IpPair {
src_ip: self.src_ip,
dst_ip: self.dst_ip,
is_local: self.is_local,
protocol: self.protocol,
}
}
}
impl Display for IpPair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_local {
write!(f, "{} <-> {} (local)", self.src_ip, self.dst_ip)
} else {
write!(f, "{} <-> {}", self.src_ip, self.dst_ip)
}
}
}
impl Display for ConnectionDetails {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_local {
write!(
f,
"{}:{} <-> {}:{} (local)",
self.src_ip, self.src_port, self.dst_ip, self.dst_port
)
} else {
write!(
f,
"{}:{} <-> {}:{}",
self.src_ip, self.src_port, self.dst_ip, self.dst_port
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stat_key_display() {
let key = StatKey {
src_port: 12345,
dst_port: 443,
src_ip: Ipv4Addr::new(192, 168, 1, 1),
dst_ip: Ipv4Addr::new(93, 184, 216, 34),
direction: Direction::Outgoing,
protocol: 6,
tcp_syn: false,
tcp_ack: false,
tcp_fin: false,
tcp_rst: false,
};
let display = format!("{}", key);
assert!(display.contains("192.168.1.1:12345"));
assert!(display.contains("93.184.216.34:443"));
assert!(display.contains("Out"));
}
#[test]
fn test_direction_display() {
assert_eq!(format!("{}", Direction::None), "None");
assert_eq!(format!("{}", Direction::Outgoing), "Out");
assert_eq!(format!("{}", Direction::Incoming), "In");
assert_eq!(format!("{}", Direction::Local), "Local");
assert_eq!(format!("{}", Direction::Internet), "Internet");
}
#[test]
fn test_ip_pair_display() {
let pair = IpPair {
src_ip: Ipv4Addr::new(192, 168, 1, 1),
dst_ip: Ipv4Addr::new(192, 168, 1, 2),
is_local: true,
protocol: 6,
};
let display = format!("{}", pair);
assert!(display.contains("local"));
}
#[test]
fn test_stat_key_ord() {
let key1 = StatKey {
src_port: 80,
dst_port: 443,
src_ip: Ipv4Addr::new(192, 168, 1, 1),
dst_ip: Ipv4Addr::new(192, 168, 1, 2),
direction: Direction::Local,
protocol: 6,
tcp_syn: false,
tcp_ack: false,
tcp_fin: false,
tcp_rst: false,
};
let key2 = StatKey {
src_port: 80,
dst_port: 443,
src_ip: Ipv4Addr::new(192, 168, 1, 1),
dst_ip: Ipv4Addr::new(192, 168, 1, 2),
direction: Direction::Local,
protocol: 6,
tcp_syn: false,
tcp_ack: false,
tcp_fin: false,
tcp_rst: false,
};
assert_eq!(key1, key2);
}
}