{-# LANGUAGE TypeFamilies, FlexibleContexts #-}
--------------------------------------------------------------------------------
-- |
-- Module      :  System.Socket.Internal.Socket
-- Copyright   :  (c) Lars Petersen 2015
-- License     :  MIT
--
-- Maintainer  :  info@lars-petersen.net
-- Stability   :  experimental
--------------------------------------------------------------------------------
module System.Socket.Internal.Socket (
    Socket (..)
  , Family (..)
  , Type (..)
  , Protocol (..)
  ) where

import           Control.Concurrent.MVar
import           Foreign.C.Types
import           Foreign.Storable
import           System.Posix.Types

-- | A generic socket type. Use `System.Socket.socket` to create a new socket.
--
--   The socket is just an `Control.Concurrent.MVar.MVar`-wrapped file descriptor.
--   The `System.Socket.Unsafe.Socket` constructor is exported trough the unsafe
--   module 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 on a
--     totally different socket. 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 separate 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)

-- | The address `Family` determines the network protocol to use.
--
--   The most common address families are `System.Socket.Family.Inet.Inet` (IPv4)
--   and `System.Socket.Family.Inet6.Inet6` (IPv6).
class Storable (SocketAddress f) => Family f where
  -- | The number designating this `Family` on the specific platform. This
  --   method is only exported for implementing extension libraries.
  --
  --   This function shall yield the values of constants like `AF_INET`, `AF_INET6` etc.
  familyNumber :: f -> CInt
  -- | The `SocketAddress` type is a [data family](https://wiki.haskell.org/GHC/Type_families#Detailed_definition_of_data_families).
  --   This allows to provide different data constructors depending on the socket
  --   family without knowing all of them in advance or the need to extend this
  --   core library.
  --
  -- > SocketAddressInet  inetLoopback  8080     :: SocketAddress Inet
  -- > SocketAddressInet6 inet6Loopback 8080 0 0 :: SocketAddress Inet6
  data SocketAddress f

-- | The `Type` determines properties of the transport layer and the semantics
--   of basic socket operations.
--
--   The instances supplied by this library are `System.Socket.Type.Raw`
--   (no transport layer), `System.Socket.Type.Stream`
--   (for unframed binary streams, e.g. `System.Socket.Protocol.TCP`),
--   `System.Socket.Type.Datagram` (for datagrams
--   of limited length, e.g. `System.Socket.Protocol.UDP`) and
--   `System.Socket.Type.SequentialPacket` (for framed messages of arbitrary
--   length, e.g. `System.Socket.Protocol.SCTP`).
class Type t where
  -- | This number designates this `Type` on the specific platform. This
  --   method is only exported for implementing extension libraries.
  --
  --   The function shall yield the values of constants like `SOCK_STREAM`,
  --   `SOCK_DGRAM` etc.
  typeNumber :: t -> CInt

-- | The `Protocol` determines the transport protocol to use.
--
--   Use `System.Socket.Protocol.Default` to let the operating system choose
--   a transport protocol compatible with the socket's `Type`.
class Protocol  p where
  -- | This number designates this `Protocol` on the specific platform. This
  --   method is only exported for implementing extension libraries.
  --
  --   The function shall yield the values of constants like `IPPROTO_TCP`,
  --   `IPPROTO_UDP` etc.
  protocolNumber :: p -> CInt