// SPDX-License-Identifier: MIT

use netlink_packet_core::{
    NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable,
};
use netlink_packet_utils::{
    traits::{Emitable, ParseableParametrized},
    DecodeError,
};

use crate::{constants::*, rules::RuleMessage, AuditBuffer, StatusMessage};

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum AuditMessage {
    GetStatus(Option<StatusMessage>),
    SetStatus(StatusMessage),
    AddRule(RuleMessage),
    DelRule(RuleMessage),
    ListRules(Option<RuleMessage>),
    /// Event message (message types 1300 through 1399). This includes the
    /// following message types (this list is non-exhaustive, and not
    /// really kept up to date): `AUDIT_SYSCALL`, `AUDIT_PATH`,
    /// `AUDIT_IPC`, `AUDIT_SOCKETCALL`, `AUDIT_CONFIG_CHANGE`,
    /// `AUDIT_SOCKADDR`, `AUDIT_CWD`, `AUDIT_EXECVE`,
    /// `AUDIT_IPC_SET_PERM`, `AUDIT_MQ_OPEN`, `AUDIT_MQ_SENDRECV`,
    /// `AUDIT_MQ_NOTIFY`, `AUDIT_MQ_GETSETATTR`, `AUDIT_KERNEL_OTHER`,
    /// `AUDIT_FD_PAIR`, `AUDIT_OBJ_PID`, `AUDIT_TTY`, `AUDIT_EOE`,
    /// `AUDIT_BPRM_FCAPS`, `AUDIT_CAPSET`, `AUDIT_MMAP`,
    /// `AUDIT_NETFILTER_PKT`, `AUDIT_NETFILTER_CFG`, `AUDIT_SECCOMP`,
    /// `AUDIT_PROCTITLE`, `AUDIT_FEATURE_CHANGE`, `AUDIT_REPLACE`,
    /// `AUDIT_KERN_MODULE`, `AUDIT_FANOTIFY`.
    ///
    /// The first element of the tuple is the message type, and the second is
    /// the event data.
    Event((u16, String)),
    /// All the other events are parsed as such as they can be parsed also.
    Other((u16, String)),
}

impl AuditMessage {
    pub fn is_event(&self) -> bool {
        matches!(self, AuditMessage::Event(_))
    }

    pub fn is_get_status(&self) -> bool {
        matches!(self, AuditMessage::GetStatus(_))
    }

    pub fn is_set_status(&self) -> bool {
        matches!(self, AuditMessage::GetStatus(_))
    }

    pub fn is_add_rule(&self) -> bool {
        matches!(self, AuditMessage::AddRule(_))
    }

    pub fn is_del_rule(&self) -> bool {
        matches!(self, AuditMessage::DelRule(_))
    }

    pub fn is_list_rules(&self) -> bool {
        matches!(self, AuditMessage::ListRules(_))
    }

    pub fn message_type(&self) -> u16 {
        use self::AuditMessage::*;

        match self {
            GetStatus(_) => AUDIT_GET,
            SetStatus(_) => AUDIT_SET,
            ListRules(_) => AUDIT_LIST_RULES,
            AddRule(_) => AUDIT_ADD_RULE,
            DelRule(_) => AUDIT_DEL_RULE,
            Event((message_type, _)) => *message_type,
            Other((message_type, _)) => *message_type,
        }
    }
}

impl Emitable for AuditMessage {
    fn buffer_len(&self) -> usize {
        use self::AuditMessage::*;

        match self {
            GetStatus(Some(ref msg)) => msg.buffer_len(),
            SetStatus(ref msg) => msg.buffer_len(),
            AddRule(ref msg) => msg.buffer_len(),
            DelRule(ref msg) => msg.buffer_len(),
            ListRules(Some(ref msg)) => msg.buffer_len(),
            GetStatus(None) | ListRules(None) => 0,
            Event((_, ref data)) => data.len(),
            Other((_, ref data)) => data.len(),
        }
    }

    fn emit(&self, buffer: &mut [u8]) {
        use self::AuditMessage::*;

        match self {
            GetStatus(Some(ref msg)) => msg.emit(buffer),
            SetStatus(ref msg) => msg.emit(buffer),
            AddRule(ref msg) => msg.emit(buffer),
            DelRule(ref msg) => msg.emit(buffer),
            ListRules(Some(ref msg)) => msg.emit(buffer),
            ListRules(None) | GetStatus(None) => {}
            Event((_, ref data)) => buffer.copy_from_slice(data.as_bytes()),
            Other((_, ref data)) => buffer.copy_from_slice(data.as_bytes()),
        }
    }
}

impl NetlinkSerializable for AuditMessage {
    fn message_type(&self) -> u16 {
        self.message_type()
    }

    fn buffer_len(&self) -> usize {
        <Self as Emitable>::buffer_len(self)
    }

    fn serialize(&self, buffer: &mut [u8]) {
        self.emit(buffer)
    }
}

impl NetlinkDeserializable for AuditMessage {
    type Error = DecodeError;
    fn deserialize(
        header: &NetlinkHeader,
        payload: &[u8],
    ) -> Result<Self, Self::Error> {
        match AuditBuffer::new_checked(payload) {
            Err(e) => Err(e),
            Ok(buffer) => match AuditMessage::parse_with_param(
                &buffer,
                header.message_type,
            ) {
                Err(e) => Err(e),
                Ok(message) => Ok(message),
            },
        }
    }
}

impl From<AuditMessage> for NetlinkPayload<AuditMessage> {
    fn from(message: AuditMessage) -> Self {
        NetlinkPayload::InnerMessage(message)
    }
}
