packages feed

posix-api-0.1.0.0: src/Posix/Socket.hs

{-# language BangPatterns #-}
{-# language DataKinds #-}
{-# language InterruptibleFFI #-}
{-# language MagicHash #-}
{-# language ScopedTypeVariables #-}
{-# language UnboxedTuples #-}
{-# language UnliftedFFITypes #-}

-- | Types and functions related to the POSIX sockets API.
--   Unusual characteristics:
--
--   * Any time the standard calls for @socklen_t@, we use
--     @CInt@ instead. Linus Torvalds <https://yarchive.net/comp/linux/socklen_t.html writes>
--     that \"Any sane library must have @socklen_t@ be the same size as @int@.
--     Anything else breaks any BSD socket layer stuff.\"
--   * Send and receive each have several variants. They are distinguished by
--     the safe/unsafe FFI use and by the @Addr@/@ByteArray@/@MutableByteArray@
--     buffer type. They all call @send@ or @recv@ exactly once. They do not
--     repeatedly make syscalls like some of the functions in @network@.
--     Users who want that behavior need to build on top of this package.
module Posix.Socket
  ( -- * Functions
    -- ** Socket
    socket
    -- ** Socket Pair
  , socketPair
    -- ** Bind
  , bind
    -- ** Connect
  , connect
    -- ** Listen
  , listen
    -- ** Accept
  , accept
  , accept_
    -- ** Close
  , close
  , unsafeClose
    -- ** Send
  , send
  , sendByteArray
  , sendMutableByteArray
  , unsafeSend
  , unsafeSendByteArray
  , unsafeSendMutableByteArray
    -- ** Send To
  , unsafeSendToByteArray
  , unsafeSendToMutableByteArray
    -- ** Receive
  , receive
  , receiveByteArray
  , unsafeReceive
  , unsafeReceiveMutableByteArray
    -- ** Receive From
  , unsafeReceiveFromMutableByteArray
  , unsafeReceiveFromMutableByteArray_
    -- * Types
  , Domain(..)
  , Type(..)
  , Protocol(..)
  , Flags(..)
    -- * Socket Address
    -- ** Types
  , SocketAddress(..)
  , PST.SocketAddressInternet(..)
  , PST.SocketAddressUnix(..)
    -- ** Encoding
  , PSP.encodeSocketAddressInternet
  , PSP.encodeSocketAddressUnix
    -- * Data Construction
    -- ** Socket Domains
  , PST.unix
  , PST.unspecified
  , PST.internet
    -- ** Socket Types
  , PST.stream
  , PST.datagram
  , PST.raw
  , PST.sequencedPacket
    -- ** Protocols
  , PST.defaultProtocol
  , PST.rawProtocol
  , PST.icmp
  , PST.tcp
  , PST.udp
  , PST.ip
  , PST.ipv6
    -- ** Receive Flags
  , PST.peek
  , PST.outOfBand
  , PST.waitAll
  ) where

import GHC.IO (IO(..))
import Data.Primitive (MutablePrimArray(..),MutableByteArray(..),Addr(..),ByteArray(..))
import Data.Void (Void)
import Foreign.C.Error (Errno,getErrno)
import Foreign.C.Types (CInt(..),CSize(..))
import Foreign.Ptr (nullPtr)
import GHC.Exts (Ptr,RealWorld,ByteArray#,MutableByteArray#,Addr#)
import Posix.Socket.Types (Domain(..),Protocol(..),Type(..),SocketAddress(..))
import Posix.Socket.Types (Flags(..),Message(..))
import System.Posix.Types (Fd(..),CSsize(..))

import qualified Posix.Socket.Types as PST
import qualified Data.Primitive as PM

-- This module include operating-system specific code used
-- to serialize some of various kind of socket address types.
import qualified Posix.Socket.Platform as PSP

foreign import ccall unsafe "sys/socket.h socket"
  c_socket :: Domain -> Type -> Protocol -> IO Fd

foreign import ccall unsafe "sys/socket.h socketpair"
  c_socketpair :: Domain -> Type -> Protocol -> MutableByteArray# RealWorld -> IO CInt

foreign import ccall unsafe "sys/socket.h listen"
  c_listen :: Fd -> CInt -> IO CInt

foreign import ccall interruptible "unistd.h close"
  c_safe_close :: Fd -> IO CInt

foreign import ccall unsafe "unistd.h close"
  c_unsafe_close :: Fd -> IO CInt

-- Per the spec, the type signature of bind is:
--   int bind(int socket, const struct sockaddr *address, socklen_t address_len);
-- However, here we choose to represent the third argument as
-- CInt rather than introducing a type corresponding to socklen_t.
-- According to Linus Torvalds, "Any sane library must have socklen_t
-- be the same size as int. Anything else breaks any BSD socket layer stuff."
-- (https://yarchive.net/comp/linux/socklen_t.html). If a platform
-- violates this assumption, this wrapper will be broken on that platform.
foreign import ccall unsafe "sys/socket.h bind"
  c_bind :: Fd -> ByteArray# -> CInt -> IO CInt

-- Per the spec, the type signature of accept is:
--   int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
-- The restrict keyword does not matter much for our purposes. See the
-- note on c_bind for why we use CInt for socklen_t. Remember that the
-- first bytearray argument is actually SocketAddress in the function that
-- wraps this one. The second bytearray argument is a pointer to the size.
foreign import ccall interruptible "sys/socket.h accept"
  c_safe_accept :: Fd
                -> MutableByteArray# RealWorld -- SocketAddress
                -> MutableByteArray# RealWorld -- Ptr CInt
                -> IO Fd
foreign import ccall interruptible "sys/socket.h accept"
  c_safe_ptr_accept :: Fd -> Ptr Void -> Ptr CInt -> IO Fd

-- Per the spec the type signature of connect is:
--   int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-- The bytearray argument is actually SocketAddress.
foreign import ccall interruptible "sys/socket.h connect"
  c_safe_connect :: Fd -> ByteArray# -> CInt -> IO CInt

-- There are several options for wrapping send. Both safe and unsafe
-- are useful. Additionally, in the unsafe category, we also
-- have the option of writing to either an address or a byte array.
-- Unsafe FFI calls guarantee that byte arrays will not be relocated
-- while the FFI call is taking place. Safe FFI calls do not have
-- this guarantee, so internally we must be careful when using these to only
-- provide pinned byte arrays as arguments.
foreign import ccall interruptible "sys/socket.h send"
  c_safe_addr_send :: Fd -> Addr# -> CSize -> Flags 'Send -> IO CSsize
foreign import ccall interruptible "sys/socket.h send_offset"
  c_safe_bytearray_send :: Fd -> ByteArray# -> CInt -> CSize -> Flags 'Send -> IO CSsize
foreign import ccall interruptible "sys/socket.h send_offset"
  c_safe_mutablebytearray_send :: Fd -> MutableByteArray# RealWorld -> CInt -> CSize -> Flags 'Send -> IO CSsize
foreign import ccall interruptible "sys/socket.h send"
  c_safe_mutablebytearray_no_offset_send :: Fd -> MutableByteArray# RealWorld -> CSize -> Flags 'Send -> IO CSsize
foreign import ccall unsafe "sys/socket.h send"
  c_unsafe_addr_send :: Fd -> Addr# -> CSize -> Flags 'Send -> IO CSsize
foreign import ccall unsafe "sys/socket.h send_offset"
  c_unsafe_bytearray_send :: Fd -> ByteArray# -> CInt -> CSize -> Flags 'Send -> IO CSsize
foreign import ccall unsafe "sys/socket.h send_offset"
  c_unsafe_mutable_bytearray_send :: Fd -> MutableByteArray# RealWorld -> CInt -> CSize -> Flags 'Send -> IO CSsize

-- The ByteArray# (second to last argument) is a SocketAddress.
foreign import ccall unsafe "sys/socket.h sendto_offset"
  c_unsafe_bytearray_sendto :: Fd -> ByteArray# -> CInt -> CSize -> Flags 'Send -> ByteArray# -> CInt -> IO CSsize
-- The ByteArray# (second to last argument) is a SocketAddress.
foreign import ccall unsafe "sys/socket.h sendto_offset"
  c_unsafe_mutable_bytearray_sendto :: Fd -> MutableByteArray# RealWorld -> CInt -> CSize -> Flags 'Send -> ByteArray# -> CInt -> IO CSsize

-- There are several ways to wrap recv.
foreign import ccall interruptible "sys/socket.h recv"
  c_safe_addr_recv :: Fd -> Addr# -> CSize -> Flags 'Receive -> IO CSsize
foreign import ccall unsafe "sys/socket.h recv"
  c_unsafe_addr_recv :: Fd -> Addr# -> CSize -> Flags 'Receive -> IO CSsize
foreign import ccall unsafe "sys/socket.h recv_offset"
  c_unsafe_mutable_byte_array_recv :: Fd -> MutableByteArray# RealWorld -> CInt -> CSize -> Flags 'Receive -> IO CSsize

-- The last two arguments are SocketAddress and Ptr CInt.
foreign import ccall unsafe "sys/socket.h recvfrom_offset"
  c_unsafe_mutable_byte_array_recvfrom :: Fd -> MutableByteArray# RealWorld -> CInt -> CSize -> Flags 'Receive -> MutableByteArray# RealWorld -> MutableByteArray# RealWorld -> IO CSsize
foreign import ccall unsafe "sys/socket.h recvfrom_offset"
  c_unsafe_mutable_byte_array_ptr_recvfrom :: Fd -> MutableByteArray# RealWorld -> CInt -> CSize -> Flags 'Receive -> Ptr Void -> Ptr CInt -> IO CSsize

-- | Create an endpoint for communication, returning a file
--   descriptor that refers to that endpoint. The
--   <http://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html POSIX specification>
--   includes more details. This is implemented as an unsafe FFI
--   call since the author believes that it cannot block indefinitely.
socket ::
     Domain -- ^ Communications domain (e.g. 'internet', 'unix')
  -> Type -- ^ Socket type (e.g. 'datagram', 'stream')
  -> Protocol -- ^ Protocol
  -> IO (Either Errno Fd)
socket dom typ prot = c_socket dom typ prot >>= errorsFromFd

-- | Create an unbound pair of connected sockets in a specified domain, of
--   a specified type, under the protocol optionally specified by the protocol
--   argument. The <http://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html POSIX specification>
--   includes more details. This is implemented as an unsafe FFI call
--   since the author believes that it cannot block indefinitely.
socketPair ::
     Domain -- ^ Communications domain (probably 'unix')
  -> Type -- ^ Socket type (e.g. 'datagram', 'stream')
  -> Protocol -- ^ Protocol
  -> IO (Either Errno (Fd,Fd))
socketPair dom typ prot = do
  -- If this ever switches to the safe FFI, we will need to use
  -- a pinned array here instead.
  (sockets@(MutablePrimArray sockets#) :: MutablePrimArray RealWorld Fd) <- PM.newPrimArray 2
  r <- c_socketpair dom typ prot sockets#
  if r == 0
    then do
      fd1 <- PM.readPrimArray sockets 0
      fd2 <- PM.readPrimArray sockets 1
      pure (Right (fd1,fd2))
    else fmap Left getErrno

-- | Assign a local socket address address to a socket identified by
--   descriptor socket that has no local socket address assigned. The
--   <http://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html POSIX specification>
--   includes more details. The 'SocketAddress' represents the @sockaddr@ pointer argument, together
--   with its @socklen_t@ size, as a byte array. This allows @bind@ to
--   be used with @sockaddr@ extensions on various platforms. This uses
--   the unsafe FFI since the author believes it cannot block indefinitely.
bind ::
     Fd -- ^ Socket
  -> SocketAddress -- ^ Socket address, extensible tagged union
  -> IO (Either Errno ())
bind fd (SocketAddress b@(ByteArray b#)) =
  c_bind fd b# (intToCInt (PM.sizeofByteArray b)) >>= errorsFromInt

-- | Mark the socket as a passive socket, that is, as a socket that
--   will be used to accept incoming connection requests using @accept@.
--   The <http://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html POSIX specification>
--   includes more details. Listen uses the unsafe FFI since it cannot block
--   and always returns promptly.
listen ::
     Fd -- ^ Socket
  -> CInt -- ^ Backlog
  -> IO (Either Errno ())
listen fd backlog = c_listen fd backlog >>= errorsFromInt

-- | Connect the socket to the specified socket address.
--   The <http://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html POSIX specification>
--   includes more details. An @unsafeConnect@ using the unsafe FFI is
--   not provided since there is no way to hook such a beast into the
--   event manager.
connect ::
     Fd -- ^ Fd
  -> SocketAddress -- ^ Socket address, extensible tagged union
  -> IO (Either Errno ())
connect fd (SocketAddress sockAddr@(ByteArray sockAddr#)) =
  c_safe_connect fd sockAddr# (intToCInt (PM.sizeofByteArray sockAddr)) >>= errorsFromInt

-- | Extract the first connection on the queue of pending connections. The
--   <http://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html POSIX specification>
--   includes more details. This function\'s type differs slightly from
--   the specification:
--
--   > int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
--
--   Instead of requiring the caller to prepare buffers through which
--   information is returned, this haskell binding to @accept@ prepares
--   those buffers internally. This eschews C\'s characteristic buffer-passing
--   in favor of the Haskell convention of allocating internally and returning.
--
--   More specifically, this binding lacks an argument corresponding to the
--   @sockaddr@ buffer from the specification. That mutable buffer is allocated
--   internally, resized and frozen upon a success, and returned along with
--   the file descriptor of the accepted socket. The size of this buffer is
--   determined by the second argument (maximum socket address size). This
--   size argument is also writen to the @address_len@ buffer, which is also
--   allocated internally. The size returned through this pointer is used to
--   resize the @sockaddr@ buffer, which is then frozen so that an immutable
--   'SocketAddress' is returned to the end user.
--
--   For applications uninterested in the peer (described by @sockaddr@),
--   POSIX @accept@ allows the null pointer to be passed as both @address@ and
--   @address_len@. This behavior is provided by 'accept_'.
accept ::
     Fd -- ^ Listening socket
  -> CInt -- ^ Maximum socket address size
  -> IO (Either Errno (SocketAddress,Fd)) -- ^ Peer information and connected socket
accept sock maxSz = do
  sockAddrBuf@(MutableByteArray sockAddrBuf#) <- PM.newPinnedByteArray (cintToInt maxSz)
  lenBuf@(MutableByteArray lenBuf#) <- PM.newPinnedByteArray (PM.sizeOf (undefined :: CInt))
  PM.writeByteArray lenBuf 0 maxSz
  r <- c_safe_accept sock sockAddrBuf# lenBuf#
  if r > (-1)
    then do
      (sz :: CInt) <- PM.readByteArray lenBuf 0
      sockAddr <- PM.unsafeFreezeByteArray =<< PM.resizeMutableByteArray sockAddrBuf (cintToInt sz)
      pure (Right (SocketAddress sockAddr,r))
    else fmap Left getErrno

-- | A variant of 'accept' that does not provide the user with a
--   'SocketAddress' detailing the peer.
accept_ ::
     Fd -- ^ Listening socket
  -> IO (Either Errno Fd) -- ^ Connected socket
accept_ sock =
  c_safe_ptr_accept sock nullPtr nullPtr >>= errorsFromFd

-- | Send data from a byte array over a network socket. Users
--   may specify an offset and a length to send fewer bytes than are
--   actually present in the array. Since this uses the safe interruptible
--   FFI, it allocates a pinned copy of the bytearry if it was not
--   already pinned.
sendByteArray ::
     Fd -- ^ Socket
  -> ByteArray -- ^ Source byte array
  -> CInt -- ^ Offset into source array
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
sendByteArray fd b@(ByteArray b#) off len flags = if PM.isByteArrayPinned b
  then errorsFromSize =<< c_safe_bytearray_send fd b# off len flags
  else do
    x@(MutableByteArray x#) <- PM.newPinnedByteArray (csizeToInt len)
    PM.copyByteArray x (cintToInt off) b 0 (csizeToInt len)
    errorsFromSize =<< c_safe_mutablebytearray_no_offset_send fd x# len flags

-- | Send data from a mutable byte array over a network socket. Users
--   may specify an offset and a length to send fewer bytes than are
--   actually present in the array. Since this uses the safe interruptible
--   FFI, it allocates a pinned copy of the bytearry if it was not
--   already pinned.
sendMutableByteArray ::
     Fd -- ^ Socket
  -> MutableByteArray RealWorld -- ^ Source byte array
  -> CInt -- ^ Offset into source array
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
sendMutableByteArray fd b@(MutableByteArray b#) off len flags = if PM.isMutableByteArrayPinned b
  then errorsFromSize =<< c_safe_mutablebytearray_send fd b# off len flags
  else do
    x@(MutableByteArray x#) <- PM.newPinnedByteArray (csizeToInt len)
    PM.copyMutableByteArray x (cintToInt off) b 0 (csizeToInt len)
    errorsFromSize =<< c_safe_mutablebytearray_no_offset_send fd x# len flags

-- | Send data from an address over a network socket. This is not guaranteed
--   to send the entire length. This uses the safe interruptible FFI since
--   it may block indefinitely.
send ::
     Fd -- ^ Connected socket
  -> Addr -- ^ Source address
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
send fd (Addr addr) len flags =
  c_safe_addr_send fd addr len flags >>= errorsFromSize

-- | Send data from an address over a network socket. This uses the unsafe FFI.
--   Users of this function should be sure to set flags that prohibit this
--   from blocking. On Linux this is accomplished with @O_NONBLOCK@. It is
--   often desirable to call 'threadWaitWrite' on a nonblocking socket before
--   calling @unsafeSend@ on it.
unsafeSend ::
     Fd -- ^ Socket
  -> Addr -- ^ Source address
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
unsafeSend fd (Addr addr) len flags =
  c_unsafe_addr_send fd addr len flags >>= errorsFromSize

-- | Send data from a byte array over a network socket. This uses the unsafe FFI;
--   considerations pertaining to 'sendUnsafe' apply to this function as well. Users
--   may specify a length to send fewer bytes than are actually present in the
--   array.
unsafeSendByteArray ::
     Fd -- ^ Socket
  -> ByteArray -- ^ Source byte array
  -> CInt -- ^ Offset into source array
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
unsafeSendByteArray fd (ByteArray b) off len flags =
  c_unsafe_bytearray_send fd b off len flags >>= errorsFromSize

-- | Send data from a mutable byte array over a network socket. This uses the unsafe FFI;
--   considerations pertaining to 'sendUnsafe' apply to this function as well. Users
--   specify an offset and a length to send fewer bytes than are actually present in the
--   array.
unsafeSendMutableByteArray ::
     Fd -- ^ Socket
  -> MutableByteArray RealWorld -- ^ Source mutable byte array
  -> CInt -- ^ Offset into source array
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
unsafeSendMutableByteArray fd (MutableByteArray b) off len flags =
  c_unsafe_mutable_bytearray_send fd b off len flags >>= errorsFromSize

-- | Send data from a byte array over an unconnected network socket.
--   This uses the unsafe FFI; considerations pertaining to 'sendToUnsafe'
--   apply to this function as well. The offset and length arguments
--   cause a slice of the byte array to be sent rather than the entire
--   byte array.
unsafeSendToByteArray :: 
     Fd -- ^ Socket
  -> ByteArray -- ^ Source byte array
  -> CInt -- ^ Offset into source array
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> SocketAddress -- ^ Socket Address
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
unsafeSendToByteArray fd (ByteArray b) off len flags (SocketAddress a@(ByteArray a#)) =
  c_unsafe_bytearray_sendto fd b off len flags a# (intToCInt (PM.sizeofByteArray a)) >>= errorsFromSize

-- | Send data from a mutable byte array over an unconnected network socket.
--   This uses the unsafe FFI; considerations pertaining to 'sendToUnsafe'
--   apply to this function as well. The offset and length arguments
--   cause a slice of the mutable byte array to be sent rather than the entire
--   byte array.
unsafeSendToMutableByteArray :: 
     Fd -- ^ Socket
  -> MutableByteArray RealWorld -- ^ Source byte array
  -> CInt -- ^ Offset into source array
  -> CSize -- ^ Length in bytes
  -> Flags 'Send -- ^ Flags
  -> SocketAddress -- ^ Socket Address
  -> IO (Either Errno CSize) -- ^ Number of bytes pushed to send buffer
unsafeSendToMutableByteArray fd (MutableByteArray b) off len flags (SocketAddress a@(ByteArray a#)) =
  c_unsafe_mutable_bytearray_sendto fd b off len flags a# (intToCInt (PM.sizeofByteArray a)) >>= errorsFromSize

-- | Receive data into an address from a network socket. This wraps @recv@ using
--   the safe interruptible FFI. When the returned size is zero, there are no
--   additional bytes to receive and the peer has performed an orderly shutdown.
receive ::
     Fd -- ^ Socket
  -> Addr -- ^ Source address
  -> CSize -- ^ Length in bytes
  -> Flags 'Receive -- ^ Flags
  -> IO (Either Errno CSize)
receive fd (Addr addr) len flags =
  c_safe_addr_recv fd addr len flags >>= errorsFromSize

-- | Receive data into a byte array from a network socket. This wraps @recv@ using
--   the safe interruptible FFI. When the returned size is zero, there are no
--   additional bytes to receive and the peer has performed an orderly shutdown.
receiveByteArray ::
     Fd -- ^ Socket
  -> CSize -- ^ Length in bytes
  -> Flags 'Receive -- ^ Flags
  -> IO (Either Errno ByteArray)
receiveByteArray fd len flags = do
  m <- PM.newPinnedByteArray (csizeToInt len)
  let !(Addr addr) = PM.mutableByteArrayContents m
  r <- c_safe_addr_recv fd addr len flags
  if r /= (-1)
    then do
      -- Why copy when we could just shrink? We want to always return
      -- byte arrays that are not explicitly pinned.
      let sz = cssizeToInt r
      x <- PM.newByteArray sz
      PM.copyMutableByteArray x 0 m 0 sz
      a <- PM.unsafeFreezeByteArray x
      pure (Right a)
    else fmap Left getErrno

-- | Receive data into an address from a network socket. This wraps @recv@
--   using the unsafe FFI. Users of this function should be sure to set flags
--   that prohibit this from blocking. On Linux this is accomplished by setting
--   the @MSG_DONTWAIT@ flag and handling the resulting @EAGAIN@ or
--   @EWOULDBLOCK@. When the returned size is zero, there are no additional
--   bytes to receive and the peer has performed an orderly shutdown.
unsafeReceive ::
     Fd -- ^ Socket
  -> Addr -- ^ Source address
  -> CSize -- ^ Length in bytes
  -> Flags 'Receive -- ^ Flags
  -> IO (Either Errno CSize)
unsafeReceive fd (Addr addr) len flags =
  c_unsafe_addr_recv fd addr len flags >>= errorsFromSize

-- | Receive data into an address from a network socket. This uses the unsafe
--   FFI; considerations pertaining to 'receiveUnsafe' apply to this function
--   as well. Users may specify a length to receive fewer bytes than are
--   actually present in the mutable byte array.
unsafeReceiveMutableByteArray ::
     Fd -- ^ Socket
  -> MutableByteArray RealWorld -- ^ Destination byte array
  -> CInt -- ^ Destination offset
  -> CSize -- ^ Maximum bytes to receive
  -> Flags 'Receive -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Bytes received into array
unsafeReceiveMutableByteArray fd (MutableByteArray b) off len flags =
  c_unsafe_mutable_byte_array_recv fd b off len flags >>= errorsFromSize

-- | Receive data into an address from an unconnected network socket. This uses the unsafe
--   FFI. Users may specify an offset into the destination byte array.
unsafeReceiveFromMutableByteArray ::
     Fd -- ^ Socket
  -> MutableByteArray RealWorld -- ^ Destination byte array
  -> CInt -- ^ Destination offset
  -> CSize -- ^ Maximum bytes to receive
  -> Flags 'Receive -- ^ Flags
  -> CInt -- ^ Maximum socket address size
  -> IO (Either Errno (SocketAddress,CSize)) -- ^ Remote host, umber of bytes received into array
unsafeReceiveFromMutableByteArray fd (MutableByteArray b) off len flags maxSz = do
  sockAddrBuf@(MutableByteArray sockAddrBuf#) <- PM.newPinnedByteArray (cintToInt maxSz)
  lenBuf@(MutableByteArray lenBuf#) <- PM.newPinnedByteArray (PM.sizeOf (undefined :: CInt))
  PM.writeByteArray lenBuf 0 maxSz
  r <- c_unsafe_mutable_byte_array_recvfrom fd b off len flags sockAddrBuf# lenBuf#
  if r > (-1)
    then do
      (sz :: CInt) <- PM.readByteArray lenBuf 0
      sockAddr <- PM.unsafeFreezeByteArray =<< PM.resizeMutableByteArray sockAddrBuf (cintToInt sz)
      pure (Right (SocketAddress sockAddr,cssizeToCSize r))
    else fmap Left getErrno

-- | Receive data into an address from a network socket. This uses the unsafe
--   FFI. This does not return the socket address of the remote host that
--   sent the packet received.
unsafeReceiveFromMutableByteArray_ ::
     Fd -- ^ Socket
  -> MutableByteArray RealWorld -- ^ Destination byte array
  -> CInt -- ^ Destination offset
  -> CSize -- ^ Maximum bytes to receive
  -> Flags 'Receive -- ^ Flags
  -> IO (Either Errno CSize) -- ^ Number of bytes received into array
unsafeReceiveFromMutableByteArray_ fd (MutableByteArray b) off len flags =
  c_unsafe_mutable_byte_array_ptr_recvfrom fd b off len flags nullPtr nullPtr >>= errorsFromSize

-- | Close a socket. The <http://pubs.opengroup.org/onlinepubs/009696899/functions/close.html POSIX specification>
--   includes more details. This uses the safe interruptible FFI.
close ::
     Fd -- ^ Socket
  -> IO (Either Errno ())
close fd = c_safe_close fd >>= errorsFromInt

-- | Close a socket. This uses the unsafe FFI. According to the
--   <http://pubs.opengroup.org/onlinepubs/009696899/functions/close.html POSIX specification>,
--   "If @fildes@ refers to a socket, @close()@ shall cause the socket to
--   be destroyed. If the socket is in connection-mode, and the @SO_LINGER@
--   option is set for the socket with non-zero linger time, and the socket
--   has untransmitted data, then @close()@ shall block for up to the current
--   linger interval until all data is transmitted."
unsafeClose ::
     Fd -- ^ Socket
  -> IO (Either Errno ())
unsafeClose fd = c_unsafe_close fd >>= errorsFromInt

errorsFromSize :: CSsize -> IO (Either Errno CSize)
errorsFromSize r = if r > (-1)
  then pure (Right (cssizeToCSize r))
  else fmap Left getErrno

errorsFromFd :: Fd -> IO (Either Errno Fd)
errorsFromFd r = if r > (-1)
  then pure (Right r)
  else fmap Left getErrno

-- Sometimes, functions that return an int use zero to indicate
-- success and negative one to indicate failure without including
-- additional information in the value.
errorsFromInt :: CInt -> IO (Either Errno ())
errorsFromInt r = if r == 0
  then pure (Right ())
  else fmap Left getErrno

intToCInt :: Int -> CInt
intToCInt = fromIntegral

cintToInt :: CInt -> Int
cintToInt = fromIntegral

csizeToInt :: CSize -> Int
csizeToInt = fromIntegral

cssizeToInt :: CSsize -> Int
cssizeToInt = fromIntegral

-- only call this when it is known that the argument is non-negative
cssizeToCSize :: CSsize -> CSize
cssizeToCSize = fromIntegral

-- touchByteArray :: ByteArray -> IO ()
-- touchByteArray (ByteArray x) = touchByteArray# x
-- 
-- touchByteArray# :: ByteArray# -> IO ()
-- touchByteArray# x = IO $ \s -> case touch# x s of s' -> (# s', () #)