//! Socket options as used by `setsockopt` and `getsockopt`.
#[cfg(any(linux_android, target_os = "illumos"))]
use super::SetSockOpt;
use crate::sys::time::TimeVal;
#[cfg(any(linux_android, target_os = "illumos"))]
use crate::{errno::Errno, Result};
use cfg_if::cfg_if;
use libc::{self, c_int, c_void, socklen_t};
#[cfg(apple_targets)]
use std::ffi::CString;
use std::ffi::{CStr, OsStr, OsString};
use std::mem::{self, MaybeUninit};
use std::os::fd::OwnedFd;
use std::os::unix::ffi::OsStrExt;
#[cfg(any(linux_android, target_os = "illumos"))]
use std::os::unix::io::{AsFd, AsRawFd};

// Constants
// TCP_CA_NAME_MAX isn't defined in user space include files
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
const TCP_CA_NAME_MAX: usize = 16;

/// Helper for implementing `SetSockOpt` for a given socket option. See
/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html).
///
/// This macro aims to help implementing `SetSockOpt` for different socket options that accept
/// different kinds of data to be used with `setsockopt`.
///
/// Instead of using this macro directly consider using [`sockopt_impl!`](crate::sockopt_impl),
/// especially if the option you are implementing represents a simple type.
///
/// # Arguments
///
/// * `$name:ident`: name of the type you want to implement `SetSockOpt` for.
/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets*
///   (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
///   and more. Please refer to your system manual for more options. Will be passed as the second
///   argument (`level`) to the `setsockopt` call.
/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
///   `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`)
///   to the `setsockopt` call.
/// * Type of the value that you are going to set.
/// * Type that implements the `Set` trait for the type from the previous item 
///   (like `SetBool` for `bool`, `SetUsize` for `usize`, etc.).
#[macro_export]
macro_rules! setsockopt_impl {
    ($name:ident, $level:expr, $flag:path, $ty:ty, $setter:ty) => {
        #[allow(deprecated)] // to allow we have deprecated socket option
        impl $crate::sys::socket::SetSockOpt for $name {
            type Val = $ty;

            fn set<F: std::os::unix::io::AsFd>(
                &self,
                fd: &F,
                val: &$ty,
            ) -> $crate::Result<()> {
                use std::os::fd::AsRawFd;
                use $crate::sys::socket::sockopt::Set;
                let setter: $setter = Set::new(val);
                let level = $level;
                let flag = $flag;
                let res = unsafe {
                    libc::setsockopt(
                        fd.as_fd().as_raw_fd(),
                        level,
                        flag,
                        setter.ffi_ptr(),
                        setter.ffi_len(),
                    )
                };
                $crate::errno::Errno::result(res).map(drop)
            }
        }
    };
}

/// Helper for implementing `GetSockOpt` for a given socket option. See
/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html).
///
/// This macro aims to help implementing `GetSockOpt` for different socket options that accept
/// different kinds of data to be use with `getsockopt`.
///
/// Instead of using this macro directly consider using [`sockopt_impl!`](crate::sockopt_impl),
/// especially if the option you are implementing represents a simple type.
///
/// # Arguments
///
/// * Name of the type you want to implement `GetSockOpt` for.
/// * Socket layer, or a `protocol level`: could be *raw sockets* (`lic::SOL_SOCKET`),  *ip
///   protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),  and more. Please refer
///   to your system manual for more options. Will be passed as the second argument (`level`) to
///   the `getsockopt` call.
/// * A flag to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
///   `libc::SO_ORIGINAL_DST` and others. Will be passed as the third argument (`option_name`) to
///   the `getsockopt` call.
/// * Type of the value that you are going to get.
/// * Type that implements the `Get` trait for the type from the previous item (`GetBool` for
///   `bool`, `GetUsize` for `usize`, etc.).
#[macro_export]
macro_rules! getsockopt_impl {
    ($name:ident, $level:expr, $flag:path, $ty:ty, $getter:ty) => {
        #[allow(deprecated)] // to allow we have deprecated socket option
        impl $crate::sys::socket::GetSockOpt for $name {
            type Val = $ty;

            fn get<F: std::os::unix::io::AsFd>(
                &self,
                fd: &F,
            ) -> $crate::Result<$ty> {
                use std::os::fd::AsRawFd;
                use $crate::sys::socket::sockopt::Get;
                let mut getter: $getter = Get::uninit();
                let level = $level;
                let flag = $flag;
                let res = unsafe {
                    libc::getsockopt(
                        fd.as_fd().as_raw_fd(),
                        level,
                        flag,
                        getter.ffi_ptr(),
                        getter.ffi_len(),
                    )
                };
                $crate::errno::Errno::result(res)?;

                // getter is definitely initialized now
                let gotten = unsafe { getter.assume_init() };
                match <$ty>::try_from(gotten) {
                    // In most `getsockopt_impl!` implementations, `assume_init()`
                    // returns `$ty`, so calling `$ty`::try_from($ty) will always
                    // succeed. which makes the following `Err(_)` branch
                    // unreachable.
                    //
                    // However, there is indeed one exception, `sockopt::SockType`,
                    // `assume_init()` returns an `i32`, but `$ty` is `super::SockType`,
                    // this exception necessitates the use of that `try_from()`,
                    // and we have to allow the unreachable pattern wraning.
                    //
                    // For the reason why we are using `i32` as the underlying
                    // buffer type for this socket option, see issue:
                    // https://github.com/nix-rust/nix/issues/1819
                    #[allow(unreachable_patterns)]
                    Err(_) => Err($crate::errno::Errno::EINVAL),
                    Ok(r) => Ok(r),
                }
            }
        }
    };
}

