/**
 * Copyright (C) Mellanox Technologies Ltd. 2019.  ALL RIGHTS RESERVED.
 * Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
 *
 * See file LICENSE for terms.
 */

#ifndef UCS_SOCKET_H
#define UCS_SOCKET_H

#include <ucs/type/status.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <dirent.h>

BEGIN_C_DECLS


#define UCS_IPV4_ADDR_LEN            sizeof(struct in_addr)
#define UCS_IPV6_ADDR_LEN            sizeof(struct in6_addr)

/* A string to hold the IP address and port from a sockaddr */
#define UCS_SOCKADDR_STRING_LEN      60

#define UCS_SOCKET_INET_ADDR(_addr)  (((struct sockaddr_in*)(_addr))->sin_addr)
#define UCS_SOCKET_INET_PORT(_addr)  (((struct sockaddr_in*)(_addr))->sin_port)

#define UCS_SOCKET_INET6_ADDR(_addr) (((struct sockaddr_in6*)(_addr))->sin6_addr)
#define UCS_SOCKET_INET6_PORT(_addr) (((struct sockaddr_in6*)(_addr))->sin6_port)


/**
 * Error callback to handle errno and status of a given socket IO operation.
 *
 * @param [in] arg       User's argument for the error callback.
 * @param [in] io_status Status set for a given IO operation.
 *
 * @return UCS_OK if error handling was done in the callback and no other
 *         actions are required from a caller (UCS_ERR_CANCELED will be
 *         returned as the result of the IO operation), UCS_ERR_NO_PROGRESS
 *         if error handling was done in the callback and the IO operation
 *         should be continued (UCS_ERR_NO_PROGRESS will be retuned as the
 *         result of the IO operation), otherwise - the default error handling
 *         should be done and the returned status will be the result of
 *         the IO operation.
 */
typedef ucs_status_t (*ucs_socket_io_err_cb_t)(void *arg,
                                               ucs_status_t io_status);


/**
 * Close the given file descriptor.
 *
 * @param [in] fd_p   pointer to the file descriptor to close.
 */
void ucs_close_fd(int *fd_p);


/**
 * Check if the given (interface) flags represent an active interface.
 *
 * @param [in] flags  Interface flags (Can be obtained using getifaddrs
 *                    or from SIOCGIFFLAGS ioctl).
 *
 * @return 1 if true, otherwise 0
 */
int ucs_netif_flags_is_active(unsigned int flags);


/**
 * Perform an ioctl call on the given interface with the given request.
 * Set the result in the ifreq struct.
 *
 * @param [in]  if_name      Interface name to test.
 * @param [in]  request      The request to fulfill.
 * @param [out] if_req       Filled with the requested information.
 *
 * @return UCS_OK on success or an error code on failure.
 */
ucs_status_t ucs_netif_ioctl(const char *if_name, unsigned long request,
                             struct ifreq *if_req);


/**
 * Check if the given interface is in an active state.
 *
 * @param [in]  if_name      Interface name to check.
 *
 * @return 1 if true, otherwise 0
 */
int ucs_netif_is_active(const char *if_name);


/**
 * Get number of active 802.3ad ports for a bond device. If the device is not
 * a bond device, or 802.3ad is not enabled, return 1.
 *
 * @param [in]  if_name      Name of network interface to check.
 *
 * @return Number of active 802.3ad ports on @a if_name.
 */
unsigned ucs_netif_bond_ad_num_ports(const char *if_name);


/**
 * Create a socket.
 *
 * @param [in]   domain     Communication domain (AF_INET/AF_INET6/etc).
 * @param [in]   type       Communication semantics (SOCK_STREAM/SOCK_DGRAM/etc).
 * @param [out]  fd_p       Pointer to created fd.
 *
 * @return UCS_OK on success or UCS_ERR_IO_ERROR on failure.
 */
ucs_status_t ucs_socket_create(int domain, int type, int *fd_p);


