#include <common/net/sock/StandardSocket.h>
#include <common/toolkit/Serialization.h>
#include <common/toolkit/SocketTk.h>
#include <common/Common.h>

#include <linux/in.h>
#include <linux/tcp.h>


#define SOCKET_LISTEN_BACKLOG                32
#define SOCKET_SHUTDOWN_RECV_BUF_LEN         32
#define STANDARDSOCKET_CONNECT_TIMEOUT_MS    5000

static const struct SocketOps standardOps = {
   .uninit = _StandardSocket_uninit,

   .connectByIP = _StandardSocket_connectByIP,
   .bindToAddr = _StandardSocket_bindToAddr,
   .listen = _StandardSocket_listen,
   .shutdown = _StandardSocket_shutdown,
   .shutdownAndRecvDisconnect = _StandardSocket_shutdownAndRecvDisconnect,

   .sendto = _StandardSocket_sendto,
   .recvT = _StandardSocket_recvT,
};

#ifdef KERNEL_HAS_SKWQ_HAS_SLEEPER
# define __sock_has_sleeper(wq) (skwq_has_sleeper(wq))
#else
# define __sock_has_sleeper(wq) (wq_has_sleeper(wq))
#endif

#if defined(KERNEL_HAS_SK_SLEEP) && !defined(KERNEL_HAS_SK_HAS_SLEEPER)
static inline int sk_has_sleeper(struct sock* sk)
{
   return sk->sk_sleep && waitqueue_active(sk->sk_sleep);
}
#endif

#if defined(KERNEL_WAKE_UP_SYNC_KEY_HAS_3_ARGUMENTS)
# define __wake_up_sync_key_m(wq, state, key) __wake_up_sync_key(wq, state, key)
#else
# define __wake_up_sync_key_m(wq, state, key) __wake_up_sync_key(wq, state, 1, key)
#endif


/* unlike linux sock_def_readable, this will also wake TASK_KILLABLE threads. we need this
 * for SocketTk_poll, which wants to wait for fatal signals only. */
#ifdef KERNEL_HAS_SK_DATA_READY_2
static void sock_readable(struct sock *sk, int len)
#else
static void sock_readable(struct sock *sk)
#endif
{
#ifdef KERNEL_HAS_SK_SLEEP
   read_lock(&sk->sk_callback_lock);
   if (sk_has_sleeper(sk))
   {
      __wake_up_sync_key_m(sk->sk_sleep, TASK_NORMAL,
            (void*) (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND));
   }
   read_unlock(&sk->sk_callback_lock);
#else
   struct socket_wq *wq;

   rcu_read_lock();
   wq = rcu_dereference(sk->sk_wq);
   if (__sock_has_sleeper(wq))
   {
      __wake_up_sync_key_m(&wq->wait, TASK_NORMAL,
            (void*) (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND));
   }
   rcu_read_unlock();
#endif
}

/* sock_def_write_space will also not wake uninterruptible threads. additionally, in newer kernels
 * it uses refcount_t for an optimization we will do not need: linux does not want to wake up
 * many writers if many of them cannot make progress. we have only a single writer. */
static void sock_write_space(struct sock *sk)
{
#ifdef KERNEL_HAS_SK_SLEEP
   read_lock(&sk->sk_callback_lock);

   if (sk_has_sleeper(sk))
   {
      __wake_up_sync_key_m(sk->sk_sleep, TASK_NORMAL,
            (void*) (POLLOUT | POLLWRNORM | POLLWRBAND));
   }

   read_unlock(&sk->sk_callback_lock);
#else
   struct socket_wq *wq;

   rcu_read_lock();

   wq = rcu_dereference(sk->sk_wq);
   if (__sock_has_sleeper(wq))
      __wake_up_sync_key_m(&wq->wait, TASK_NORMAL, (void*) (POLLOUT | POLLWRNORM | POLLWRBAND));

   rcu_read_unlock();
#endif
}

