{-# LANGUAGE CPP #-}

-----------------------------------------------------------------------------
-- |
-- This module allows you to set per-connection keep alive parameters on windows and linux enviroments.
-- For more information on keep alive signals see https://en.wikipedia.org/wiki/Keepalive.
-- See also https://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/ for a linux specific implementation.
--
-- The module is meant to be used in conjuction with the "network" package. However, in order to ensure adaptability, all functions require
-- a socket file descriptor instead of an implementation dependent socket type. For the network package, such a  descriptor can be obtained
-- with the withFdSocket function:
--
-- > -- sock is a Socket type
-- > withFdSocket sock $ \fd -> do
-- >    before <- getKeepAliveOnOff fd
-- >    print before -- False
-- >    -- set keep alive on, idle 60 seconds, interval 2 seconds
-- >    rlt <- setKeepAlive fd $ KeepAlive True 60 2
-- >    case rlt of
-- >        Left err -> print err
-- >        Right () -> return ()
-- >    after <- getKeepAliveOnOff fd
-- >    print after -- True
--
-- Please note that only the envocing process can manipulate sockets based on their file descriptors.

module Network.Socket.KeepAlive
    ( KeepAlive (..)
    , KeepAliveError (..)
    , setKeepAlive
    , getKeepAliveOnOff
    ) where

import           Data.Word  (Word32)
import           Foreign
import           Foreign.C
import           LibForeign


-- | The main data structure defining keep alive parameters
data KeepAlive = KeepAlive
    { kaOnOff :: Bool
    -- ^ Turns on / off keep alive probes
    , kaIdle  :: Word32
    -- ^ The interval in seconds between the last data packet sent and the first keep alive probe
    , kaIntvl :: Word32
    -- ^ The interval in seconds between subsequential keepalive probes
    }
    deriving (Show, Eq, Ord)

-- | Errors starting with WSA are windows specific
data KeepAliveError = WSA_IO_PENDING
    | WSA_OPERATION_ABORTED
    | WSAEFAULT
    | WSAEINPROGRESS
    | WSAEINTR
    | WSAEINVAL
    | WSAENETDOWN
    | WSAENOPROTOOPT
    | WSAENOTSOCK
    | WSAEOPNOTSUPP
    | EBADF
    | EDOM
    | EINVAL
    | EISCONN
    | ENOPROTOOPT
    | ENOTSOCK
    | ENOMEM
    | ENOBUFS
    | OTHER_KEEPALIVE_ERROR
    deriving (Show, Eq, Ord)

-- | Set keep alive parameters for the current socket
setKeepAlive ::
    CInt
    -- ^ Socket file descriptor
    -> KeepAlive
    -- ^ Keep alive parameters
    -> IO ( Either KeepAliveError ())
setKeepAlive fd (KeepAlive onoff idle intvl) = do
    rlt <- setKeepAlive_ fd (cFromBool onoff) idle intvl
    return $ case rlt of
        0     -> Right ()
        997   -> Left WSA_IO_PENDING
        995   -> Left WSA_OPERATION_ABORTED
        10014 -> Left WSAEFAULT
        10036 -> Left WSAEINPROGRESS
        10004 -> Left WSAEINTR
        10022 -> Left WSAEINVAL
        10050 -> Left WSAENETDOWN
        10042 -> Left WSAENOPROTOOPT
        10038 -> Left WSAENOTSOCK
        10045 -> Left WSAEOPNOTSUPP
        9     -> Left EBADF
        33    -> Left EDOM
        22    -> Left EINVAL
        106   -> Left EISCONN
        92    -> Left ENOPROTOOPT
        88    -> Left ENOTSOCK
        12    -> Left ENOMEM
        105   -> Left ENOBUFS
        _     -> Left OTHER_KEEPALIVE_ERROR

-- | Returns True if keep alive is active for the specified socket
getKeepAliveOnOff ::
    CInt
    -- ^ Socket file descriptor
    -> IO Bool
getKeepAliveOnOff fd =
    cToBool . fromInteger . toInteger <$> getKeepAliveOnOff_ fd

cToBool :: Int -> Bool
cToBool x
    | x == 0 = False
    | otherwise = True

cFromBool :: Bool -> Word32
cFromBool True  = 1
cFromBool False = 0