hans-2.1.0.0: src/Hans/Layer/Tcp/Socket.hs
{-# LANGUAGE DeriveDataTypeable #-}
module Hans.Layer.Tcp.Socket (
-- * Socket Layer
Socket()
, SocketError(..)
, listenPort
, acceptSocket
, connect
, sendSocket
, closeSocket
, readBytes
, readLine
) where
import Hans.Address.IP4
import Hans.Channel
import Hans.Layer
import Hans.Layer.Tcp.Monad
import Hans.Message.Tcp (TcpPort(..))
import Network.TCP.LTS.User (tcp_process_user_request)
import Network.TCP.Type.Base
(IPAddr(..),SocketID,TCPAddr(..))
import Network.TCP.Type.Syscall (SockReq(..),SockRsp(..))
import Control.Exception (throwIO,Exception)
import Control.Concurrent (MVar,newMVar,newEmptyMVar,takeMVar,putMVar)
import Data.Typeable (Typeable)
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
-- Socket Layer ----------------------------------------------------------------
data Socket = Socket
{ socketTcpHandle :: TcpHandle
, socketId :: !SocketID
, socketBuffer :: MVar L.ByteString
}
data SocketResult a
= SocketResult a
| SocketError SocketError
data SocketError
= ListenError String
| AcceptError String
| ConnectError String
| SendError String
| RecvError String
| CloseError String
deriving (Typeable,Show)
instance Exception SocketError
-- | Block on a socket operation, waiting for the TCP layer to finish an action.
blockResult :: TcpHandle -> (MVar (SocketResult a) -> Tcp ()) -> IO a
blockResult tcp k = do
var <- newEmptyMVar
send tcp (k var)
sr <- takeMVar var
case sr of
SocketResult a -> return a
SocketError se -> throwIO se
-- | Call @output@ if the @Tcp@ action returns a @Just@.
maybeOutput :: Tcp (Maybe (IO ())) -> Tcp ()
maybeOutput body = do
mb <- body
case mb of
Just m -> output m
Nothing -> return ()
-- | Listen on a port.
listenPort :: TcpHandle -> TcpPort -> IO Socket
listenPort tcp (TcpPort port) = blockResult tcp $ \ res -> do
let mkError = SocketError . ListenError
k rsp = case rsp of
SockNew sid -> do
buf <- newMVar L.empty
putMVar res (SocketResult (Socket tcp sid buf))
SockError err -> putMVar res (mkError err)
_ -> putMVar res (mkError "Unexpected response")
maybeOutput (tcp_process_user_request (SockListen port,k))
-- | Accept a client connection on a @Socket@.
acceptSocket :: Socket -> IO Socket
acceptSocket sock = blockResult (socketTcpHandle sock) $ \ res -> do
let mkError = SocketError . AcceptError
k rsp = case rsp of
SockNew sid -> do
buf <- newMVar L.empty
putMVar res (SocketResult (Socket (socketTcpHandle sock) sid buf))
SockError err -> putMVar res (mkError err)
_ -> putMVar res (mkError "Unexpected response")
maybeOutput (tcp_process_user_request (SockAccept (socketId sock),k))
-- | Connect to a remote server.
connect :: TcpHandle -> IP4 -> IP4 -> TcpPort -> IO Socket
connect tcp src dst (TcpPort port) = blockResult tcp $ \ res -> do
let us = IPAddr (convertToWord32 src)
them = TCPAddr (IPAddr (convertToWord32 dst), port)
mkError = SocketError . ConnectError
k rsp = case rsp of
SockNew sid -> do
buf <- newMVar L.empty
putMVar res (SocketResult (Socket tcp sid buf))
SockError err -> putMVar res (mkError err)
_ -> putMVar res (mkError "Unexpected response")
maybeOutput (tcp_process_user_request (SockConnect us them,k))
-- | Send on a @Socket@.
sendSocket :: Socket -> S.ByteString -> IO ()
sendSocket sock bytes = blockResult (socketTcpHandle sock) $ \ res -> do
let mkError = SocketError . SendError
k rsp = putMVar res $! case rsp of
SockOK -> SocketResult ()
SockError err -> mkError err
_ -> mkError "Unexpected response"
maybeOutput (tcp_process_user_request (SockSend (socketId sock) bytes,k))
-- | Receive from a @Socket@.
recvSocket :: Socket -> IO S.ByteString
recvSocket sock = blockResult (socketTcpHandle sock) $ \ res -> do
let mkError = SocketError . RecvError
k rsp = putMVar res $! case rsp of
SockData bs -> SocketResult bs
SockError err -> mkError err
_ -> mkError "Unexpected response"
maybeOutput (tcp_process_user_request (SockRecv (socketId sock),k))
-- | Close a socket.
closeSocket :: Socket -> IO ()
closeSocket sock =
blockResult (socketTcpHandle sock) $ \ res -> do
let mkError = SocketError . CloseError
k rsp = putMVar res $! case rsp of
SockOK -> SocketResult ()
SockError err -> mkError err
_ -> mkError "Unexpected response"
maybeOutput (tcp_process_user_request (SockClose (socketId sock),k))
-- Derived Interaction ---------------------------------------------------------
-- | Read n bytes from a @Socket@.
readBytes :: Socket -> Int -> IO S.ByteString
readBytes sock goal = do
buf <- takeMVar (socketBuffer sock)
loop buf (fromIntegral (L.length buf))
where
loop buf len
| goal <= len = finish buf
| otherwise = do
bytes <- recvSocket sock
if S.null bytes
then finish buf
else loop (buf `L.append` L.fromChunks [bytes]) (len + S.length bytes)
finish buf = do
let (as,bs) = L.splitAt (fromIntegral goal) buf
putMVar (socketBuffer sock) bs
return (S.concat (L.toChunks as))
-- | Read until a CRLF, LF or CR are read.
readLine :: Socket -> IO S.ByteString
readLine sock = do
buf <- takeMVar (socketBuffer sock)
loop False 0 buf
where
loop cr ix buf
| L.null buf = fillBuffer cr ix buf
| otherwise =
case L.index buf ix of
0x0d -> loop True (ix+1) buf
0x0a -> finish (ix+1) buf
_ | cr -> finish ix buf
| otherwise -> loop False (ix+1) buf
fillBuffer cr ix buf = do
bytes <- recvSocket sock
if S.null bytes
then finish ix buf
else loop cr ix (buf `L.append` L.fromChunks [bytes])
finish ix buf = do
let (as,bs) = L.splitAt ix buf
putMVar (socketBuffer sock) bs
return (S.concat (L.toChunks as))