/* sock_def_wakeup, which is called for disconnects, has the same problem. */
static void sock_wakeup(struct sock *sk)
{
#ifdef KERNEL_HAS_SK_SLEEP
   read_lock(&sk->sk_callback_lock);
   if (sk_has_sleeper(sk))
      wake_up_all(sk->sk_sleep);
   read_unlock(&sk->sk_callback_lock);
#else
   struct socket_wq *wq;

   rcu_read_lock();
   wq = rcu_dereference(sk->sk_wq);
   if (__sock_has_sleeper(wq))
      wake_up_all(&wq->wait);
   rcu_read_unlock();
#endif
}

/* as does sock_def_error_report */
static void sock_error_report(struct sock *sk)
{
#ifdef KERNEL_HAS_SK_SLEEP
   read_lock(&sk->sk_callback_lock);
   if (sk_has_sleeper(sk))
      __wake_up_sync_key_m(sk->sk_sleep, TASK_NORMAL, (void*) (POLLERR));
   read_unlock(&sk->sk_callback_lock);
#else
   struct socket_wq *wq;

   rcu_read_lock();
   wq = rcu_dereference(sk->sk_wq);
   if (__sock_has_sleeper(wq))
      __wake_up_sync_key_m(&wq->wait, TASK_NORMAL, (void*) (POLLERR));
   rcu_read_unlock();
#endif
}


bool StandardSocket_init(StandardSocket* this, int domain, int type, int protocol)
{
   Socket* thisBase = (Socket*)this;

   NicAddrType_t nicType = NICADDRTYPE_STANDARD;

   // init super class
   if(domain == PF_SDP)
      nicType = NICADDRTYPE_SDP;

   _PooledSocket_init( (PooledSocket*)this, nicType);

   thisBase->ops = &standardOps;

   // normal init part

   this->sock = NULL;

   this->sockDomain = domain;

   if(domain == PF_SDP)
      thisBase->sockType = NICADDRTYPE_SDP;

   return _StandardSocket_initSock(this, domain, type, protocol);
}

StandardSocket* StandardSocket_construct(int domain, int type, int protocol)
{
   StandardSocket* this = kmalloc(sizeof(*this), GFP_NOFS);

   if(!this ||
      !StandardSocket_init(this, domain, type, protocol) )
   {
      kfree(this);
      return NULL;
   }

   return this;
}

StandardSocket* StandardSocket_constructUDP(void)
{
   return StandardSocket_construct(PF_INET, SOCK_DGRAM, 0);
}

StandardSocket* StandardSocket_constructTCP(void)
{
   return StandardSocket_construct(PF_INET, SOCK_STREAM, 0);
}

StandardSocket* StandardSocket_constructSDP(void)
{
   return StandardSocket_construct(PF_SDP, SOCK_STREAM, 0);
}

void _StandardSocket_uninit(Socket* this)
{
   StandardSocket* thisCast = (StandardSocket*)this;

   _PooledSocket_uninit(this);

   if(thisCast->sock)
      sock_release(thisCast->sock);
}

bool _StandardSocket_initSock(StandardSocket* this, int domain, int type, int protocol)
{
   int createRes;

   // prepare/create socket
#ifndef KERNEL_HAS_SOCK_CREATE_KERN_NS
   createRes = sock_create_kern(domain, type, protocol, &this->sock);
#else
   createRes = sock_create_kern(&init_net, domain, type, protocol, &this->sock);
#endif
   if(createRes < 0)
   {
      //printk_fhgfs(KERN_WARNING, "Failed to create socket\n");
      return false;
   }

   __StandardSocket_setAllocMode(this, GFP_NOFS);
   this->sock->sk->sk_data_ready = sock_readable;
   this->sock->sk->sk_write_space = sock_write_space;
   this->sock->sk->sk_state_change = sock_wakeup;
   this->sock->sk->sk_error_report = sock_error_report;

   return true;
}

void __StandardSocket_setAllocMode(StandardSocket* this, gfp_t flags)
{
   this->sock->sk->sk_allocation = flags;
}


/**
 * Use this to change socket options.
 * Note: Behaves (almost) like user-space setsockopt.
 *
 * @return 0 on success, error code otherwise (=> different from userspace version)
 */
