use std::fmt;
use std::net::IpAddr;
use std::time::Duration;
use serde::Serialize;
use crate::proto::mount::{ExportEntry, MountedClient};
use crate::proto::portmap::PortmapEntry;
#[derive(Debug, Clone)]
pub(crate) struct TargetSpec {
pub ip: IpAddr,
pub hostname: Option<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PortReachability {
Tcp,
Udp,
TcpUdp,
Unreachable,
}
impl PortReachability {
#[must_use]
pub(crate) const fn from_probes(tcp: bool, udp: bool) -> Self {
match (tcp, udp) {
(true, true) => Self::TcpUdp,
(true, false) => Self::Tcp,
(false, true) => Self::Udp,
(false, false) => Self::Unreachable,
}
}
#[must_use]
pub(crate) const fn is_reachable(&self) -> bool {
!matches!(self, Self::Unreachable)
}
#[must_use]
pub(crate) const fn has_tcp(&self) -> bool {
matches!(self, Self::Tcp | Self::TcpUdp)
}
}
impl fmt::Display for PortReachability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tcp => f.write_str("tcp"),
Self::Udp => f.write_str("udp"),
Self::TcpUdp => f.write_str("tcp+udp"),
Self::Unreachable => f.write_str("--"),
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub(crate) struct VersionRange {
pub low: u32,
pub high: u32,
}
impl fmt::Display for VersionRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.low == self.high { write!(f, "v{}", self.low) } else { write!(f, "v{}-v{}", self.low, self.high) }
}
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct NfsPortInfo {
pub port: u16,
pub tcp: bool,
pub udp: bool,
pub v2: bool,
pub v3: bool,
pub v4: bool,
}
impl NfsPortInfo {
#[must_use]
pub(crate) const fn any_version(&self) -> bool {
self.v2 || self.v3 || self.v4
}
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct MountPortInfo {
pub port: u16,
pub tcp: bool,
pub udp: bool,
pub versions: Vec<u32>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub(crate) struct V4ExportEntry {
pub path: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub auth_flavors: Vec<u32>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct HostResult {
pub ip: IpAddr,
#[serde(skip_serializing_if = "Option::is_none")]
pub hostname: Option<String>,
pub portmap_reachability: PortReachability,
pub nfs_ports: Vec<NfsPortInfo>,
pub mount_ports: Vec<MountPortInfo>,
pub exports_v2: Option<Vec<ExportEntry>>,
pub exports_v3: Option<Vec<ExportEntry>>,
pub exports_v4: Option<Vec<V4ExportEntry>>,
pub rpc_services: Vec<PortmapEntry>,
pub mounts: Option<Vec<MountedClient>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hint: Option<VersionRange>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub rdma_detected: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub os_guess: Option<String>,
#[serde(with = "duration_ms")]
pub scan_duration: Duration,
}
impl HostResult {
#[must_use]
pub(crate) fn has_nfs(&self) -> bool {
self.nfs_ports.iter().any(NfsPortInfo::any_version)
}
#[must_use]
pub(crate) fn has_v2(&self) -> bool {
self.nfs_ports.iter().any(|p| p.v2)
}
#[must_use]
pub(crate) fn has_v3(&self) -> bool {
self.nfs_ports.iter().any(|p| p.v3)
}
#[must_use]
pub(crate) fn has_v4(&self) -> bool {
self.nfs_ports.iter().any(|p| p.v4)
}
}
mod duration_ms {
use std::time::Duration;
use serde::{self, Serializer};
pub(super) fn serialize<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
s.serialize_u64(d.as_millis().try_into().map_err(serde::ser::Error::custom)?)
}
}