// Syd: rock-solid application kernel
// src/kernel/net/accept.rs: accept(2) and accept4(2) handler
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::os::fd::{AsFd, OwnedFd};

use bitflags::bitflags;
use libseccomp::ScmpNotifResp;
use nix::{
    errno::Errno,
    sys::socket::{AddressFamily, SockFlag, SockaddrLike, SockaddrStorage},
};

use crate::{
    compat::getsockdomain,
    cookie::safe_accept4,
    fd::{get_nonblock, has_recv_timeout},
    kernel::net::sandbox_addr,
    req::UNotifyEventRequest,
    sandbox::Capability,
};

bitflags! {
    #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
    struct Accept4Flags: u8 {
        const WANT_ADDR = 1 << 0; // Want source address?
        const INTERRUPT = 1 << 1; // Blocking call?
        const IGNORERES = 1 << 2; // Ignore restarts?
    }
}

pub(crate) fn handle_accept(
    fd: OwnedFd,
    request: &UNotifyEventRequest,
    args: &[u64; 6],
    op: u8,
) -> Result<ScmpNotifResp, Errno> {
    // Determine address length if specified.
    let addrlen = if args[2] != 0 {
        const SIZEOF_SOCKLEN_T: usize = size_of::<libc::socklen_t>();
        let mut buf = [0u8; SIZEOF_SOCKLEN_T];
        if request.read_mem(&mut buf, args[2], SIZEOF_SOCKLEN_T)? == SIZEOF_SOCKLEN_T {
            // libc defines socklen_t as u32,
            // however we should check for negative values
            // and return EINVAL as necessary.
            let len = i32::from_ne_bytes(buf);
            let len = libc::socklen_t::try_from(len).or(Err(Errno::EINVAL))?;
            if len > 0 && args[1] == 0 {
                // address length is positive however address is NULL:
                // Return EINVAL and NOT EFAULT here, see LTP accept01 check.
                return Err(Errno::EINVAL);
            }
            Some(len)
        } else {
            // Invalid/short read, assume invalid address length.
            return Err(Errno::EINVAL);
        }
    } else {
        None
    };

    let sandbox = request.get_sandbox();
    let force_cloexec = sandbox.flags.force_cloexec();
    let force_rand_fd = sandbox.flags.force_rand_fd();
    drop(sandbox); // release read-lock.

    let mut flags = if op == 0x12 {
        // accept4
        SockFlag::from_bits(args[3].try_into().or(Err(Errno::EINVAL))?).ok_or(Errno::EINVAL)?
    } else {
        // accept
        SockFlag::empty()
    };
    let cloexec = force_cloexec || flags.contains(SockFlag::SOCK_CLOEXEC);
    flags.insert(SockFlag::SOCK_CLOEXEC);

    // Check whether we should block and ignore restarts.
    let (is_blocking, ignore_restart) = if !get_nonblock(&fd)? {
        let ignore_restart = has_recv_timeout(&fd)?;
        (true, ignore_restart)
    } else {
        (false, false)
    };

    let mut aflags = Accept4Flags::empty();
    if addrlen.is_some() || matches!(getsockdomain(&fd)?, libc::AF_INET | libc::AF_INET6) {
        // Source address is only checked for access for IPv{4,6} sockets.
        aflags.insert(Accept4Flags::WANT_ADDR);
    }
    if is_blocking {
        aflags.insert(Accept4Flags::INTERRUPT);
    }
    if ignore_restart {
        aflags.insert(Accept4Flags::IGNORERES);
    }

    // Do the accept call.
    let (fd, maybe_addr) = do_accept4(fd, request, flags, aflags)?;

    // Check sandbox access for IPv{4,6} sockets.
    if let Some((addr, _)) = maybe_addr.filter(|(addr, _)| {
        matches!(
            addr.family(),
            Some(AddressFamily::Inet | AddressFamily::Inet6)
        )
    }) {
        let sandbox = request.get_sandbox();
        sandbox_addr(request, &sandbox, &addr, &None, op, Capability::empty())?;
    }

    // Write address buffer as necessary.
    if let Some(addrlen) = addrlen {
        // SAFETY: addrlen.is_some() asserts maybe_addr.is_some().
        #[expect(clippy::disallowed_methods)]
        let (addr, addrlen_out) = maybe_addr.unwrap();

        // Create a byte slice from the socket address pointer.
        // SAFETY:
        // 1. `addrlen_out` value is returned by the host Linux kernel
        //    and is therefore trusted.
        // 2. `ptr` is a valid pointer to memory of at least
        //    `addrlen_out` bytes, as it is provided by the
        //    `SockaddrStorage` instance.
        // 3. The `SockaddrStorage` type ensures that the memory pointed
        //    to by `ptr` is valid and properly aligned.
        let buf = unsafe { std::slice::from_raw_parts(addr.as_ptr().cast(), addr.len() as usize) };

        // Write the truncated socket address into memory.
        // SAFETY: We truncate late to avoid potential UB in
        // std::slice::slice_from_raw_parts().
        let len = addrlen_out.min(addrlen) as usize;
        request.write_mem(&buf[..len], args[1])?;

        // Convert `len` into a vector of bytes.
        // SAFETY: This must be socklen_t and _not_ usize!
        #[expect(clippy::cast_possible_truncation)]
        let len = len as libc::socklen_t;
        let buf = len.to_ne_bytes();

        // Write `len` into memory.
        request.write_mem(&buf, args[2])?;
    }

    // Send the fd and return.
    request.send_fd(fd, cloexec, force_rand_fd)
}

#[expect(clippy::type_complexity)]
fn do_accept4<Fd: AsFd>(
    fd: Fd,
    request: &UNotifyEventRequest,
    flags: SockFlag,
    aflags: Accept4Flags,
) -> Result<(OwnedFd, Option<(SockaddrStorage, libc::socklen_t)>), Errno> {
    // Allocate storage for the address.
    let mut addr: [u8; size_of::<SockaddrStorage>()] = [0u8; size_of::<SockaddrStorage>()];
    #[expect(clippy::cast_possible_truncation)]
    let mut len = size_of::<SockaddrStorage>() as libc::socklen_t;

    // Cast the storage buffer to a sockaddr pointer.
    let (addr_ptr, len_ptr) = if aflags.contains(Accept4Flags::WANT_ADDR) {
        (addr.as_mut_ptr().cast(), &raw mut len)
    } else {
        (std::ptr::null_mut(), std::ptr::null_mut())
    };

    // SAFETY: Record blocking call so it can get invalidated.
    if aflags.contains(Accept4Flags::INTERRUPT) {
        request
            .cache
            .add_sys_block(request.scmpreq, aflags.contains(Accept4Flags::IGNORERES))?;
    };

    // Make the accept4(2) call.
    //
    // SAFETY: addr_ptr and len_ptr are valid pointers.
    let result = unsafe { safe_accept4(fd, addr_ptr, len_ptr, flags) };

    // Remove invalidation record unless interrupted.
    if aflags.contains(Accept4Flags::INTERRUPT) {
        request
            .cache
            .del_sys_block(request.scmpreq.id, matches!(result, Err(Errno::EINTR)))?;
    }

    // Check for accept4 errors after invalidation.
    let fd = result?;

    let maybe_addr = if !addr_ptr.is_null() {
        Some((
            // SAFETY:
            // Convert the raw address into a SockaddrStorage structure.
            // accept4 returned success so the pointer is valid.
            unsafe { SockaddrStorage::from_raw(addr_ptr, Some(len)) }.ok_or(Errno::EINVAL)?,
            len,
        ))
    } else {
        None
    };

    Ok((fd, maybe_addr))
}
