//! Interface to Packet Filter.
//!
//! Source code:
//!
//! Darwin:
//! - https://github.com/apple-oss-distributions/xnu/blob/main/bsd/net/pfvar.h
//!
//! FreeBSD:
//! - https://github.com/freebsd/freebsd-src/blob/main/sys/net/pfvar.h
//! - https://github.com/freebsd/freebsd-src/blob/main/sys/netpfil/pf/pf.h
//!
//! https://man.netbsd.org/pf.4
//! https://man.freebsd.org/cgi/man.cgi?pf
//! https://man.openbsd.org/pf.4

mod calls;
mod rule;

use std::os::fd::{AsRawFd, RawFd};

use defguard_wireguard_rs::bsd::c_int_to_error;
use libc::ioctl;

use self::{
    calls::{Change, DIOCADDRULE, DIOCBEGINADDRS, IocPoolAddr, IocRule, Rule},
    rule::PacketFilterRule,
};
use super::{FirewallError, FirewallRule, api::FirewallApi};
use crate::enterprise::firewall::Port;

const ANCHOR_PREFIX: &str = "defguard/";

impl FirewallApi {
    /// Construct anchor name based on prefix and network interface name.
    fn anchor(&self) -> String {
        ANCHOR_PREFIX.to_owned() + &self.ifname
    }

    /// Return raw file descriptor to Packet Filter device.
    fn fd(&self) -> RawFd {
        self.file.as_raw_fd()
    }

    fn get_pool_ticket(&self, anchor: &str) -> Result<u32, FirewallError> {
        let mut ioc = IocPoolAddr::new(anchor);

        let result = unsafe { ioctl(self.fd(), DIOCBEGINADDRS, &raw mut ioc) };
        c_int_to_error(result)?;

        Ok(ioc.ticket)
    }

    fn add_rule_policy(
        &self,
        ticket: u32,
        pool_ticket: u32,
        anchor: &str,
    ) -> Result<(), FirewallError> {
        let rule = PacketFilterRule::for_policy(self.default_policy, &self.ifname);
        let mut ioc = IocRule::with_rule(anchor, Rule::from_pf_rule(&rule));
        ioc.ticket = ticket;
        ioc.pool_ticket = pool_ticket;
        let result = unsafe { ioctl(self.fd(), DIOCADDRULE, &raw mut ioc) };
        if let Err(err) = c_int_to_error(result) {
            error!("Packet filter rule {rule} can't be added.");
            return Err(err.into());
        }

        Ok(())
    }

    /// Add a single firewall `rule`.
    fn add_rule(
        &self,
        rule: &FirewallRule,
        ticket: u32,
        pool_ticket: u32,
        anchor: &str,
    ) -> Result<(), FirewallError> {
        debug!("add_rule {rule:?}");
        let rules = PacketFilterRule::from_firewall_rule(&self.ifname, rule);

        for rule in rules {
            let mut ioc = IocRule::with_rule(anchor, Rule::from_pf_rule(&rule));
            ioc.action = Change::None;
            ioc.ticket = ticket;
            ioc.pool_ticket = pool_ticket;
            let result = unsafe { ioctl(self.fd(), DIOCADDRULE, &raw mut ioc) };
            if let Err(err) = c_int_to_error(result) {
                error!("Packet filter rule {rule} can't be added.");
                return Err(err.into());
            }
        }

        Ok(())
    }
}

mod api;