/**
 * Set options on socket.
 *
 * @param [in]   fd          Socket fd.
 * @param [in]   level       The level at which the option is defined.
 * @param [in]   optname     The socket option for which the value is to be set.
 * @param [in]   optval      A pointer to the buffer in which the value for the
 *                           requested option is specified.
 * @param [in]   optlen      The size, in bytes, of the buffer pointed to by the
 *                           optval and def_optval parameters.
 *
 * @return UCS_OK on success or UCS_ERR_IO_ERROR on failure
 */
ucs_status_t ucs_socket_setopt(int fd, int level, int optname,
                               const void *optval, socklen_t optlen);


/**
 * Get options of a socket.
 *
 * @param [in]   fd          Socket fd.
 * @param [in]   level       The level at which the option is defined.
 * @param [in]   optname     The socket option for which the value is fetched.
 * @param [in]   optval      A pointer to the buffer in which the value for the
 *                           requested option is stored.
 * @param [in]   optlen      The size, in bytes, of optval.
 *
 * @return UCS_OK on success or UCS_ERR_IO_ERROR on failure
 */
ucs_status_t ucs_socket_getopt(int fd, int level, int optname,
                               void *optval, socklen_t optlen);


/**
 * Connect the socket referred to by the file descriptor `fd`
 * to the address specified by `dest_addr`.
 *
 * @param [in]  fd                Socket fd.
 * @param [in]  dest_addr         Pointer to destination address.
 *
 * @return UCS_OK on success or UCS_ERR_UNREACHABLE on failure or
 *         UCS_INPROGRESS if operation is in progress.
 */
ucs_status_t ucs_socket_connect(int fd, const struct sockaddr *dest_addr);


/**
 * Accept a connection request on the given socket fd.
 *
 * @param [in]  fd                Socket fd.
 * @param [out] addr              Client socket address that initiated the connection
 * @param [out] length_ptr        Client address socket's length
 * @param [out] accept_fd         Upon success, a non-negative file descriptor
 *                                of the accepted socket. Otherwise, -1.
 *
 * @return UCS_OK on success or UCS_ERR_NO_PROGRESS to indicate that no progress
 *         was made or UCS_ERR_IO_ERROR on failure.
 */
ucs_status_t ucs_socket_accept(int fd, struct sockaddr *addr, socklen_t *length_ptr,
                               int *accept_fd);


/**
 * Get the address of the peer's socket that the given fd is connected to
 *
 * @param [in]  fd                Socket fd.
 * @param [out] peer_addr         Address of the remote peer.
 * @param [out] peer_addr_len     Length of the remote peer's address.
 *
 * @return UCS_OK on success or UCS_ERR_IO_ERROR on failure
 */
ucs_status_t ucs_socket_getpeername(int fd, struct sockaddr_storage *peer_addr,
                                    socklen_t *peer_addr_len);


/**
 * Check whether the socket referred to by the file descriptor `fd`
 * is connected to a peer or not.
 *
 * @param [in]  fd          Socket fd.
 *
 * @return 1 - connected, 0 - not connected.
 */
int ucs_socket_is_connected(int fd);


/**
 * Set options on a socket for its send and receive buffers.
 * Set the options only if the given buffers sizes are not set to UCS_MEMUNITS_AUTO.
 *
 * @param [in]  fd                 Socket fd.
 * @param [in]  sockopt_sndbuf     Send buffer in which the value for the
 *                                 option is specified.
 * @param [in]  sockopt_rcvbuf     Receive buffer in which the value for the
 *                                 option is specified.
 *
 * @return UCS_OK on success or UCS_ERR_IO_ERROR on failure.
 */
ucs_status_t ucs_socket_set_buffer_size(int fd, size_t sockopt_sndbuf,
                                        size_t sockopt_rcvbuf);