/// Helper to generate the sockopt accessors. See
/// [`::sys::socket::GetSockOpt`](sys/socket/trait.GetSockOpt.html) and
/// [`::sys::socket::SetSockOpt`](sys/socket/trait.SetSockOpt.html).
///
/// This macro aims to help implementing `GetSockOpt` and `SetSockOpt` for different socket options
/// that accept different kinds of data to be use with `getsockopt` and `setsockopt` respectively.
///
/// Basically this macro wraps up the [`getsockopt_impl!`](macro.getsockopt_impl.html) and
/// [`setsockopt_impl!`](macro.setsockopt_impl.html) macros.
///
/// # Arguments
///
/// * `GetOnly`, `SetOnly` or `Both`: whether you want to implement only getter, only setter or
///   both of them.
/// * `$name:ident`: name of type `GetSockOpt`/`SetSockOpt` will be implemented for.
/// * `$level:expr` : socket layer, or a `protocol level`: could be *raw sockets*
///   (`libc::SOL_SOCKET`), *ip protocol* (libc::IPPROTO_IP), *tcp protocol* (`libc::IPPROTO_TCP`),
///   and more. Please refer to your system manual for more options. Will be passed as the second
///   argument (`level`) to the `getsockopt`/`setsockopt` call.
/// * `$flag:path`: a flag name to set. Some examples: `libc::SO_REUSEADDR`, `libc::TCP_NODELAY`,
///   `libc::IP_ADD_MEMBERSHIP` and others. Will be passed as the third argument (`option_name`)
///   to the `setsockopt`/`getsockopt` call.
/// * `$ty:ty`: type of the value that will be get/set.
/// * `$getter:ty`: `Get` implementation; optional; only for `GetOnly` and `Both`.
/// * `$setter:ty`: `Set` implementation; optional; only for `SetOnly` and `Both`.
// Some targets don't use all rules.
#[allow(unused_macro_rules)]
#[macro_export]
macro_rules! sockopt_impl {
    ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, bool) => {
        sockopt_impl!($(#[$attr])*
                      $name, GetOnly, $level, $flag, bool, $crate::sys::socket::sockopt::GetBool);
    };

    ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, u8) => {
        sockopt_impl!($(#[$attr])* $name, GetOnly, $level, $flag, u8, $crate::sys::socket::sockopt::GetU8);
    };

    ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, usize) =>
    {
        sockopt_impl!($(#[$attr])*
                      $name, GetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize);
    };

    ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, OwnedFd) =>
    {
        sockopt_impl!($(#[$attr])*
                      $name, GetOnly, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::GetOwnedFd);
    };

    ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, bool) => {
        sockopt_impl!($(#[$attr])*
                      $name, SetOnly, $level, $flag, bool, $crate::sys::socket::sockopt::SetBool);
    };

    ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, u8) => {
        sockopt_impl!($(#[$attr])* $name, SetOnly, $level, $flag, u8, $crate::sys::socket::sockopt::SetU8);
    };

    ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, usize) =>
    {
        sockopt_impl!($(#[$attr])*
                      $name, SetOnly, $level, $flag, usize, $crate::sys::socket::sockopt::SetUsize);
    };

    ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, OwnedFd) =>
    {
        sockopt_impl!($(#[$attr])*
                      $name, SetOnly, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::SetOwnedFd);
    };

    ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, bool) => {
        sockopt_impl!($(#[$attr])*
                      $name, Both, $level, $flag, bool, $crate::sys::socket::sockopt::GetBool, $crate::sys::socket::sockopt::SetBool);
    };

    ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, u8) => {
        sockopt_impl!($(#[$attr])*
                      $name, Both, $level, $flag, u8, $crate::sys::socket::sockopt::GetU8, $crate::sys::socket::sockopt::SetU8);
    };

    ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, usize) => {
        sockopt_impl!($(#[$attr])*
                      $name, Both, $level, $flag, usize, $crate::sys::socket::sockopt::GetUsize, $crate::sys::socket::sockopt::SetUsize);
    };

    ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, OwnedFd) => {
        sockopt_impl!($(#[$attr])*
                      $name, Both, $level, $flag, OwnedFd, $crate::sys::socket::sockopt::GetOwnedFd, $crate::sys::socket::sockopt::SetOwnedFd);
    };

    ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path,
     OsString<$array:ty>) =>
    {
        sockopt_impl!($(#[$attr])*
                      $name, Both, $level, $flag, std::ffi::OsString, $crate::sys::socket::sockopt::GetOsString<$array>,
                      $crate::sys::socket::sockopt::SetOsString);
    };

    /*
     * Matchers with generic getter types must be placed at the end, so
     * they'll only match _after_ specialized matchers fail
     */
    ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty) =>
    {
        sockopt_impl!($(#[$attr])*
                      $name, GetOnly, $level, $flag, $ty, $crate::sys::socket::sockopt::GetStruct<$ty>);
    };

    ($(#[$attr:meta])* $name:ident, GetOnly, $level:expr, $flag:path, $ty:ty,
     $getter:ty) =>
    {
        $(#[$attr])*
        #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
        pub struct $name;

        getsockopt_impl!($name, $level, $flag, $ty, $getter);
    };

    ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty) =>
    {
        sockopt_impl!($(#[$attr])*
                      $name, SetOnly, $level, $flag, $ty, $crate::sys::socket::sockopt::SetStruct<$ty>);
    };

    ($(#[$attr:meta])* $name:ident, SetOnly, $level:expr, $flag:path, $ty:ty,
     $setter:ty) =>
    {
        $(#[$attr])*
        #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
        pub struct $name;

        setsockopt_impl!($name, $level, $flag, $ty, $setter);
    };

    ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty,
     $getter:ty, $setter:ty) =>
    {
        $(#[$attr])*
        #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
        pub struct $name;

        setsockopt_impl!($name, $level, $flag, $ty, $setter);
        getsockopt_impl!($name, $level, $flag, $ty, $getter);
    };

    ($(#[$attr:meta])* $name:ident, Both, $level:expr, $flag:path, $ty:ty) => {
        sockopt_impl!($(#[$attr])*
                      $name, Both, $level, $flag, $ty, $crate::sys::socket::sockopt::GetStruct<$ty>,
                      $crate::sys::socket::sockopt::SetStruct<$ty>);
    };
}

/*
 *
 * ===== Define sockopts =====
 *
 */

sockopt_impl!(
    /// Enables local address reuse
    ReuseAddr,
    Both,
    libc::SOL_SOCKET,
    libc::SO_REUSEADDR,
    bool
);
#[cfg(not(any(solarish, target_os = "cygwin")))]
sockopt_impl!(
    /// Permits multiple AF_INET or AF_INET6 sockets to be bound to an
    /// identical socket address.
    ReusePort,
    Both,
    libc::SOL_SOCKET,
    libc::SO_REUSEPORT,
    bool
);
#[cfg(target_os = "freebsd")]
sockopt_impl!(
    /// Enables incoming connections to be distributed among N sockets (up to 256)
    /// via a Load-Balancing hash based algorithm.
    ReusePortLb,
    Both,
    libc::SOL_SOCKET,
    libc::SO_REUSEPORT_LB,
    bool
);
#[cfg(target_os = "freebsd")]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Select or query the set of functions that TCP will use for this connection.  This allows a
    /// user to select an alternate TCP stack.
    TcpFunctionBlk,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_FUNCTION_BLK,
    libc::tcp_function_set
);
#[cfg(target_os = "freebsd")]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Query the alias name of the set of function of the socket's TCP stack.
    /// Uses the same field for the main name when getting from TCP_FUNCTION_BLK.
    /// Empty if no alias.
    TcpFunctionAlias,
    GetOnly,
    libc::IPPROTO_TCP,
    libc::TCP_FUNCTION_ALIAS,
    libc::tcp_function_set
);
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Used to disable Nagle's algorithm.
    /// 
    /// Nagle's algorithm:
    /// 
    /// Under most circumstances, TCP sends data when it is presented; when
    /// outstanding data has not yet been acknowledged, it gathers small amounts
    /// of output to be sent in a single packet once an acknowledgement is
    /// received.  For a small number of clients, such as window systems that
    /// send a stream of mouse events which receive no replies, this
    /// packetization may cause significant delays.  The boolean option, when
    /// enabled, defeats this algorithm.
    TcpNoDelay,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_NODELAY,
    bool
);
sockopt_impl!(
    /// When enabled, a close(2) or shutdown(2) will not return until all
    /// queued messages for the socket have been successfully sent or the
    /// linger timeout has been reached.
    Linger,
    Both,
    libc::SOL_SOCKET,
    libc::SO_LINGER,
    libc::linger
);
#[cfg(apple_targets)]
sockopt_impl!(
    /// Same as `SO_LINGER`, but the duration is in seconds rather than kernel ticks.
    LingerSec,
    Both,
    libc::SOL_SOCKET,
    libc::SO_LINGER_SEC,
    libc::linger
);
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Join a multicast group
    IpAddMembership,
    SetOnly,
    libc::IPPROTO_IP,
    libc::IP_ADD_MEMBERSHIP,
    super::IpMembershipRequest
);
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Leave a multicast group.
    IpDropMembership,
    SetOnly,
    libc::IPPROTO_IP,
    libc::IP_DROP_MEMBERSHIP,
    super::IpMembershipRequest
);
cfg_if! {
    if #[cfg(linux_android)] {
        #[cfg(feature = "net")]
        sockopt_impl!(
            #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
            /// Join an IPv6 multicast group.
            Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_ADD_MEMBERSHIP, super::Ipv6MembershipRequest);
        #[cfg(feature = "net")]
        sockopt_impl!(
            #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
            /// Leave an IPv6 multicast group.
            Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6, libc::IPV6_DROP_MEMBERSHIP, super::Ipv6MembershipRequest);
    } else if #[cfg(any(bsd, solarish))] {
        #[cfg(feature = "net")]
        sockopt_impl!(
            #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
            /// Join an IPv6 multicast group.
            Ipv6AddMembership, SetOnly, libc::IPPROTO_IPV6,
            libc::IPV6_JOIN_GROUP, super::Ipv6MembershipRequest);
        #[cfg(feature = "net")]
        sockopt_impl!(
            #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
            /// Leave an IPv6 multicast group.
            Ipv6DropMembership, SetOnly, libc::IPPROTO_IPV6,
            libc::IPV6_LEAVE_GROUP, super::Ipv6MembershipRequest);
    }
}
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set or read the time-to-live value of outgoing multicast packets for
    /// this socket.
    IpMulticastTtl,
    Both,
    libc::IPPROTO_IP,
    libc::IP_MULTICAST_TTL,
    u8
);
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set or read the hop limit value of outgoing IPv6 multicast packets for
    /// this socket.
    Ipv6MulticastHops,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_MULTICAST_HOPS,
    libc::c_int
);
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set or read a boolean integer argument that determines whether sent
    /// multicast packets should be looped back to the local sockets.
    IpMulticastLoop,
    Both,
    libc::IPPROTO_IP,
    libc::IP_MULTICAST_LOOP,
    bool
);
#[cfg(target_os = "linux")]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set the protocol-defined priority for all packets to be
    /// sent on this socket
    Priority,
    Both,
    libc::SOL_SOCKET,
    libc::SO_PRIORITY,
    libc::c_int
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[deprecated(since = "0.30.0", note = "Use Ipv4Tos instead")]
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set or receive the Type-Of-Service (TOS) field that is
    /// sent with every IP packet originating from this socket
    IpTos,
    Both,
    libc::IPPROTO_IP,
    libc::IP_TOS,
    libc::c_int
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set or receive the Type-Of-Service (TOS) field that is
    /// sent with every IP packet originating from this socket
    Ipv4Tos,
    Both,
    libc::IPPROTO_IP,
    libc::IP_TOS,
    libc::c_int
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// If enabled, the IP_TOS ancillary message is passed with incoming packets.
    IpRecvTos,
    Both,
    libc::IPPROTO_IP,
    libc::IP_RECVTOS,
    bool
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set the traffic class associated with outgoing packets.
    Ipv6TClass,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_TCLASS,
    libc::c_int
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// If enabled, the IPV6_TCLASS ancillary message is passed with incoming packets.
    Ipv6RecvTClass,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_RECVTCLASS,
    bool
);
#[cfg(any(linux_android, target_os = "fuchsia"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// If enabled, this boolean option allows binding to an IP address that
    /// is nonlocal or does not (yet) exist.
    IpFreebind,
    Both,
    libc::IPPROTO_IP,
    libc::IP_FREEBIND,
    bool
);
#[cfg(linux_android)]
#[cfg(feature = "net")]
#[cfg(not(target_env = "uclibc"))]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// If enabled, the kernel will not reserve an ephemeral port when binding
    /// socket with a port number of 0. The port will later be automatically
    /// chosen at connect time, in a way that allows sharing a source port as
    /// long as the 4-tuple is unique.
    IpBindAddressNoPort,
    Both,
    libc::IPPROTO_IP,
    libc::IP_BIND_ADDRESS_NO_PORT,
    bool
);
sockopt_impl!(
    /// Specify the receiving timeout until reporting an error.
    ReceiveTimeout,
    Both,
    libc::SOL_SOCKET,
    libc::SO_RCVTIMEO,
    TimeVal
);
sockopt_impl!(
    /// Specify the sending timeout until reporting an error.
    SendTimeout,
    Both,
    libc::SOL_SOCKET,
    libc::SO_SNDTIMEO,
    TimeVal
);
sockopt_impl!(
    /// Set or get the broadcast flag.
    Broadcast,
    Both,
    libc::SOL_SOCKET,
    libc::SO_BROADCAST,
    bool
);
sockopt_impl!(
    /// If this option is enabled, out-of-band data is directly placed into
    /// the receive data stream.
    OobInline,
    Both,
    libc::SOL_SOCKET,
    libc::SO_OOBINLINE,
    bool
);
sockopt_impl!(
    /// Get and clear the pending socket error.
    SocketError,
    GetOnly,
    libc::SOL_SOCKET,
    libc::SO_ERROR,
    i32
);
sockopt_impl!(
    /// Set or get the don't route flag.
    DontRoute,
    Both,
    libc::SOL_SOCKET,
    libc::SO_DONTROUTE,
    bool
);
sockopt_impl!(
    /// Enable sending of keep-alive messages on connection-oriented sockets.
    KeepAlive,
    Both,
    libc::SOL_SOCKET,
    libc::SO_KEEPALIVE,
    bool
);
#[cfg(freebsdlike)]
sockopt_impl!(
    /// Get the credentials of the peer process of a connected unix domain
    /// socket.
    LocalPeerCred,
    GetOnly,
    0,
    libc::LOCAL_PEERCRED,
    super::XuCred
);
#[cfg(apple_targets)]
sockopt_impl!(
    /// Get the credentials of the peer process of a connected unix domain
    /// socket.
    LocalPeerCred,
    GetOnly,
    libc::SOL_LOCAL,
    libc::LOCAL_PEERCRED,
    super::XuCred
);
#[cfg(apple_targets)]
sockopt_impl!(
    /// Get the PID of the peer process of a connected unix domain socket.
    LocalPeerPid,
    GetOnly,
    libc::SOL_LOCAL,
    libc::LOCAL_PEERPID,
    libc::c_int
);
#[cfg(apple_targets)]
sockopt_impl!(
    /// Get the audit token of the peer process of a connected unix domain
    /// socket.
    LocalPeerToken,
    GetOnly,
    libc::SOL_LOCAL,
    libc::LOCAL_PEERTOKEN,
    super::audit_token_t
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Return the credentials of the foreign process connected to this socket.
    PeerCredentials,
    GetOnly,
    libc::SOL_SOCKET,
    libc::SO_PEERCRED,
    super::UnixCredentials
);
#[cfg(target_os = "linux")]
sockopt_impl!(
    /// Return the pidfd of the foreign process connected to this socket.
    PeerPidfd,
    GetOnly,
    libc::SOL_SOCKET,
    libc::SO_PEERPIDFD,
    OwnedFd
);
#[cfg(target_os = "freebsd")]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Get backlog limit of the socket
    ListenQLimit,
    GetOnly,
    libc::SOL_SOCKET,
    libc::SO_LISTENQLIMIT,
    u32
);
#[cfg(apple_targets)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Specify the amount of time, in seconds, that the connection must be idle
    /// before keepalive probes (if enabled) are sent.
    TcpKeepAlive,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_KEEPALIVE,
    u32
);
#[cfg(any(freebsdlike, linux_android))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The time (in seconds) the connection needs to remain idle before TCP
    /// starts sending keepalive probes
    TcpKeepIdle,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_KEEPIDLE,
    u32
);
cfg_if! {
    if #[cfg(any(linux_android, apple_targets))] {
        sockopt_impl!(
            /// The maximum segment size for outgoing TCP packets.
            TcpMaxSeg, Both, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32);
    } else if #[cfg(not(target_os = "redox"))] {
        sockopt_impl!(
            /// The maximum segment size for outgoing TCP packets.
            TcpMaxSeg, GetOnly, libc::IPPROTO_TCP, libc::TCP_MAXSEG, u32);
    }
}
#[cfg(not(any(
    target_os = "openbsd",
    target_os = "haiku",
    target_os = "redox"
)))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The maximum number of keepalive probes TCP should send before
    /// dropping the connection.
    TcpKeepCount,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_KEEPCNT,
    u32
);
#[cfg(any(linux_android, target_os = "fuchsia"))]
sockopt_impl!(
    #[allow(missing_docs)]
    // Not documented by Linux!
    TcpRepair,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_REPAIR,
    u32
);
#[cfg(not(any(
    target_os = "openbsd",
    target_os = "haiku",
    target_os = "redox"
)))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The time (in seconds) between individual keepalive probes.
    TcpKeepInterval,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_KEEPINTVL,
    u32
);
#[cfg(any(target_os = "fuchsia", target_os = "linux"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Specifies the maximum amount of time in milliseconds that transmitted
    /// data may remain unacknowledged before TCP will forcibly close the
    /// corresponding connection
    TcpUserTimeout,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_USER_TIMEOUT,
    u32
);
#[cfg(linux_android)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Enables TCP Fast Open (RFC 7413) on a connecting socket. If a fast open
    /// cookie is not available (first attempt to connect), `connect` syscall
    /// will behave as usual, except for internally trying to solicit a cookie
    /// from remote peer. When cookie is available, the next `connect` syscall
    /// will immediately succeed without actually establishing TCP connection.
    /// The connection establishment will be defered till the next `write` or
    /// `sendmsg` syscalls on the socket, allowing TCP prtocol to establish
    /// connection and send data in the same packets. Note: calling `read` right
    /// after `connect` without `write` on the socket will cause the blocking
    /// socket to be blocked forever.
    TcpFastOpenConnect,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_FASTOPEN_CONNECT,
    bool
);
sockopt_impl!(
    /// Sets or gets the maximum socket receive buffer in bytes.
    RcvBuf,
    Both,
    libc::SOL_SOCKET,
    libc::SO_RCVBUF,
    usize
);
sockopt_impl!(
    /// Sets or gets the maximum socket send buffer in bytes.
    SndBuf,
    Both,
    libc::SOL_SOCKET,
    libc::SO_SNDBUF,
    usize
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Using this socket option, a privileged (`CAP_NET_ADMIN`) process can
    /// perform the same task as `SO_RCVBUF`, but the `rmem_max limit` can be
    /// overridden.
    RcvBufForce,
    SetOnly,
    libc::SOL_SOCKET,
    libc::SO_RCVBUFFORCE,
    usize
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Using this socket option, a privileged (`CAP_NET_ADMIN`)  process can
    /// perform the same task as `SO_SNDBUF`, but the `wmem_max` limit can be
    /// overridden.
    SndBufForce,
    SetOnly,
    libc::SOL_SOCKET,
    libc::SO_SNDBUFFORCE,
    usize
);
sockopt_impl!(
    /// Gets the socket type as an integer.
    SockType,
    GetOnly,
    libc::SOL_SOCKET,
    libc::SO_TYPE,
    super::SockType,
    GetStruct<i32>
);
sockopt_impl!(
    /// Returns a value indicating whether or not this socket has been marked to
    /// accept connections with `listen(2)`.
    AcceptConn,
    GetOnly,
    libc::SOL_SOCKET,
    libc::SO_ACCEPTCONN,
    bool
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Bind this socket to a particular device like “eth0”.
    BindToDevice,
    Both,
    libc::SOL_SOCKET,
    libc::SO_BINDTODEVICE,
    OsString<[u8; libc::IFNAMSIZ]>
);
#[cfg(linux_android)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    #[allow(missing_docs)]
    // Not documented by Linux!
    OriginalDst,
    GetOnly,
    libc::SOL_IP,
    libc::SO_ORIGINAL_DST,
    libc::sockaddr_in
);
#[cfg(linux_android)]
sockopt_impl!(
    #[allow(missing_docs)]
    // Not documented by Linux!
    Ip6tOriginalDst,
    GetOnly,
    libc::SOL_IPV6,
    libc::IP6T_SO_ORIGINAL_DST,
    libc::sockaddr_in6
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Specifies exact type of timestamping information collected by the kernel
    /// [Further reading](https://www.kernel.org/doc/html/latest/networking/timestamping.html)
    Timestamping,
    Both,
    libc::SOL_SOCKET,
    libc::SO_TIMESTAMPING,
    super::TimestampingFlag
);
#[cfg(not(any(
    target_os = "aix",
    target_os = "haiku",
    target_os = "hurd",
    target_os = "redox",
    target_os = "cygwin"
)))]
sockopt_impl!(
    /// Enable or disable the receiving of the `SO_TIMESTAMP` control message.
    ReceiveTimestamp,
    Both,
    libc::SOL_SOCKET,
    libc::SO_TIMESTAMP,
    bool
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Enable or disable the receiving of the `SO_TIMESTAMPNS` control message.
    ReceiveTimestampns,
    Both,
    libc::SOL_SOCKET,
    libc::SO_TIMESTAMPNS,
    bool
);
#[cfg(target_os = "freebsd")]
sockopt_impl!(
    /// Sets a specific timestamp format instead of the classic `SCM_TIMESTAMP`,
    /// to follow up after `SO_TIMESTAMP` is set.
    TsClock,
    Both,
    libc::SOL_SOCKET,
    libc::SO_TS_CLOCK,
    super::SocketTimestamp
);
#[cfg(linux_android)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Setting this boolean option enables transparent proxying on this socket.
    IpTransparent,
    Both,
    libc::SOL_IP,
    libc::IP_TRANSPARENT,
    bool
);
#[cfg(target_os = "openbsd")]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Allows the socket to be bound to addresses which are not local to the
    /// machine, so it can be used to make a transparent proxy.
    BindAny,
    Both,
    libc::SOL_SOCKET,
    libc::SO_BINDANY,
    bool
);
#[cfg(target_os = "freebsd")]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Can `bind(2)` to any address, even one not bound to any available
    /// network interface in the system.
    BindAny,
    Both,
    libc::IPPROTO_IP,
    libc::IP_BINDANY,
    bool
);
#[cfg(target_os = "freebsd")]
sockopt_impl!(
    /// Set the route table (FIB) for this socket up to the `net.fibs` OID limit
    /// (more specific than the setfib command line/call which are process based).
    Fib,
    SetOnly,
    libc::SOL_SOCKET,
    libc::SO_SETFIB,
    i32
);
#[cfg(target_os = "freebsd")]
sockopt_impl!(
    /// Set `so_user_cookie` for this socket allowing network traffic based
    /// upon it, similar to Linux's netfilter MARK.
    UserCookie,
    SetOnly,
    libc::SOL_SOCKET,
    libc::SO_USER_COOKIE,
    u32
);
#[cfg(target_os = "openbsd")]
sockopt_impl!(
    /// Set the route table for this socket, needs a privileged user if
    /// the process/socket had been set to the non default route.
    Rtable,
    SetOnly,
    libc::SOL_SOCKET,
    libc::SO_RTABLE,
    i32
);
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))]
sockopt_impl!(
    /// Get/set a filter on this socket before accepting connections similarly
    /// to Linux's TCP_DEFER_ACCEPT but after the listen's call.
    AcceptFilter,
    Both,
    libc::SOL_SOCKET,
    libc::SO_ACCEPTFILTER,
    libc::accept_filter_arg
);
#[cfg(target_os = "linux")]
sockopt_impl!(
    /// Set the mark for each packet sent through this socket (similar to the
    /// netfilter MARK target but socket-based).
    Mark,
    Both,
    libc::SOL_SOCKET,
    libc::SO_MARK,
    u32
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Enable or disable the receiving of the `SCM_CREDENTIALS` control
    /// message.
    PassCred,
    Both,
    libc::SOL_SOCKET,
    libc::SO_PASSCRED,
    bool
);
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// This option allows the caller to set the TCP congestion control
    /// algorithm to be used,  on a per-socket basis.
    TcpCongestion,
    Both,
    libc::IPPROTO_TCP,
    libc::TCP_CONGESTION,
    OsString<[u8; TCP_CA_NAME_MAX]>
);
#[cfg(any(linux_android, apple_targets, target_os = "netbsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Pass an `IP_PKTINFO` ancillary message that contains a pktinfo
    /// structure that supplies some information about the incoming packet.
    Ipv4PacketInfo,
    Both,
    libc::IPPROTO_IP,
    libc::IP_PKTINFO,
    bool
);
#[cfg(any(linux_android, bsd))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Set delivery of the `IPV6_PKTINFO` control message on incoming
    /// datagrams.
    Ipv6RecvPacketInfo,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_RECVPKTINFO,
    bool
);

