{-# LINE 1 "src/System/Socket/Internal/Socket.hsc" #-}
module System.Socket.Internal.Socket (
{-# LINE 2 "src/System/Socket/Internal/Socket.hsc" #-}
    Socket (..)
  , GetSockOpt (..)
  , SetSockOpt (..)
  , SO_ACCEPTCONN (..)
  , SO_REUSEADDR (..)
  ) where

import Control.Concurrent.MVar
import Control.Exception
import Control.Monad
import Control.Applicative

import Foreign.Ptr
import Foreign.Storable
import Foreign.C.Types
import Foreign.Marshal.Alloc
import System.Posix.Types

import System.Socket.Internal.FFI
import System.Socket.Internal.Exception


{-# LINE 24 "src/System/Socket/Internal/Socket.hsc" #-}

-- | A generic socket type. Also see `socket` for details.
--
--   The socket is just an `Control.Concurrent.MVar.MVar`-wrapped file descriptor.
--   It is exposed in order to make this library easily extensible, but it is
--   usually not necessary nor advised to work directly on the file descriptor.
--   If you do, the following rules must be obeyed:
--
--   - Make sure not to deadlock. Use `Control.Concurrent.MVar.withMVar` or similar.
--   - The lock __must not__ be held during a blocking call. This would make it impossible
--     to send and receive simultaneously or to close the socket.
--   - The lock __must__ be held when calling operations that use the file descriptor.
--     Otherwise the socket might get closed or even reused by another
--     thread/capability which might result in reading from or writing
--     totally different connection. This is a security nightmare!
--   - The socket is non-blocking and all the code relies on that assumption.
--     You need to use GHC's eventing mechanism primitives to block until
--     something happens. The former rules forbid to use `GHC.Conc.threadWaitRead` as it
--     does not seperate between registering the file descriptor (for which
--     the lock __must__ be held) and the actual waiting (for which you must
--     __not__ hold the lock).
--     Also see [this](https://mail.haskell.org/pipermail/haskell-cafe/2014-September/115823.html)
--     thread and read the library code to see how the problem is currently circumvented.
newtype Socket f t p
      = Socket (MVar Fd)

class GetSockOpt o where
  getSockOpt :: Socket f t p -> IO o

class SetSockOpt o where
  setSockOpt :: Socket f t p -> o -> IO ()

data SO_ACCEPTCONN
   = SO_ACCEPTCONN Bool

instance GetSockOpt SO_ACCEPTCONN where
  getSockOpt s =
    SO_ACCEPTCONN <$> getSockOptBool s (1) (30)
{-# LINE 62 "src/System/Socket/Internal/Socket.hsc" #-}

data SO_REUSEADDR
   = SO_REUSEADDR Bool

instance GetSockOpt SO_REUSEADDR where
  getSockOpt s =
    SO_REUSEADDR <$> getSockOptBool s (1) (2)
{-# LINE 69 "src/System/Socket/Internal/Socket.hsc" #-}

instance SetSockOpt SO_REUSEADDR where
  setSockOpt s (SO_REUSEADDR o) =
    setSockOptBool s (1) (2) o
{-# LINE 73 "src/System/Socket/Internal/Socket.hsc" #-}

-------------------------------------------------------------------------------
-- Unsafe helpers
-------------------------------------------------------------------------------

setSockOptBool :: Socket f t p -> CInt -> CInt -> Bool -> IO ()
setSockOptBool (Socket mfd) level name value = do
  withMVar mfd $ \fd->
    alloca $ \vPtr-> do
        if value
          then poke vPtr 1
          else poke vPtr 0
        i <- c_setsockopt fd level name 
                          (vPtr :: Ptr CInt)
                          (fromIntegral $ sizeOf (undefined :: CInt))
        when (i < 0) $ do
          c_get_last_socket_error >>= throwIO

getSockOptBool :: Socket f t p -> CInt -> CInt -> IO Bool
getSockOptBool (Socket mfd) level name = do
  withMVar mfd $ \fd->
    alloca $ \vPtr-> do
      alloca $ \lPtr-> do
        i <- c_getsockopt fd level name 
                          (vPtr :: Ptr CInt)
                          (lPtr :: Ptr CInt)
        if i < 0 then do
          c_get_last_socket_error >>= throwIO
        else do
          v <- peek vPtr
          return (v == 1)