//
// Syd: rock-solid unikernel
// src/hash.rs: Utilities for caching
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

// SAFETY: PathBuf is fine for the cache as we want it on the heap.
#[allow(clippy::disallowed_types)]
use std::{
    borrow::Borrow,
    net::IpAddr,
    os::unix::ffi::OsStrExt,
    path::{Path, PathBuf},
};

use quick_cache::{sync::Cache, Equivalent, Weighter};

use crate::{
    sandbox::{Action, Capability},
    wildmatch::litmatch,
};

/// Value type for action caches.
/// The boolean represents filtering.
pub type CacheVal = (Action, bool);

/// Key type for the Path Cache.
//#[derive(Clone, Debug, Hash, Eq, PartialEq)]
#[allow(clippy::disallowed_types)]
pub type PathCacheKey = (Capability, PathBuf);

/// Key type for the IP Address Cache.
/// u16 represents the port.
pub type AddrCacheKey = (Capability, IpAddr, u16);

/// Path action cache to cache security policies about paths.
/// This is similar to Action Vector Cache of SELinux.
pub type PathCache = Cache<PathCacheKey, CacheVal, PathWeighter>;

/// IP address action cache to cache security policies about internet addresses.
/// This is similar to Action Vector Cache of SELinux.
pub type AddrCache = Cache<AddrCacheKey, CacheVal, PortWeighter>;

/// Convenience type to avoid cloning Paths during lookup.
#[derive(Hash)]
pub struct PathCap<'a>(pub Capability, pub &'a Path);

impl<'a> Equivalent<PathCacheKey> for PathCap<'a> {
    fn equivalent(&self, key: &PathCacheKey) -> bool {
        self.0 == key.0 && litmatch(self.1.as_os_str().as_bytes(), key.1.as_os_str().as_bytes())
    }
}

impl ToOwned for PathCap<'_> {
    type Owned = PathCacheKey;

    fn to_owned(&self) -> Self::Owned {
        (self.0, self.1.to_path_buf())
    }
}

impl<'a> Borrow<PathCap<'a>> for PathCacheKey {
    fn borrow(&self) -> &PathCap<'a> {
        unreachable!();
    }
}

/// A length-based weighter for paths where shorter paths are given
/// higher weights.
///
/// This weighter inversely weights the length of the path relative to
/// the maximum possible path length defined by the operating system
/// (`PATH_MAX`). Shorter paths, typically accessed more frequently, are
/// assigned higher weights to prioritize their retention in the cache.
/// This method helps optimize cache performance by better aligning item
/// retention with observed access patterns where shorter paths are more
/// significant.
#[derive(Clone)]
pub struct PathWeighter;

impl Weighter<PathCacheKey, CacheVal> for PathWeighter {
    #[allow(clippy::arithmetic_side_effects)]
    fn weight(&self, key: &PathCacheKey, _: &CacheVal) -> u64 {
        // Calculate the weight inversely proportional to the path length.
        // Use `PATH_MAX` as the normalization base.
        let len = key.1.as_os_str().as_bytes().len() as u64;

        // Ensure that we do not assign a zero weight which could make
        // items immune to eviction. +1 cannot possible overflow.
        (nix::libc::PATH_MAX as u64).saturating_sub(len) + 1
    }
}

/// A port weighter for IP addresses where lower ports are given higher
/// weights.
///
/// This weighter inversely weights the port numbers relative to the
/// maximum possible port number (`u16::MAX`). Lower port numbers, which
/// are often more critical or commonly used (e.g., well-known ports
/// like 80 for HTTP), are assigned higher weights to prioritize their
/// retention in the cache. This approach aligns the caching strategy
/// with typical network service priorities where lower numbered ports
/// are more significant.
#[derive(Clone)]
pub struct PortWeighter;

impl Weighter<AddrCacheKey, CacheVal> for PortWeighter {
    #[allow(clippy::arithmetic_side_effects)]
    fn weight(&self, key: &AddrCacheKey, _: &CacheVal) -> u64 {
        // Calculate the weight as the inverse of the port number,
        // normalized by u16::MAX. This ensures that lower port numbers
        // have higher weights. `u16::MAX` minus the port number gives
        // lower ports higher weight values. +1 ensures there is no
        // zero weight, which could make an item immune to eviction.
        u64::from(u16::MAX - key.2) + 1
    }
}

/// Create a new Path Action Cache.
pub fn path_cache_new(icap: usize, wcap: u64) -> PathCache {
    PathCache::with_weighter(icap, wcap, PathWeighter)
}

/// Create a new Address Action Cache.
pub fn addr_cache_new(icap: usize, wcap: u64) -> AddrCache {
    AddrCache::with_weighter(icap, wcap, PortWeighter)
}