#[cfg(any(linux_android, bsd))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// Pass an `IPV6_PKTINFO` ancillary message that contains a in6_pktinfo
    /// structure that supplies some information about the incoming packet.
    Ipv6PacketInfo,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_PKTINFO,
    bool
);
#[cfg(bsd)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The `recvmsg(2)` call returns a `struct sockaddr_dl` corresponding to
    /// the interface on which the packet was received.
    Ipv4RecvIf,
    Both,
    libc::IPPROTO_IP,
    libc::IP_RECVIF,
    bool
);
#[cfg(bsd)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The `recvmsg(2)` call will return the destination IP address for a UDP
    /// datagram.
    Ipv4RecvDstAddr,
    Both,
    libc::IPPROTO_IP,
    libc::IP_RECVDSTADDR,
    bool
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The `recvmsg(2)` call will return the destination IP address for a UDP
    /// datagram.
    Ipv4OrigDstAddr,
    Both,
    libc::IPPROTO_IP,
    libc::IP_ORIGDSTADDR,
    bool
);
#[cfg(linux_android)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    #[allow(missing_docs)]
    // Not documented by Linux!
    UdpGsoSegment,
    Both,
    libc::SOL_UDP,
    libc::UDP_SEGMENT,
    libc::c_int
);
#[cfg(linux_android)]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    #[allow(missing_docs)]
    // Not documented by Linux!
    UdpGroSegment,
    Both,
    libc::IPPROTO_UDP,
    libc::UDP_GRO,
    bool
);
#[cfg(target_os = "linux")]
sockopt_impl!(
    /// Configures the behavior of time-based transmission of packets, for use
    /// with the `TxTime` control message.
    TxTime,
    Both,
    libc::SOL_SOCKET,
    libc::SO_TXTIME,
    libc::sock_txtime
);
#[cfg(any(linux_android, target_os = "fuchsia"))]
sockopt_impl!(
    /// Indicates that an unsigned 32-bit value ancillary message (cmsg) should
    /// be attached to received skbs indicating the number of packets dropped by
    /// the socket since its creation.
    RxqOvfl,
    Both,
    libc::SOL_SOCKET,
    libc::SO_RXQ_OVFL,
    libc::c_int
);
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The socket is restricted to sending and receiving IPv6 packets only.
    Ipv6V6Only,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_V6ONLY,
    bool
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Enable extended reliable error message passing.
    Ipv4RecvErr,
    Both,
    libc::IPPROTO_IP,
    libc::IP_RECVERR,
    bool
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Control receiving of asynchronous error options.
    Ipv6RecvErr,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_RECVERR,
    bool
);
#[cfg(linux_android)]
sockopt_impl!(
    /// Fetch the current system-estimated Path MTU.
    IpMtu,
    GetOnly,
    libc::IPPROTO_IP,
    libc::IP_MTU,
    libc::c_int
);
#[cfg(any(linux_android, target_os = "freebsd"))]
sockopt_impl!(
    /// Set or retrieve the current time-to-live field that is used in every
    /// packet sent from this socket.
    Ipv4Ttl,
    Both,
    libc::IPPROTO_IP,
    libc::IP_TTL,
    libc::c_int
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    /// Enables a receiving socket to retrieve the Time-to-Live (TTL) field 
    /// from incoming IPv4 packets.
    Ipv4RecvTtl,
    Both,
    libc::IPPROTO_IP,
    libc::IP_RECVTTL,
    bool
);
#[cfg(any(apple_targets, linux_android, target_os = "freebsd"))]
sockopt_impl!(
    /// Set the unicast hop limit for the socket.
    Ipv6Ttl,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_UNICAST_HOPS,
    libc::c_int
);
#[cfg(any(linux_android, target_os = "freebsd"))]
#[cfg(feature = "net")]
sockopt_impl!(
    /// Enables a receiving socket to retrieve the Hop Limit field 
    /// (similar to TTL in IPv4) from incoming IPv6 packets.
    Ipv6RecvHopLimit,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_RECVHOPLIMIT,
    bool
);
#[cfg(any(
    all(linux_android, not(target_env = "uclibc")),
    target_os = "freebsd"
))]
#[cfg(feature = "net")]
sockopt_impl!(
    #[cfg_attr(docsrs, doc(cfg(feature = "net")))]
    /// The `recvmsg(2)` call will return the destination IP address for a UDP
    /// datagram.
    Ipv6OrigDstAddr,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_ORIGDSTADDR,
    bool
);
#[cfg(apple_targets)]
sockopt_impl!(
    /// Set "don't fragment packet" flag on the IP packet.
    IpDontFrag,
    Both,
    libc::IPPROTO_IP,
    libc::IP_DONTFRAG,
    bool
);
#[cfg(any(
    all(linux_android, not(target_env = "uclibc")),
    apple_targets
))]
sockopt_impl!(
    /// Set "don't fragment packet" flag on the IPv6 packet.
    Ipv6DontFrag,
    Both,
    libc::IPPROTO_IPV6,
    libc::IPV6_DONTFRAG,
    bool
);
#[cfg(apple_targets)]
#[cfg(feature = "net")]
sockopt_impl!(
    /// Get the utun interface name.
    UtunIfname,
    GetOnly,
    libc::SYSPROTO_CONTROL,
    libc::UTUN_OPT_IFNAME,
    CString,
    GetCString<[u8; libc::IFNAMSIZ]>
);

