use std::sync::{Arc, Mutex};
use crate::vortix_core::ports::killswitch::{KillswitchError, Result as KsResult};
#[cfg(target_os = "linux")]
use crate::vortix_platform_linux as platform_impl;
#[cfg(target_os = "macos")]
use crate::vortix_platform_macos as platform_impl;
#[cfg(target_os = "windows")]
use crate::vortix_platform_windows as platform_impl;
#[derive(Debug, Default, Clone)]
pub struct MockKillswitch {
state: Arc<Mutex<MockKillswitchState>>,
}
#[derive(Debug, Default)]
struct MockKillswitchState {
pub fail_enable: Option<String>,
pub fail_disable: Option<String>,
pub enabled: bool,
pub disabled: bool,
}
impl MockKillswitch {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn fail_next_enable(&self, msg: impl Into<String>) {
self.state.lock().unwrap().fail_enable = Some(msg.into());
}
#[must_use]
pub fn was_enabled(&self) -> bool {
self.state.lock().unwrap().enabled
}
#[must_use]
pub fn was_disabled(&self) -> bool {
self.state.lock().unwrap().disabled
}
fn enable_blocking(&self, _iface: &str, _server: Option<&str>) -> KsResult<()> {
let mut s = self.state.lock().unwrap();
if let Some(msg) = s.fail_enable.take() {
return Err(KillswitchError::CommandFailed(msg));
}
s.enabled = true;
Ok(())
}
fn disable_blocking(&self) -> KsResult<()> {
let mut s = self.state.lock().unwrap();
if let Some(msg) = s.fail_disable.take() {
return Err(KillswitchError::CommandFailed(msg));
}
s.disabled = true;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct MockDns {
pub dns: Option<String>,
}
#[derive(Debug, Default, Clone)]
pub struct MockInterface {
pub wg_present: bool,
}
#[derive(Debug, Default, Clone)]
pub struct MockNetworkStats {
pub bytes_in: u64,
pub bytes_out: u64,
}
#[derive(Debug, Default, Clone)]
pub struct MockRouteTable {
pub gateway: Option<String>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum KillswitchKind {
#[cfg(target_os = "macos")]
Macos,
#[cfg(target_os = "linux")]
Linux,
#[cfg(target_os = "windows")]
Windows,
Mock(MockKillswitch),
}
impl KillswitchKind {
pub fn enable_blocking(
&self,
vpn_interface: &str,
vpn_server_ip: Option<&str>,
) -> KsResult<()> {
use crate::vortix_core::ports::killswitch::Killswitch;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::PfFirewall::enable_blocking(vpn_interface, vpn_server_ip),
#[cfg(target_os = "linux")]
Self::Linux => {
platform_impl::IptablesFirewall::enable_blocking(vpn_interface, vpn_server_ip)
}
#[cfg(target_os = "windows")]
Self::Windows => {
platform_impl::WindowsFirewall::enable_blocking(vpn_interface, vpn_server_ip)
}
Self::Mock(m) => m.enable_blocking(vpn_interface, vpn_server_ip),
}
}
pub fn disable_blocking(&self) -> KsResult<()> {
use crate::vortix_core::ports::killswitch::Killswitch;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::PfFirewall::disable_blocking(),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::IptablesFirewall::disable_blocking(),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsFirewall::disable_blocking(),
Self::Mock(m) => m.disable_blocking(),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum DnsResolverKind {
#[cfg(target_os = "macos")]
Macos,
#[cfg(target_os = "linux")]
Linux,
#[cfg(target_os = "windows")]
Windows,
Mock(MockDns),
}
impl DnsResolverKind {
#[must_use]
pub fn get_dns_server(&self) -> Option<String> {
use crate::vortix_core::ports::dns::DnsResolver;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::MacDns::get_dns_server(),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::LinuxDns::get_dns_server(),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsDns::get_dns_server(),
Self::Mock(m) => m.dns.clone(),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum InterfaceKind {
#[cfg(target_os = "macos")]
Macos,
#[cfg(target_os = "linux")]
Linux,
#[cfg(target_os = "windows")]
Windows,
Mock(MockInterface),
}
impl InterfaceKind {
#[must_use]
pub fn check_wireguard_interface(&self, name: &str) -> bool {
use crate::vortix_core::ports::interface::Interface;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::MacInterface::check_wireguard_interface(name),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::LinuxInterface::check_wireguard_interface(name),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsInterface::check_wireguard_interface(name),
Self::Mock(m) => m.wg_present,
}
}
#[must_use]
pub fn resolve_wireguard_interface(&self, name: &str) -> Option<String> {
use crate::vortix_core::ports::interface::Interface;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::MacInterface::resolve_wireguard_interface(name),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::LinuxInterface::resolve_wireguard_interface(name),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsInterface::resolve_wireguard_interface(name),
Self::Mock(m) => {
if m.wg_present {
Some(name.to_string())
} else {
None
}
}
}
}
#[must_use]
pub fn get_wireguard_pid(&self, interface: &str) -> Option<u32> {
use crate::vortix_core::ports::interface::Interface;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::MacInterface::get_wireguard_pid(interface),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::LinuxInterface::get_wireguard_pid(interface),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsInterface::get_wireguard_pid(interface),
Self::Mock(_) => None,
}
}
#[must_use]
pub fn get_interface_info(&self, interface: &str) -> (String, String) {
use crate::vortix_core::ports::interface::Interface;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::MacInterface::get_interface_info(interface),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::LinuxInterface::get_interface_info(interface),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsInterface::get_interface_info(interface),
Self::Mock(_) => (String::new(), String::new()),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum NetworkStatsKind {
#[cfg(target_os = "macos")]
Macos,
#[cfg(target_os = "linux")]
Linux,
#[cfg(target_os = "windows")]
Windows,
Mock(MockNetworkStats),
}
impl NetworkStatsKind {
#[must_use]
pub fn get_total_bytes(&self) -> (u64, u64) {
use crate::vortix_core::ports::network_stats::NetworkStats;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::MacNetworkStats::get_total_bytes(),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::LinuxNetworkStats::get_total_bytes(),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsNetworkStats::get_total_bytes(),
Self::Mock(m) => (m.bytes_in, m.bytes_out),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum RouteTableKind {
#[cfg(target_os = "macos")]
Macos,
#[cfg(target_os = "linux")]
Linux,
#[cfg(target_os = "windows")]
Windows,
Mock(MockRouteTable),
}
impl RouteTableKind {
#[must_use]
pub fn default_gateway(&self) -> Option<String> {
use crate::vortix_core::ports::route_table::RouteTable;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::MacRouteTable::default_gateway(),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::LinuxRouteTable::default_gateway(),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsRouteTable::default_gateway(),
Self::Mock(m) => m.gateway.clone(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct MockSocketAudit {
pub canned: Vec<crate::vortix_core::ports::socket_audit::SocketSnapshot>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum SocketAuditKind {
#[cfg(target_os = "macos")]
Macos,
#[cfg(target_os = "linux")]
Linux,
#[cfg(target_os = "windows")]
Windows,
Mock(MockSocketAudit),
}
impl SocketAuditKind {
pub fn snapshot(
&self,
) -> crate::vortix_core::ports::socket_audit::SocketAuditResult<
Vec<crate::vortix_core::ports::socket_audit::SocketSnapshot>,
> {
use crate::vortix_core::ports::socket_audit::SocketAudit;
match self {
#[cfg(target_os = "macos")]
Self::Macos => platform_impl::LsofSocketAudit::snapshot(),
#[cfg(target_os = "linux")]
Self::Linux => platform_impl::ProcSocketAudit::snapshot(),
#[cfg(target_os = "windows")]
Self::Windows => platform_impl::WindowsSocketAudit::snapshot(),
Self::Mock(m) => Ok(m.canned.clone()),
}
}
}
#[derive(Debug, Clone)]
pub struct Platform {
pub killswitch: KillswitchKind,
pub dns: DnsResolverKind,
pub interface: InterfaceKind,
pub network_stats: NetworkStatsKind,
pub route_table: RouteTableKind,
pub socket_audit: SocketAuditKind,
}
impl Platform {
#[must_use]
pub fn detect_current() -> Self {
#[cfg(target_os = "macos")]
{
Self {
killswitch: KillswitchKind::Macos,
dns: DnsResolverKind::Macos,
interface: InterfaceKind::Macos,
network_stats: NetworkStatsKind::Macos,
route_table: RouteTableKind::Macos,
socket_audit: SocketAuditKind::Macos,
}
}
#[cfg(target_os = "linux")]
{
Self {
killswitch: KillswitchKind::Linux,
dns: DnsResolverKind::Linux,
interface: InterfaceKind::Linux,
network_stats: NetworkStatsKind::Linux,
route_table: RouteTableKind::Linux,
socket_audit: SocketAuditKind::Linux,
}
}
#[cfg(target_os = "windows")]
{
Self {
killswitch: KillswitchKind::Windows,
dns: DnsResolverKind::Windows,
interface: InterfaceKind::Windows,
network_stats: NetworkStatsKind::Windows,
route_table: RouteTableKind::Windows,
socket_audit: SocketAuditKind::Windows,
}
}
}
#[must_use]
pub fn for_test() -> Self {
Self {
killswitch: KillswitchKind::Mock(MockKillswitch::new()),
dns: DnsResolverKind::Mock(MockDns::default()),
interface: InterfaceKind::Mock(MockInterface::default()),
network_stats: NetworkStatsKind::Mock(MockNetworkStats::default()),
route_table: RouteTableKind::Mock(MockRouteTable::default()),
socket_audit: SocketAuditKind::Mock(MockSocketAudit::default()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_test_uses_mock_variants() {
let p = Platform::for_test();
assert!(matches!(p.killswitch, KillswitchKind::Mock(_)));
assert!(matches!(p.dns, DnsResolverKind::Mock(_)));
assert!(matches!(p.interface, InterfaceKind::Mock(_)));
assert!(matches!(p.network_stats, NetworkStatsKind::Mock(_)));
assert!(matches!(p.route_table, RouteTableKind::Mock(_)));
}
#[test]
fn mock_killswitch_records_calls() {
let mock = MockKillswitch::new();
assert!(!mock.was_enabled());
let ks = KillswitchKind::Mock(mock.clone());
ks.enable_blocking("wg0", Some("1.2.3.4")).unwrap();
assert!(mock.was_enabled());
ks.disable_blocking().unwrap();
assert!(mock.was_disabled());
}
#[test]
fn mock_killswitch_scripts_failure() {
let mock = MockKillswitch::new();
mock.fail_next_enable("simulated iptables error");
let ks = KillswitchKind::Mock(mock);
let err = ks.enable_blocking("wg0", None).unwrap_err();
assert!(matches!(err, KillswitchError::CommandFailed(_)));
}
#[test]
fn mock_dns_returns_canned_value() {
let dns = DnsResolverKind::Mock(MockDns {
dns: Some("1.1.1.1".into()),
});
assert_eq!(dns.get_dns_server(), Some("1.1.1.1".into()));
}
#[test]
fn mock_route_table_returns_canned_gateway() {
let rt = RouteTableKind::Mock(MockRouteTable {
gateway: Some("192.168.1.1".into()),
});
assert_eq!(rt.default_gateway(), Some("192.168.1.1".into()));
}
}