int _StandardSocket_setsockopt(StandardSocket* this, int level,
   int optname, char* optval, int optlen)
{
   struct socket *sock = this->sock;

   #if defined(KERNEL_HAS_SOCK_SETSOCKOPT_SOCKPTR_T_PARAM)

      sockptr_t ptr = KERNEL_SOCKPTR(optval);

      if (level == SOL_SOCKET)
         return sock_setsockopt(sock, level, optname, ptr, optlen);
      else
         return sock->ops->setsockopt(sock, level, optname, ptr, optlen);

   #elif defined(KERNEL_HAS_GET_FS)

      char __user *ptr = (char __user __force *) optval;
      int r;

      WITH_PROCESS_CONTEXT
         if (level == SOL_SOCKET)
            r = sock_setsockopt(sock, level, optname, ptr, optlen);
         else
            r = sock->ops->setsockopt(sock, level, optname, ptr, optlen);
      return r;

   #else
      #error need set_fs()/get_fs() if sockptr_t is not available.
   #endif

      // unreachable
      BUG();
}

bool StandardSocket_setSoKeepAlive(StandardSocket* this, bool enable)
{
   int val = (enable ? 1 : 0);

   int r = _StandardSocket_setsockopt(this, SOL_SOCKET, SO_KEEPALIVE, (char *) &val, sizeof val);

   return r == 0;
}

bool StandardSocket_setSoBroadcast(StandardSocket* this, bool enable)
{
   int val = (enable ? 1 : 0);

   int r = _StandardSocket_setsockopt(this, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof val);

   return r == 0;
}

int StandardSocket_getSoRcvBuf(StandardSocket* this)
{
   //TODO: should this be READ_ONCE()? There are different uses in the Linux kernel
   return this->sock->sk->sk_rcvbuf;
}

/**
 * Note: Increase only (buffer will not be set to a smaller value).
 *
 * @return false on error, true otherwise (decrease skipping is not an error)
 */
bool StandardSocket_setSoRcvBuf(StandardSocket* this, int size)
{
   int origBufLen = StandardSocket_getSoRcvBuf(this);

   if (origBufLen >= size)
   {
      // we don't decrease buf sizes (but this is not an error)
      return true;
   }
   else
   {
      /* note: according to socket(7) man page, the value given to setsockopt()
       * is doubled and the doubled value is returned by getsockopt()
       *
       * update 2022-05-13: the kernel doubles the value passed to
       * setsockopt(SO_RCVBUF) to allow for bookkeeping overhead. Halving the
       * value is probably "not correct" but it's been this way since 2010 and
       * changing it will potentially do more harm than good at this point.
       */

      int val = size/2;

      int r = _StandardSocket_setsockopt(this, SOL_SOCKET, SO_RCVBUF, (char *)
            &val, sizeof val);

      if(r != 0)
         printk_fhgfs_debug(KERN_INFO, "%s: setSoRcvBuf error: %d;\n", __func__, r);

      return r == 0;
   }
}

bool StandardSocket_setTcpNoDelay(StandardSocket* this, bool enable)
{
   int val = (enable ? 1 : 0);

   int r = _StandardSocket_setsockopt(this, SOL_TCP, TCP_NODELAY, (char*) &val, sizeof val);

   return r == 0;
}

bool StandardSocket_setTcpCork(StandardSocket* this, bool enable)
{
   int val = (enable ? 1 : 0);

   int r = _StandardSocket_setsockopt(this, SOL_TCP, TCP_CORK, (char*) &val, sizeof val);

   return r == 0;
}