#[cfg(solarish)]
sockopt_impl!(
    /// Enable/disable exclusive binding.
    /// Prevent multiple sockets to bind to the same
    /// address:port, neutralizing `SO_REUSEADDR` effect.
    ExclBind,
    Both,
    libc::SOL_SOCKET,
    libc::SO_EXCLBIND,
    bool
);
#[cfg(target_os = "linux")]
sockopt_impl!(
    /// To be used with `ReusePort`,
    /// we can then attach a BPF (classic)
    /// to set how the packets are assigned
    /// to the socket (e.g. cpu distribution).
    AttachReusePortCbpf,
    SetOnly,
    libc::SOL_SOCKET,
    libc::SO_ATTACH_REUSEPORT_CBPF,
    libc::sock_fprog
);

#[allow(missing_docs)]
// Not documented by Linux!
#[cfg(linux_android)]
#[derive(Copy, Clone, Debug)]
pub struct AlgSetAeadAuthSize;

// ALG_SET_AEAD_AUTH_SIZE read the length from passed `option_len`
// See https://elixir.bootlin.com/linux/v4.4/source/crypto/af_alg.c#L222
#[cfg(linux_android)]
impl SetSockOpt for AlgSetAeadAuthSize {
    type Val = usize;

    fn set<F: AsFd>(&self, fd: &F, val: &usize) -> Result<()> {
        unsafe {
            let res = libc::setsockopt(
                fd.as_fd().as_raw_fd(),
                libc::SOL_ALG,
                libc::ALG_SET_AEAD_AUTHSIZE,
                ::std::ptr::null(),
                *val as libc::socklen_t,
            );
            Errno::result(res).map(drop)
        }
    }
}

