{-# LINE 1 "src/Network/Socket/Splice/Internal.hsc" #-}
-- | Implementation.
{-# LINE 2 "src/Network/Socket/Splice/Internal.hsc" #-}


{-# LINE 4 "src/Network/Socket/Splice/Internal.hsc" #-}

{-# LINE 5 "src/Network/Socket/Splice/Internal.hsc" #-}

{-# LINE 6 "src/Network/Socket/Splice/Internal.hsc" #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}


module Network.Socket.Splice.Internal (

  -- * Cross-platform API for Socket to Socket Data Transfer Loops
  {- | 'splice' is the cross-platform API for continous, uni-directional
       data transfer between two network sockets.

       It is an /infinite loop/ that is intended to be used with
       'Control.Concurrent.forkIO':

       > void . forkIO . try_ $ splice 1024 sourceSocket targetSocket
       > void . forkIO . try_ $ splice 1024 targetSocket sourceSocket
  -}

    splice
  , ChunkSize
  , zeroCopy

  -- * Combinators for Exception Handling
  , try_

  ) where


import Data.Word
import Foreign.Ptr

import Network.Socket
import Control.Monad
import Control.Exception


{-# LINE 41 "src/Network/Socket/Splice/Internal.hsc" #-}
import Data.Int
import Data.Bits
import System.Posix.IO
import Unsafe.Coerce
import Foreign.C.Error
import System.Posix.Types
import System.Posix.Internals
import qualified System.IO.Splice.Linux as L

{-# LINE 54 "src/Network/Socket/Splice/Internal.hsc" #-}


--------------------------------------------------------------------------------


-- | Indicates whether 'splice' uses zero-copy system calls or the portable user 
--   space Haskell implementation.
zeroCopy :: Bool -- ^ @True@ if 'splice' uses zero-copy system calls;
                 --   otherwise, false.
zeroCopy =

{-# LINE 65 "src/Network/Socket/Splice/Internal.hsc" #-}
  True

{-# LINE 69 "src/Network/Socket/Splice/Internal.hsc" #-}


-- | The numeric type to recommend chunk sizes for moving data between sockets
--   used by both zero-copy and portable implementations of 'splice'.
type ChunkSize =

{-# LINE 75 "src/Network/Socket/Splice/Internal.hsc" #-}
  L.ChunkSize

{-# LINE 79 "src/Network/Socket/Splice/Internal.hsc" #-}


-- | Pipes data from one socket to another in an /infinite loop/.
--
--   On Linux this uses the @splice()@ system call and eliminates copying
--   between kernel and user address spaces.
--
--   On other operating systems, a portable Haskell implementation utilizes a
--   user space buffer.
splice
  :: ChunkSize -- ^ chunk size.
  -> Socket    -- ^ source socket.
  -> Socket    -- ^ target socket.
  -> IO ()     -- ^ infinite loop.
splice len sIn sOut = do

  let throwRecv0 = error "Network.Socket.Splice.splice ended"

  let fdIn  = fdSocket sIn
  let fdOut = fdSocket sOut 


{-# LINE 101 "src/Network/Socket/Splice/Internal.hsc" #-}

  (r,w) <- createPipe     -- r,w: read / write ends of pipe
  let s = Fd fdIn         -- s  : source socket
  let t = Fd fdOut        -- t  : target socket
  let n = nullPtr  
  let u = unsafeCoerce :: (Int32) -> (Word32)
{-# LINE 107 "src/Network/Socket/Splice/Internal.hsc" #-}
  let check = throwErrnoIfMinus1 "Network.Socket.Splice.splice"
  let flags = L.sPLICE_F_MOVE .|. L.sPLICE_F_MORE
  let setNonBlockingMode v = do setNonBlockingFD fdIn  v
                                setNonBlockingFD fdOut v

  setNonBlockingMode False
  finally
    (forever $ do 
       bytes <- check $ L.c_splice s n w n    len    flags
       if bytes > 0
         then           L.c_splice r n t n (u bytes) flags
         else           throwRecv0)
    (do closeFd r
        closeFd w
        try_ $ setNonBlockingMode True)


{-# LINE 142 "src/Network/Socket/Splice/Internal.hsc" #-}


-- | Similar to 'Control.Exception.Base.try' but used when an obvious exception
--   is expected which can be safely ignored.
try_
  :: IO () -- ^ action to run which can throw /any/ exception.
  -> IO () -- ^ new action where exceptions are silenced.
try_ a = (try a :: IO (Either SomeException ())) >> return ()