use std::fmt::{Debug, Display, Write};
use std::hash::Hash;
use std::net::IpAddr;
use chrono::{DateTime, Local, TimeZone, Timelike, Utc};
use deepsize::DeepSizeOf;
use flagset::{flags, FlagSet};
use serde::{Deserialize, Serialize};
use tracing::error;
use crate::analyze::fmt_timestamp;
use crate::errors::StoreError;
use crate::store::Version;
#[derive(Debug, PartialEq, Eq, Hash, Deserialize, Serialize, Clone, Copy, DeepSizeOf)]
pub enum IpType {
V4,
V6,
}
pub const TARGETS: &[&str] = &["1.1.1.1", "2606:4700:4700::1111"];
flags! {
#[derive(Hash, Deserialize, Serialize)]
pub enum CheckFlag: u16 {
Success = 0b0000_0000_0000_0001,
Timeout = 0b0000_0000_0000_0010,
Unreachable = 0b0000_0000_0000_0100,
TypeHTTP = 0b0001_0000_0000_0000,
TypeIcmp = 0b0100_0000_0000_0000,
TypeDns = 0b1000_0000_0000_0000,
}
}
#[derive(Debug, PartialEq, Eq, Hash, Deserialize, Serialize, Clone, Copy, DeepSizeOf)]
pub enum CheckType {
Dns,
Http,
Icmp,
Unknown,
}
impl CheckType {
pub fn make(&self, remote: IpAddr) -> Check {
let mut check = Check::new(Utc::now(), FlagSet::default(), None, remote);
match self {
#[cfg(feature = "http")]
Self::Http => {
check.add_flag(CheckFlag::TypeHTTP);
match crate::checks::check_http(remote) {
Err(err) => {
error!("error while performing an Http check: {err}")
}
Ok(lat) => {
check.add_flag(CheckFlag::Success);
check.latency = Some(lat);
}
}
}
#[cfg(not(feature = "http"))]
Self::Http => {
panic!("Trying to make a http check, but the http feature is not enabled")
}
#[cfg(feature = "ping")]
Self::Icmp => {
check.add_flag(CheckFlag::TypeIcmp);
match crate::checks::just_fucking_ping(remote) {
Err(err) => {
error!("error while performing an ICMPv4 check: {err}")
}
Ok(lat) => {
check.add_flag(CheckFlag::Success);
check.latency = Some(lat);
}
}
}
#[cfg(not(feature = "ping"))]
Self::Icmp => {
panic!("Trying to make a ICMPv4 check, but the ping feature is not enabled")
}
Self::Unknown => {
panic!("tried to make an Unknown check");
}
Self::Dns => {
todo!("dns not done yet")
}
}
check
}
pub const fn all() -> &'static [Self] {
&[Self::Dns, Self::Http, Self::Icmp]
}
pub const fn default_enabled() -> &'static [Self] {
&[
#[cfg(feature = "http")]
Self::Http,
#[cfg(feature = "ping")]
Self::Icmp,
]
}
}
impl Display for CheckType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Dns => "DNS",
Self::Http => "HTTP(S)",
Self::Icmp => "ICMP",
Self::Unknown => "Unknown",
}
)
}
}
#[derive(PartialEq, Eq, Hash, Deserialize, Serialize, Clone, Copy)]
pub struct Check {
timestamp: i64,
flags: FlagSet<CheckFlag>,
latency: Option<u16>,
target: IpAddr,
}
impl DeepSizeOf for Check {
fn deep_size_of_children(&self, context: &mut deepsize::Context) -> usize {
self.latency.deep_size_of_children(context)
}
}
impl PartialOrd for Check {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Check {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.timestamp.cmp(&other.timestamp)
}
}
impl Check {
pub fn get_hash(&self) -> blake3::Hash {
blake3::hash(&bincode::serialize(&self).expect("serialization of a check failed"))
}
pub fn new(
time: impl Into<DateTime<Utc>>,
flags: impl Into<FlagSet<CheckFlag>>,
latency: Option<u16>,
target: IpAddr,
) -> Self {
let mut t: DateTime<Utc> = time.into();
t = t
.with_second(0)
.expect("minute with second 0 does not exist");
t = t
.with_nanosecond(0)
.expect("minute with nanosecond 0 does not exist");
Check {
timestamp: t.timestamp(),
flags: flags.into(),
latency,
target,
}
}
pub fn is_success(&self) -> bool {
self.flags.contains(CheckFlag::Success)
}
pub fn latency(&self) -> Option<u16> {
if !self.is_success() {
None
} else {
self.latency
}
}
pub fn flags(&self) -> FlagSet<CheckFlag> {
self.flags
}
pub fn timestamp(&self) -> i64 {
self.timestamp
}
pub fn timestamp_parsed(&self) -> chrono::DateTime<Local> {
let t: DateTime<Local> = Local.timestamp_opt(self.timestamp(), 0).unwrap();
t
}
pub fn flags_mut(&mut self) -> &mut FlagSet<CheckFlag> {
&mut self.flags
}
pub fn add_flag(&mut self, flag: CheckFlag) {
self.flags |= flag
}
pub fn calc_type(&self) -> Result<CheckType, StoreError> {
Ok(if self.flags.contains(CheckFlag::TypeHTTP) {
CheckType::Http
} else if self.flags.contains(CheckFlag::TypeDns) {
CheckType::Dns
} else if self.flags.contains(CheckFlag::TypeIcmp) {
CheckType::Icmp
} else {
CheckType::Unknown
})
}
pub fn set_target(&mut self, target: IpAddr) {
self.target = target;
}
pub fn ip_type(&self) -> IpType {
IpType::from(self.target)
}
pub fn migrate(&mut self, current: Version) -> Result<(), StoreError> {
match current {
Version::V0 => (),
Version::V1 => self.timestamp = i64::from_ne_bytes(self.timestamp.to_ne_bytes()), _ => unimplemented!("migrating from Version {current} is not yet imlpemented"),
}
Ok(())
}
pub fn target(&self) -> IpAddr {
self.target
}
}
impl Display for Check {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Time: {}\nType: {}\nOk: {}\nTarget: {}\nLatency: {}\nHash: {}",
fmt_timestamp(self.timestamp_parsed()),
self.calc_type().unwrap_or(CheckType::Unknown),
self.is_success(),
self.target,
match self.latency() {
Some(l) => format!("{l} ms"),
None => "(Error)".to_string(),
},
self.get_hash()
)
}
}
impl From<IpAddr> for IpType {
fn from(value: IpAddr) -> Self {
match value {
IpAddr::V4(_) => Self::V4,
IpAddr::V6(_) => Self::V6,
}
}
}
impl Debug for Check {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Check")
.field("timestamp", &self.timestamp_parsed())
.field("flags", &self.flags())
.field("latency", &self.latency())
.field("target", &self.target())
.finish()
}
}
pub fn display_group(group: &[&Check], f: &mut String) -> Result<(), std::fmt::Error> {
if group.is_empty() {
writeln!(f, "\t<Empty>")?;
return Ok(());
}
for (cidx, check) in group.iter().enumerate() {
writeln!(f, "{cidx}:")?;
writeln!(f, "\t{}", check.to_string().replace("\n", "\n\t"))?;
}
Ok(())
}
#[cfg(test)]
mod test {
use crate::TIMEOUT_MS;
use std::time;
use super::*;
#[test]
fn test_creating_check() {
let _c = Check::new(
time::SystemTime::now(),
CheckFlag::Success,
Some(TIMEOUT_MS),
"127.0.0.1".parse().unwrap(),
);
}
#[test]
fn test_check_size_of_check() {
let c = Check::new(
time::SystemTime::now(),
CheckFlag::Success,
Some(TIMEOUT_MS),
"127.0.0.1".parse().unwrap(),
);
assert_eq!(
c.deep_size_of(),
std::mem::size_of::<IpAddr>() + std::mem::size_of::<i64>() + std::mem::size_of::<u16>() +3 + 2 );
let c1 = Check::new(
time::SystemTime::now(),
CheckFlag::Timeout,
None,
"127.0.0.1".parse().unwrap(),
);
assert_eq!(
c1.deep_size_of(),
std::mem::size_of::<IpAddr>() + std::mem::size_of::<i64>() + std::mem::size_of::<u16>() +3 + 2 );
let c2 = Check::new(
time::SystemTime::now(),
CheckFlag::Timeout,
None,
"::1".parse().unwrap(),
);
assert_eq!(
c2.deep_size_of(),
std::mem::size_of::<IpAddr>() + std::mem::size_of::<i64>() + std::mem::size_of::<u16>() +3 + 2 )
}
}