bool _StandardSocket_connectByIP(Socket* this, struct in_addr ipaddress, unsigned short port)
{
   // note: does not set the family type to the one of this socket (would lead to problems with SDP)

   // note: this might look a bit strange (it's kept similar to the c++ version)

   // note: error messages here would flood the log if hosts are unreachable on primary interface


   const int timeoutMS = STANDARDSOCKET_CONNECT_TIMEOUT_MS;

   StandardSocket* thisCast = (StandardSocket*)this;

   int connRes;

   struct sockaddr_in serveraddr =
   {
      .sin_family = AF_INET,
      .sin_addr = ipaddress,
      .sin_port = htons(port),
   };

   connRes = kernel_connect(thisCast->sock,
      (struct sockaddr*) &serveraddr,
      sizeof(serveraddr),
      O_NONBLOCK);

   if(connRes)
   {
      if(connRes == -EINPROGRESS)
      { // wait for "ready to send data"
         PollState state;
         int pollRes;

         PollState_init(&state);
         PollState_addSocket(&state, this, POLLOUT);

         pollRes = SocketTk_poll(&state, timeoutMS);

         if(pollRes > 0)
         { // we got something (could also be an error)

            /* note: it's important to test ERR/HUP/NVAL here instead of POLLOUT only, because
               POLLOUT and POLLERR can be returned together. */

            if(this->poll.revents & (POLLERR | POLLHUP | POLLNVAL) )
               return false;

            // connection successfully established

            if(!this->peername[0])
            {
               SocketTk_endpointAddrToStrNoAlloc(this->peername, SOCKET_PEERNAME_LEN, ipaddress, port);
               this->peerIP = ipaddress;
            }

            return true;
         }
         else
         if(!pollRes)
            return false; // timeout
         else
            return false; // connection error

      } // end of "EINPROGRESS"
   }
   else
   { // connected immediately

      // set peername if not done so already (e.g. by connect(hostname) )
      if(!this->peername[0])
      {
         SocketTk_endpointAddrToStrNoAlloc(this->peername, SOCKET_PEERNAME_LEN, ipaddress, port);
         this->peerIP = ipaddress;
      }

      return true;
   }

   return false;
}


bool _StandardSocket_bindToAddr(Socket* this, struct in_addr ipaddress, unsigned short port)
{
   StandardSocket* thisCast = (StandardSocket*)this;

   struct sockaddr_in bindAddr;
   int bindRes;

   bindAddr.sin_family = thisCast->sockDomain;
   bindAddr.sin_addr = ipaddress;
   bindAddr.sin_port = htons(port);

   bindRes = kernel_bind(thisCast->sock, (struct sockaddr*)&bindAddr, sizeof(bindAddr) );

   if(bindRes)
   {
      printk_fhgfs(KERN_WARNING, "Failed to bind socket. ErrCode: %d\n", bindRes);
      return false;
   }

   this->boundPort = port;

   return true;
}

bool _StandardSocket_listen(Socket* this)
{
   StandardSocket* thisCast = (StandardSocket*)this;
   int r;

   r = kernel_listen(thisCast->sock, SOCKET_LISTEN_BACKLOG);
   if(r)
   {
      printk_fhgfs(KERN_WARNING, "Failed to set socket to listening mode. ErrCode: %d\n",
         r);
      return false;
   }

   snprintf(this->peername, SOCKET_PEERNAME_LEN, "Listen(Port: %d)", this->boundPort);

   return true;
}

bool _StandardSocket_shutdown(Socket* this)
{
   StandardSocket* thisCast = (StandardSocket*)this;

   int sendshutRes;

   sendshutRes = kernel_sock_shutdown(thisCast->sock, SEND_SHUTDOWN);

   if( (sendshutRes < 0) && (sendshutRes != -ENOTCONN) )
   {
      printk_fhgfs(KERN_WARNING, "Failed to send shutdown. ErrCode: %d\n", sendshutRes);
      return false;
   }

   return true;
}

bool _StandardSocket_shutdownAndRecvDisconnect(Socket* this, int timeoutMS)
{
   bool shutRes;
   char buf[SOCKET_SHUTDOWN_RECV_BUF_LEN];
   int recvRes;

   shutRes = this->ops->shutdown(this);
   if(!shutRes)
      return false;

   // receive until shutdown arrives
   do
   {
      recvRes = Socket_recvT_kernel(this, buf, SOCKET_SHUTDOWN_RECV_BUF_LEN, 0, timeoutMS);
   } while(recvRes > 0);

   if(recvRes &&
      (recvRes != -ECONNRESET) )
   { // error occurred (but we're disconnecting, so we don't really care about errors)
      return false;
   }

   return true;
}



/* Compatibility wrappers for sock_sendmsg / sock_recvmsg. At some point in the
 * 4.x series, the size argument disappeared. */