/**
 * Initialize a TCP server.
 * Open a socket, bind a sockadrr to that socket and start listening on it for
 * incoming connection requests.
 *
 * @param [in]  saddr           Sockaddr for the server to listen on.
 *                              If the port number inside is set to zero -
 *                              use a random port.
 * @param [in]  socklen         Size of saddr.
 * @param [in]  backlog         Length of the queue for pending connections -
 *                              for the listen() call.
 * @param [out] listen_fd       The fd that belongs to the server.
 *
 * @return UCS_OK on success or an error code on failure.
 */
ucs_status_t ucs_socket_server_init(const struct sockaddr *saddr, socklen_t socklen,
                                    int backlog, int *listen_fd);


/**
 * Returns the maximum possible value for the number of sockets that
 * are ready to be accepted. It maybe either value from the system path
 * or SOMAXCONN value.
 *
 * @return The queue length for completely established sockets
 * waiting to be accepted.
 */
int ucs_socket_max_conn();


/**
 * Non-blocking send operation sends data on the connected (or bound
 * connectionless) socket referred to by the file descriptor `fd`.
 *
 * @param [in]      fd              Socket fd.
 * @param [in]      data            A pointer to a buffer containing the data to
 *                                  be transmitted.
 * @param [in/out]  length_p        The length, in bytes, of the data in buffer
 *                                  pointed to by the `data` parameter. The amount of
 *                                  data transmitted is written to this argument.
 * @param [in]      err_cb          Error callback.
 * @param [in]      err_cb_arg      User's argument for the error callback.
 *
 * @return UCS_OK on success, UCS_ERR_CANCELED if some error happened, but it
 *         was handled in a user's err_cb and no other actions are required,
 *         UCS_ERR_NO_PROGRESS if system call was interrupted or would block,
 *         UCS_ERR_NOT_CONNECTED if the connection was destroyed,
 *         UCS_ERR_IO_ERROR on failure, or any other errors returned from a
 *         user's error callback.
 */
ucs_status_t ucs_socket_send_nb(int fd, const void *data, size_t *length_p,
                                ucs_socket_io_err_cb_t err_cb,
                                void *err_cb_arg);


/**
 * Non-blocking receive operation receives data from the connected (or bound
 * connectionless) socket referred to by the file descriptor `fd`.
 *
 * @param [in]      fd              Socket fd.
 * @param [in]      data            A pointer to a buffer to receive the incoming
 *                                  data.
 * @param [in/out]  length_p        The length, in bytes, of the data in buffer
 *                                  pointed to by the `data` parameter. The amount of
 *                                  data received is written to this argument.
 * @param [in]      err_cb          Error callback.
 * @param [in]      err_cb_arg      User's argument for the error callback.
 *
 * @return UCS_OK on success, UCS_ERR_CANCELED if some error happened, but it
 *         was handled in user's err_cb and no other actions are required,
 *         UCS_ERR_NO_PROGRESS if system call was interrupted or would block,
 *         UCS_ERR_NOT_CONNECTED if the connection was destroyed,
 *         UCS_ERR_IO_ERROR on failure, or any other errors returned from a
 *         user's error callback.
 */
ucs_status_t ucs_socket_recv_nb(int fd, void *data, size_t *length_p,
                                ucs_socket_io_err_cb_t err_cb,
                                void *err_cb_arg);


/**
 * Blocking send operation sends data on the connected (or bound connectionless)
 * socket referred to by the file descriptor `fd`.
 *
 * @param [in]      fd              Socket fd.
 * @param [in]      data            A pointer to a buffer containing the data to
 *                                  be transmitted.
 * @param [in/out]  length          The length, in bytes, of the data in buffer
 *                                  pointed to by the `data` parameter.
 * @param [in]      err_cb          Error callback.
 * @param [in]      err_cb_arg      User's argument for the error callback.
 *
 * @return UCS_OK on success, UCS_ERR_CANCELED if some error happened, but it
 *         was handled in user's err_cb and no other actions are required,
 *         UCS_ERR_NOT_CONNECTED if the connection was destroyed,
 *         UCS_ERR_IO_ERROR on failure, or any other errors returned from a
 *         user's error callback.
 */
