packages feed

sockets-0.6.1.0: src-stream-receive/SequencedPacket/Receive/Indefinite.hs

{-# language BangPatterns #-}
{-# language LambdaCase #-}
{-# language MultiWayIf #-}

module SequencedPacket.Receive.Indefinite
  ( receive
  , receiveAttempt
  ) where

import Control.Concurrent.STM (TVar)
import Foreign.C.Error (Errno(..), eAGAIN, eWOULDBLOCK, eCONNRESET)
import Foreign.C.Types (CSize)
import Socket.Error (die)
import Socket.EventManager (Token)
import Socket.SequencedPacket (ReceiveException(..))
import Socket.Buffer (Buffer)
import Socket.Interrupt (Interrupt,Intr,wait,tokenToSequencedPacketReceiveException)
import System.Posix.Types (Fd)

import qualified Foreign.C.Error.Describe as D
import qualified Socket.EventManager as EM
import qualified Socket.Buffer as Buffer
import qualified Stream.Receive as Receive
import qualified Linux.Socket as L

receive ::
     Interrupt
  -> Fd
  -> Buffer
  -> IO (Either (ReceiveException Intr) Int)
receive !intr !sock !buf = do
  let !mngr = EM.manager
  tv <- EM.reader mngr sock
  token0 <- wait intr tv
  case tokenToSequencedPacketReceiveException token0 of
    Left err -> pure (Left err)
    Right _ -> receiveLoop intr tv token0 sock buf

receiveAttempt ::
     Fd -- ^ Socket
  -> Buffer -- ^ Buffer
  -> IO (Either (ReceiveException Intr) (Maybe Int))
{-# inline receiveAttempt #-}
receiveAttempt !fd !buf = do
  e <- Receive.receiveOnce fd buf
  case e of
    Left err ->
      if | err == eWOULDBLOCK || err == eAGAIN -> pure (Right Nothing)
         | err == eCONNRESET -> pure (Left ReceiveReset)
         | otherwise -> die $ concat
             [ "Socket.SequencedPacket.receive: " 
             , describeErrorCode err
             ]
    Right recvSz -> do
      let !recvSzInt = csizeToInt recvSz
      -- TODO: This is sort of a hack, but it mostly works. We should
      -- use MSG_TRUNC, but I am too lazy to fix this at the moment.
      if recvSzInt == Buffer.length buf
        then pure (Left ReceiveTruncated)
        else pure (Right (Just recvSzInt))

receiveLoop ::
     Interrupt
  -> TVar Token
  -> Token
  -> Fd -- ^ Socket
  -> Buffer -- ^ Buffer
  -> IO (Either (ReceiveException Intr) Int)
receiveLoop !intr !tv !token0 !fd !buf = receiveAttempt fd buf >>= \case
  Left err -> pure (Left err)
  Right m -> case m of
    Nothing -> do
      EM.unready token0 tv
      token1 <- wait intr tv
      case tokenToSequencedPacketReceiveException token1 of
        Left err -> pure (Left err)
        Right _ -> receiveLoop intr tv token1 fd buf
    Just !r -> pure (Right r)

csizeToInt :: CSize -> Int
csizeToInt = fromIntegral

describeErrorCode :: Errno -> String
describeErrorCode err@(Errno e) = "error code " ++ D.string err ++ " (" ++ show e ++ ")"