#[allow(missing_docs)]
// Not documented by Linux!
#[cfg(linux_android)]
#[derive(Clone, Debug)]
pub struct AlgSetKey<T>(::std::marker::PhantomData<T>);

#[cfg(linux_android)]
impl<T> Default for AlgSetKey<T> {
    fn default() -> Self {
        AlgSetKey(Default::default())
    }
}

#[cfg(linux_android)]
impl<T> SetSockOpt for AlgSetKey<T>
where
    T: AsRef<[u8]> + Clone,
{
    type Val = T;

    fn set<F: AsFd>(&self, fd: &F, val: &T) -> Result<()> {
        unsafe {
            let res = libc::setsockopt(
                fd.as_fd().as_raw_fd(),
                libc::SOL_ALG,
                libc::ALG_SET_KEY,
                val.as_ref().as_ptr().cast(),
                val.as_ref().len() as libc::socklen_t,
            );
            Errno::result(res).map(drop)
        }
    }
}

/// Set the Upper Layer Protocol (ULP) on the TCP socket.
///
/// For example, to enable the TLS ULP on a socket, the C function call would be:
///
/// ```c
/// setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));
/// ```
///
/// ... and the `nix` equivalent is:
///
/// ```ignore,rust
/// setsockopt(sock, TcpUlp::default(), b"tls");
/// ```
///
/// Note that the ULP name does not need a trailing NUL terminator (`\0`).
#[cfg(linux_android)]
#[derive(Clone, Debug)]
pub struct TcpUlp<T>(::std::marker::PhantomData<T>);