ucs_status_t ucs_socket_send(int fd, const void *data, size_t length,
                             ucs_socket_io_err_cb_t err_cb,
                             void *err_cb_arg);


/**
 * Non-blocking send operation sends I/O vector on the connected (or bound
 * connectionless) socket referred to by the file descriptor `fd`.
 *
 * @param [in]      fd              Socket fd.
 * @param [in]      iov             A pointer to an array of iovec buffers.
 * @param [in]      iov_cnt         The number of buffers pointed to by
 *                                  the iov parameter.
 * @param [out]     length_p        The amount of data transmitted is written to
 *                                  this argument.
 * @param [in]      err_cb          Error callback.
 * @param [in]      err_cb_arg      User's argument for the error callback.
 *
 * @return UCS_OK on success, UCS_ERR_CANCELED if some error happened, but it
 *         was handled in user's err_cb and no other actions are required,
 *         UCS_ERR_NO_PROGRESS if system call was interrupted or would block,
 *         UCS_ERR_NOT_CONNECTED if the connection was destroyed,
 *         UCS_ERR_IO_ERROR on failure, or any other errors returned from a
 *         user's error callback.
 */
ucs_status_t ucs_socket_sendv_nb(int fd, struct iovec *iov, size_t iov_cnt,
                                 size_t *length_p, ucs_socket_io_err_cb_t err_cb,
                                 void *err_cb_arg);


/**
 * Blocking receive operation receives data from the connected (or bound
 * connectionless) socket referred to by the file descriptor `fd`.
 *
 * @param [in]      fd              Socket fd.
 * @param [in]      data            A pointer to a buffer to receive the incoming
 *                                  data.
 * @param [in/out]  length          The length, in bytes, of the data in buffer
 *                                  pointed to by the `data` paramete.
 * @param [in]      err_cb          Error callback.
 * @param [in]      err_cb_arg      User's argument for the error callback.
 *
 * @return UCS_OK on success, UCS_ERR_CANCELED if some error happened, but it
 *         was handled in user's err_cb and no other actions are required,
 *         UCS_ERR_NOT_CONNECTED if the connection was destroyed,
 *         UCS_ERR_IO_ERROR on failure, or any other errors returned from a
 *         user's error callback.
 */
ucs_status_t ucs_socket_recv(int fd, void *data, size_t length,
                             ucs_socket_io_err_cb_t err_cb,
                             void *err_cb_arg);


/**
 * Return size of a given sockaddr structure.
 * 
 * @param [in]   addr       Pointer to sockaddr structure.
 * @param [out]  size_p     Pointer to variable where size of
 *                          sockaddr_in/sockaddr_in6 structure will be written
 *
 * @return UCS_OK on success or UCS_ERR_INVALID_PARAM on failure.
 */
ucs_status_t ucs_sockaddr_sizeof(const struct sockaddr *addr, size_t *size_p);


/**
 * Return port of a given sockaddr structure.
 * 
 * @param [in]   addr       Pointer to sockaddr structure.
 * @param [out]  port_p     Pointer to variable where port (host notation)
 *                          of sockaddr_in/sockaddr_in6 structure will be written
 *
 * @return UCS_OK on success or UCS_ERR_INVALID_PARAM on failure.
 */
ucs_status_t ucs_sockaddr_get_port(const struct sockaddr *addr, uint16_t *port_p);


/**
 * Set port to a given sockaddr structure.
 * 
 * @param [in]   addr       Pointer to sockaddr structure.
 * @param [in]   port       Port (host notation) that will be written
 *
 * @return UCS_OK on success or UCS_ERR_INVALID_PARAM on failure.
 */
ucs_status_t ucs_sockaddr_set_port(struct sockaddr *addr, uint16_t port);


/**
 * Return IP addr of a given sockaddr structure.
 * 
 * @param [in]   addr       Pointer to sockaddr structure.
 *
 * @return IP address of sockaddr_in/sockaddr_in6 structure
 *         on success or NULL on failure.
 */
