// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::landlock::{
    compat::TryCompat, uapi, Access, AddRuleError, AddRulesError, HandleAccessError,
    HandleAccessesError, PrivateAccess, Ruleset, TailoredCompatLevel, ABI,
};

crate::landlock::access::bitflags_type! {
    /// Scope flags
    ///
    /// Each variant of `Scope` is an [scope
    /// flag](https://docs.kernel.org/userspace-api/landlock.html#scope-flags).
    ///
    /// # Example
    ///
    /// ```
    /// use syd::landlock::{ABI, Access, Scope};
    ///
    /// let scope_set = Scope::AbstractUnixSocket | Scope::Signal;
    ///
    /// let scope_v6 = Scope::from_all(ABI::V6);
    ///
    /// assert_eq!(scope_set, scope_v6);
    /// ```
    pub struct Scope: u64 {
        /// Restrict a sandboxed process from connecting to an abstract UNIX socket created by a
        /// process outside the related Landlock domain
        const AbstractUnixSocket = uapi::LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET as u64;
        /// Restrict a sandboxed process from sending a signal to another process outside the
        /// domain.
        const Signal = uapi::LANDLOCK_SCOPE_SIGNAL as u64;
    }
}

impl TailoredCompatLevel for Scope {}

/// # Warning
///
/// If `ABI <= ABI::V5`, `Scope::from_all()` returns an empty `Scope`, which
/// makes `Ruleset::handle_access(Scope::from_all(ABI::V5))` return an error.
impl Access for Scope {
    fn from_all(abi: ABI) -> Self {
        match abi {
            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 => Scope::EMPTY,
            ABI::V6 | ABI::V7 => Scope::AbstractUnixSocket | Scope::Signal,
        }
    }
}

impl PrivateAccess for Scope {
    fn is_empty(self) -> bool {
        Scope::is_empty(&self)
    }

    fn ruleset_handle_access(
        ruleset: &mut Ruleset,
        scope: Self,
    ) -> Result<(), HandleAccessesError> {
        // We need to record the requested scopes for PrivateRule::check_consistency().
        ruleset.requested_scoped |= scope;
        if let Some(a) = scope
            .try_compat(
                ruleset.compat.abi(),
                ruleset.compat.level,
                &mut ruleset.compat.state,
            )
            .map_err(HandleAccessError::Compat)?
        {
            ruleset.actual_scoped |= a;
        }
        Ok(())
    }

    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
        AddRulesError::Scope(error)
    }

    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
        HandleAccessesError::Scope(error)
    }
}