#[cfg(linux_android)]
impl<T> Default for TcpUlp<T> {
    fn default() -> Self {
        TcpUlp(Default::default())
    }
}

#[cfg(linux_android)]
impl<T> SetSockOpt for TcpUlp<T>
where
    T: AsRef<[u8]> + Clone,
{
    type Val = T;

    fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
        unsafe {
            let res = libc::setsockopt(
                fd.as_fd().as_raw_fd(),
                libc::SOL_TCP,
                libc::TCP_ULP,
                val.as_ref().as_ptr().cast(),
                val.as_ref().len() as libc::socklen_t,
            );
            Errno::result(res).map(drop)
        }
    }
}

/// Value used with the [`TcpTlsTx`] and [`TcpTlsRx`] socket options.
#[cfg(target_os = "linux")]
#[derive(Copy, Clone, Debug)]
pub enum TlsCryptoInfo {
    /// AES-128-GCM
    Aes128Gcm(libc::tls12_crypto_info_aes_gcm_128),

    /// AES-256-GCM
    Aes256Gcm(libc::tls12_crypto_info_aes_gcm_256),

    /// CHACHA20-POLY1305
    Chacha20Poly1305(libc::tls12_crypto_info_chacha20_poly1305),
}

/// Set the Kernel TLS write parameters on the TCP socket.
///
/// For example, the C function call would be:
///
/// ```c
/// setsockopt(sock, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info));
/// ```
///
/// ... and the `nix` equivalent is:
///
/// ```ignore,rust
/// setsockopt(sock, TcpTlsTx, &crypto_info);
/// ```
#[cfg(target_os = "linux")]
#[derive(Copy, Clone, Debug)]
pub struct TcpTlsTx;

#[cfg(target_os = "linux")]
impl SetSockOpt for TcpTlsTx {
    type Val = TlsCryptoInfo;

    fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
        let (ffi_ptr, ffi_len) = match val {
            TlsCryptoInfo::Aes128Gcm(crypto_info) => {
                (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
            }
            TlsCryptoInfo::Aes256Gcm(crypto_info) => {
                (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
            }
            TlsCryptoInfo::Chacha20Poly1305(crypto_info) => {
                (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
            }
        };
        unsafe {
            let res = libc::setsockopt(
                fd.as_fd().as_raw_fd(),
                libc::SOL_TLS,
                libc::TLS_TX,
                ffi_ptr,
                ffi_len as libc::socklen_t,
            );
            Errno::result(res).map(drop)
        }
    }
}

/// Set the Kernel TLS read parameters on the TCP socket.
///
/// For example, the C function call would be:
///
/// ```c
/// setsockopt(sock, SOL_TLS, TLS_RX, &crypto_info, sizeof(crypto_info));
/// ```
///
/// ... and the `nix` equivalent is:
///
/// ```ignore,rust
/// setsockopt(sock, TcpTlsRx, &crypto_info);
/// ```
#[cfg(target_os = "linux")]
#[derive(Copy, Clone, Debug)]
pub struct TcpTlsRx;

#[cfg(target_os = "linux")]
impl SetSockOpt for TcpTlsRx {
    type Val = TlsCryptoInfo;

    fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
        let (ffi_ptr, ffi_len) = match val {
            TlsCryptoInfo::Aes128Gcm(crypto_info) => {
                (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
            }
            TlsCryptoInfo::Aes256Gcm(crypto_info) => {
                (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
            }
            TlsCryptoInfo::Chacha20Poly1305(crypto_info) => {
                (<*const _>::cast(crypto_info), mem::size_of_val(crypto_info))
            }
        };
        unsafe {
            let res = libc::setsockopt(
                fd.as_fd().as_raw_fd(),
                libc::SOL_TLS,
                libc::TLS_RX,
                ffi_ptr,
                ffi_len as libc::socklen_t,
            );
            Errno::result(res).map(drop)
        }
    }
}

#[cfg(target_os = "illumos")]
#[derive(Copy, Clone, Debug)]
/// Attach a named filter to this socket to be able to
/// defer when anough byte had been buffered by the kernel
pub struct FilterAttach;

#[cfg(target_os = "illumos")]
impl SetSockOpt for FilterAttach {
    type Val = OsStr;

    fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
        if val.len() > libc::FILNAME_MAX as usize {
            return Err(Errno::EINVAL);
        }
        unsafe {
            let res = libc::setsockopt(
                fd.as_fd().as_raw_fd(),
                libc::SOL_FILTER,
                libc::FIL_ATTACH,
                val.as_bytes().as_ptr().cast(),
                val.len() as libc::socklen_t,
            );
            Errno::result(res).map(drop)
        }
    }
}

#[cfg(target_os = "illumos")]
#[derive(Copy, Clone, Debug)]
/// Detach a socket filter previously attached with FIL_ATTACH
pub struct FilterDetach;

#[cfg(target_os = "illumos")]
impl SetSockOpt for FilterDetach {
    type Val = OsStr;

    fn set<F: AsFd>(&self, fd: &F, val: &Self::Val) -> Result<()> {
        if val.len() > libc::FILNAME_MAX as usize {
            return Err(Errno::EINVAL);
        }
        unsafe {
            let res = libc::setsockopt(
                fd.as_fd().as_raw_fd(),
                libc::SOL_FILTER,
                libc::FIL_DETACH,
                val.as_bytes().as_ptr().cast(),
                val.len() as libc::socklen_t,
            );
            Errno::result(res).map(drop)
        }
    }
}
/*
 *
 * ===== Accessor helpers =====
 *
 */

/// Helper trait that describes what is expected from a `GetSockOpt` getter.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
pub trait Get<T> {
    /// Returns an uninitialized value.
    fn uninit() -> Self;
    /// Returns a pointer to the stored value. This pointer will be passed to the system's
    /// `getsockopt` call (`man 3p getsockopt`, argument `option_value`).
    fn ffi_ptr(&mut self) -> *mut c_void;
    /// Returns length of the stored value. This pointer will be passed to the system's
    /// `getsockopt` call (`man 3p getsockopt`, argument `option_len`).
    fn ffi_len(&mut self) -> *mut socklen_t;
    /// Returns the hopefully initialized inner value.
    unsafe fn assume_init(self) -> T;
}

/// Helper trait that describes what is expected from a `SetSockOpt` setter.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
pub trait Set<'a, T> {
    /// Initialize the setter with a given value.
    fn new(val: &'a T) -> Self;
    /// Returns a pointer to the stored value. This pointer will be passed to the system's
    /// `setsockopt` call (`man 3p setsockopt`, argument `option_value`).
    fn ffi_ptr(&self) -> *const c_void;
    /// Returns length of the stored value. This pointer will be passed to the system's
    /// `setsockopt` call (`man 3p setsockopt`, argument `option_len`).
    fn ffi_len(&self) -> socklen_t;
}

/// Getter for an arbitrary `struct`.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Debug)]
pub struct GetStruct<T> {
    len: socklen_t,
    val: MaybeUninit<T>,
}

impl<T> Get<T> for GetStruct<T> {
    fn uninit() -> Self {
        GetStruct {
            len: mem::size_of::<T>() as socklen_t,
            val: MaybeUninit::uninit(),
        }
    }

    fn ffi_ptr(&mut self) -> *mut c_void {
        self.val.as_mut_ptr().cast()
    }

    fn ffi_len(&mut self) -> *mut socklen_t {
        &mut self.len
    }

    unsafe fn assume_init(self) -> T {
        assert_eq!(
            self.len as usize,
            mem::size_of::<T>(),
            "invalid getsockopt implementation"
        );
        unsafe { self.val.assume_init() }
    }
}

/// Setter for an arbitrary `struct`.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Debug)]
pub struct SetStruct<'a, T: 'static> {
    ptr: &'a T,
}

impl<'a, T> Set<'a, T> for SetStruct<'a, T> {
    fn new(ptr: &'a T) -> SetStruct<'a, T> {
        SetStruct { ptr }
    }

    fn ffi_ptr(&self) -> *const c_void {
        self.ptr as *const T as *const c_void
    }

    fn ffi_len(&self) -> socklen_t {
        mem::size_of::<T>() as socklen_t
    }
}

/// Getter for a boolean value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct GetBool {
    len: socklen_t,
    val: MaybeUninit<c_int>,
}

impl Get<bool> for GetBool {
    fn uninit() -> Self {
        GetBool {
            len: mem::size_of::<c_int>() as socklen_t,
            val: MaybeUninit::uninit(),
        }
    }

    fn ffi_ptr(&mut self) -> *mut c_void {
        self.val.as_mut_ptr().cast()
    }

    fn ffi_len(&mut self) -> *mut socklen_t {
        &mut self.len
    }

    unsafe fn assume_init(self) -> bool {
        assert_eq!(
            self.len as usize,
            mem::size_of::<c_int>(),
            "invalid getsockopt implementation"
        );
        unsafe { self.val.assume_init() != 0 }
    }
}

/// Setter for a boolean value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetBool {
    val: c_int,
}

impl<'a> Set<'a, bool> for SetBool {
    fn new(val: &'a bool) -> SetBool {
        SetBool {
            val: i32::from(*val),
        }
    }

    fn ffi_ptr(&self) -> *const c_void {
        &self.val as *const c_int as *const c_void
    }