static int beegfs_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, int flags)
{
#ifdef KERNEL_HAS_RECVMSG_SIZE
   return sock_recvmsg(sock, msg, len, flags);
#else
   return sock_recvmsg(sock, msg, flags);
#endif
}
static int beegfs_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
{
#ifdef KERNEL_HAS_RECVMSG_SIZE
   return sock_sendmsg(sock, msg, len);
#else
   return sock_sendmsg(sock, msg);
#endif
}




/**
 * @return -ETIMEDOUT on timeout
 */
ssize_t _StandardSocket_recvT(Socket* this, struct iov_iter* iter, int flags, int timeoutMS)
{
   StandardSocket* thisCast = (StandardSocket*)this;

   return StandardSocket_recvfromT(thisCast, iter, flags, NULL, timeoutMS);
}


ssize_t _StandardSocket_sendto(Socket* this, struct iov_iter* iter, int flags,
   fhgfs_sockaddr_in *to)
{
   StandardSocket* thisCast = (StandardSocket*)this;
   struct socket *sock = thisCast->sock;

   int sendRes;
   size_t len;
   struct sockaddr_in toSockAddr;

   struct msghdr msg =
   {
      .msg_control      = NULL,
      .msg_controllen   = 0,
      .msg_flags        = flags | MSG_NOSIGNAL,
      .msg_name         = (struct sockaddr*)(to ? &toSockAddr : NULL),
      .msg_namelen      = sizeof(toSockAddr),
      .msg_iter         = *iter,
   };

   len = iov_iter_count(iter);

   if (to)
   {
      toSockAddr.sin_family = thisCast->sockDomain;
      toSockAddr.sin_addr = to->addr;
      toSockAddr.sin_port = to->port;
   }

   sendRes = beegfs_sendmsg(sock, &msg, len);

   if(sendRes >= 0)
      iov_iter_advance(iter, sendRes);

   return sendRes;
}

ssize_t StandardSocket_recvfrom(StandardSocket* this, struct iov_iter* iter, int flags,
   fhgfs_sockaddr_in *from)
{
   int recvRes;
   size_t len;
   struct sockaddr_in fromSockAddr;
   struct socket *sock = this->sock;

   struct msghdr msg =
   {
      .msg_control      = NULL,
      .msg_controllen   = 0,
      .msg_flags        = flags,
      .msg_name         = (struct sockaddr*)&fromSockAddr,
      .msg_namelen      = sizeof(fromSockAddr),
      .msg_iter         = *iter,
   };

   len = iov_iter_count(iter);

   recvRes = beegfs_recvmsg(sock, &msg, len, flags);

   if(recvRes > 0)
      iov_iter_advance(iter, recvRes);

   if(from)
   {
      from->addr = fromSockAddr.sin_addr;
      from->port = fromSockAddr.sin_port;
   }

   return recvRes;
}

/**
 * @return -ETIMEDOUT on timeout
 */
ssize_t StandardSocket_recvfromT(StandardSocket* this, struct iov_iter* iter, int flags,
   fhgfs_sockaddr_in *from, int timeoutMS)
{
   Socket* thisBase = (Socket*)this;

   int pollRes;
   PollState state;

   if(timeoutMS < 0)
      return StandardSocket_recvfrom(this, iter, flags, from);

   PollState_init(&state);
   PollState_addSocket(&state, thisBase, POLLIN);

   pollRes = SocketTk_poll(&state, timeoutMS);

   if( (pollRes > 0) && (thisBase->poll.revents & POLLIN) )
      return StandardSocket_recvfrom(this, iter, flags, from);

   if(!pollRes)
      return -ETIMEDOUT;

   if(thisBase->poll.revents & POLLERR)
      printk_fhgfs_debug(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: Error condition\n",
         thisBase->peername);
   else
   if(thisBase->poll.revents & POLLHUP)
      printk_fhgfs_debug(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: Hung up\n",
         thisBase->peername);
   else
   if(thisBase->poll.revents & POLLNVAL)
      printk_fhgfs(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: Invalid request\n",
         thisBase->peername);
   else
      printk_fhgfs(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: ErrCode: %d\n",
         thisBase->peername, pollRes);

   return -ECOMM;
}