const void *ucs_sockaddr_get_inet_addr(const struct sockaddr *addr);


/**
 * Extract the IP address from a given sockaddr and return it as a string.
 *
 * @param [in]   sock_addr   Sockaddr to take IP address from.
 * @param [out]  str         A string filled with the IP address.
 * @param [in]   max_size    Size of a string (considering '\0'-terminated symbol)
 *
 * @return ip_str if the sock_addr has a valid IP address or 'Invalid address'
 *         otherwise.
 */
const char* ucs_sockaddr_str(const struct sockaddr *sock_addr,
                             char *str, size_t max_size);


/**
 * Extract the IP address from a given socket fd and return it as a string.
 *
 * @param [in]   fd          Socket fd.
 * @param [out]  str         A string filled with the IP address.
 * @param [in]   max_size    Size of a string (considering '\0'-terminated symbol)
 *
 * @return ip_str if the sock_addr has a valid IP address or 'Invalid address'
 *         otherwise.
 */
const char *ucs_socket_getname_str(int fd, char *str, size_t max_size);


/**
 * Return a value indicating the relationships between passed sockaddr structures.
 *
 * @param [in]     sa1        Pointer to sockaddr structure #1.
 * @param [in]     sa2        Pointer to sockaddr structure #2.
 * @param [in/out] status_p   Pointer (can be NULL) to a status: UCS_OK on success
 *                            or UCS_ERR_INVALID_PARAM on failure.
 *
 * @return Returns an integral value indicating the relationship between the
 *         socket addresses:
 *         > 0 - the first socket address is greater than the second
 *               socket address;
 *         < 0 - the first socket address is lower than the second
 *               socket address;
 *         = 0 - the socket addresses are equal.
 *         Note: it returns a positive integer value in case of error occured
 *               during comparison.
 */
int ucs_sockaddr_cmp(const struct sockaddr *sa1,
                     const struct sockaddr *sa2,
                     ucs_status_t *status_p);


/**
 * Check if the IP addresses of the given sockaddrs are the same.
 *
 * @param [in] sa1        Pointer to sockaddr structure #1.
 * @param [in] sa2        Pointer to sockaddr structure #2.
 *
 * @return Return 0 if the IP addresses are the same and a non-zero value
 *         otherwise.
 */
int ucs_sockaddr_ip_cmp(const struct sockaddr *sa1, const struct sockaddr *sa2);


/**
 * Indicate if given IP addr is INADDR_ANY (IPV4) or in6addr_any (IPV6)
 * 
 * @param [in]   addr       Pointer to sockaddr structure.
 *
 * @return 1 if input is INADDR_ANY or in6addr_any
 *         0 if not
 */
int ucs_sockaddr_is_inaddr_any(struct sockaddr *addr);


/**
 * Copy the src_addr sockaddr to dst_addr sockaddr. The length to copy is
 * the size of the src_addr sockaddr.
 *
 * @param [in] dst_addr  Pointer to destination sockaddr (to copy to).
 * @param [in] src_addr  Pointer to source sockaddr (to copy from).
 *
 * @return UCS_OK on success or UCS_ERR_INVALID_PARAM on failure.
 */
ucs_status_t ucs_sockaddr_copy(struct sockaddr *dst_addr,
                               const struct sockaddr *src_addr);


/**
 * Copy into ifname_name the interface associated the IP on which the socket
 * file descriptor fd is bound on. IPv4 and IPv6 addresses are handled.
 *
 * @param [in]   fd          Socket fd.
 * @param [out]  if_str      A string filled with the interface name.
 * @param [in]   max_strlen  Maximum length of the if_str.
 */
ucs_status_t ucs_sockaddr_get_ifname(int fd, char *ifname_str, size_t max_strlen);


/**
 * Convert the given address family to a string containing its value.
 *
 * @param [in]   af          Address family to convert.
 *
 * Only IPv4 and IPv6 conversions are supported.
 */
const char *ucs_sockaddr_address_family_str(sa_family_t af);

END_C_DECLS

#endif