    fn ffi_len(&self) -> socklen_t {
        mem::size_of_val(&self.val) as socklen_t
    }
}

/// Getter for an `u8` value.
#[cfg(feature = "net")]
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct GetU8 {
    len: socklen_t,
    val: MaybeUninit<u8>,
}

#[cfg(feature = "net")]
impl Get<u8> for GetU8 {
    fn uninit() -> Self {
        GetU8 {
            len: mem::size_of::<u8>() as socklen_t,
            val: MaybeUninit::uninit(),
        }
    }

    fn ffi_ptr(&mut self) -> *mut c_void {
        self.val.as_mut_ptr().cast()
    }

    fn ffi_len(&mut self) -> *mut socklen_t {
        &mut self.len
    }

    unsafe fn assume_init(self) -> u8 {
        assert_eq!(
            self.len as usize,
            mem::size_of::<u8>(),
            "invalid getsockopt implementation"
        );
        unsafe { self.val.assume_init() }
    }
}

/// Setter for an `u8` value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetU8 {
    val: u8,
}

#[cfg(feature = "net")]
impl<'a> Set<'a, u8> for SetU8 {
    fn new(val: &'a u8) -> SetU8 {
        SetU8 { val: *val }
    }

    fn ffi_ptr(&self) -> *const c_void {
        &self.val as *const u8 as *const c_void
    }

    fn ffi_len(&self) -> socklen_t {
        mem::size_of_val(&self.val) as socklen_t
    }
}

/// Getter for an `usize` value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct GetUsize {
    len: socklen_t,
    val: MaybeUninit<c_int>,
}

impl Get<usize> for GetUsize {
    fn uninit() -> Self {
        GetUsize {
            len: mem::size_of::<c_int>() as socklen_t,
            val: MaybeUninit::uninit(),
        }
    }

    fn ffi_ptr(&mut self) -> *mut c_void {
        self.val.as_mut_ptr().cast()
    }

    fn ffi_len(&mut self) -> *mut socklen_t {
        &mut self.len
    }

    unsafe fn assume_init(self) -> usize {
        assert_eq!(
            self.len as usize,
            mem::size_of::<c_int>(),
            "invalid getsockopt implementation"
        );
        unsafe { self.val.assume_init() as usize }
    }
}

/// Setter for an `usize` value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetUsize {
    val: c_int,
}

impl<'a> Set<'a, usize> for SetUsize {
    fn new(val: &'a usize) -> SetUsize {
        SetUsize { val: *val as c_int }
    }

    fn ffi_ptr(&self) -> *const c_void {
        &self.val as *const c_int as *const c_void
    }

    fn ffi_len(&self) -> socklen_t {
        mem::size_of_val(&self.val) as socklen_t
    }
}


/// Getter for a `OwnedFd` value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct GetOwnedFd {
    len: socklen_t,
    val: MaybeUninit<c_int>,
}

impl Get<OwnedFd> for GetOwnedFd {
    fn uninit() -> Self {
        GetOwnedFd {
            len: mem::size_of::<c_int>() as socklen_t,
            val: MaybeUninit::uninit(),
        }
    }

    fn ffi_ptr(&mut self) -> *mut c_void {
        self.val.as_mut_ptr().cast()
    }

    fn ffi_len(&mut self) -> *mut socklen_t {
        &mut self.len
    }

    unsafe fn assume_init(self) -> OwnedFd {
        use std::os::fd::{FromRawFd, RawFd};

        assert_eq!(
            self.len as usize,
            mem::size_of::<c_int>(),
            "invalid getsockopt implementation"
        );
        unsafe { OwnedFd::from_raw_fd(self.val.assume_init() as RawFd) }
    }
}

/// Setter for an `OwnedFd` value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetOwnedFd {
    val: c_int,
}

impl<'a> Set<'a, OwnedFd> for SetOwnedFd {
    fn new(val: &'a OwnedFd) -> SetOwnedFd {
        use std::os::fd::AsRawFd;

        SetOwnedFd { val: val.as_raw_fd() as c_int }
    }

    fn ffi_ptr(&self) -> *const c_void {
        &self.val as *const c_int as *const c_void
    }

    fn ffi_len(&self) -> socklen_t {
        mem::size_of_val(&self.val) as socklen_t
    }
}

/// Getter for a `OsString` value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Debug)]
pub struct GetOsString<T: AsMut<[u8]>> {
    len: socklen_t,
    val: MaybeUninit<T>,
}

impl<T: AsMut<[u8]>> Get<OsString> for GetOsString<T> {
    fn uninit() -> Self {
        GetOsString {
            len: mem::size_of::<T>() as socklen_t,
            val: MaybeUninit::uninit(),
        }
    }

    fn ffi_ptr(&mut self) -> *mut c_void {
        self.val.as_mut_ptr().cast()
    }

    fn ffi_len(&mut self) -> *mut socklen_t {
        &mut self.len
    }

    unsafe fn assume_init(self) -> OsString {
        let len = self.len as usize;
        let mut v = unsafe { self.val.assume_init() };
        if let Ok(cs) = CStr::from_bytes_until_nul(&v.as_mut()[0..len]) {
            // It's legal for the kernel to return any number of NULs at the
            // end of the string.  C applications don't care, after all.
            OsStr::from_bytes(cs.to_bytes())
        } else {
            // Even zero NULs is possible.
            OsStr::from_bytes(&v.as_mut()[0..len])
        }
        .to_owned()
    }
}

/// Setter for a `OsString` value.
// Hide the docs, because it's an implementation detail of `sockopt_impl!`
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetOsString<'a> {
    val: &'a OsStr,
}

#[cfg(any(target_os = "freebsd", linux_android, target_os = "illumos"))]
impl<'a> Set<'a, OsString> for SetOsString<'a> {
    fn new(val: &OsString) -> SetOsString<'_> {
        SetOsString {
            val: val.as_os_str(),
        }
    }

    fn ffi_ptr(&self) -> *const c_void {
        self.val.as_bytes().as_ptr().cast()
    }

    fn ffi_len(&self) -> socklen_t {
        self.val.len() as socklen_t
    }
}

/// Getter for a `CString` value.
#[cfg(apple_targets)]
#[cfg(feature = "net")]
struct GetCString<T: AsMut<[u8]>> {
    len: socklen_t,
    val: MaybeUninit<T>,
}

#[cfg(apple_targets)]
#[cfg(feature = "net")]
impl<T: AsMut<[u8]>> Get<CString> for GetCString<T> {
    fn uninit() -> Self {
        GetCString {
            len: mem::size_of::<T>() as socklen_t,
            val: MaybeUninit::uninit(),
        }
    }

    fn ffi_ptr(&mut self) -> *mut c_void {
        self.val.as_mut_ptr().cast()
    }

    fn ffi_len(&mut self) -> *mut socklen_t {
        &mut self.len
    }

    unsafe fn assume_init(self) -> CString {
        let mut v = unsafe { self.val.assume_init() };
        CStr::from_bytes_until_nul(v.as_mut())
            .expect("string should be null-terminated")
            .to_owned()
    }
}
