git.iko.soy/distributed-process
on master help sign in
TCP layer passes unit tests
dfcf360Edsko de Vries
network-transport-2/network-transport.cabal+4−4
@@ -14,7 +14,8 @@ Library
14 data-lens,
14 data-lens,
15 network,
15 network,
16 regex-applicative,
16 regex-applicative,
17− mtl
17+ mtl,
18+ transformers
18 Exposed-modules: Network.Transport,
19 Exposed-modules: Network.Transport,
19 Network.Transport.Chan
20 Network.Transport.Chan
20 extensions: EmptyDataDecls, ForeignFunctionInterface
21 extensions: EmptyDataDecls, ForeignFunctionInterface
@@ -30,7 +31,8 @@ Test-Suite TestTCP
30 data-lens,
31 data-lens,
31 network,
32 network,
32 regex-applicative,
33 regex-applicative,
33− mtl
34+ mtl,
35+ transformers
34 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface
36 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface
35 ghc-options: -Wall -fno-warn-unused-do-bind
37 ghc-options: -Wall -fno-warn-unused-do-bind
36 HS-Source-Dirs: tests src
38 HS-Source-Dirs: tests src
@@ -42,8 +44,6 @@ Test-Suite TestMulticastInMemory
42 containers,
44 containers,
43 bytestring,
45 bytestring,
44 data-lens,
46 data-lens,
45− network,
46− regex-applicative,
47 mtl
47 mtl
48 extensions: EmptyDataDecls, OverloadedStrings
48 extensions: EmptyDataDecls, OverloadedStrings
49 ghc-options: -Wall -fno-warn-unused-do-bind
49 ghc-options: -Wall -fno-warn-unused-do-bind
network-transport-2/src/Network/Transport/Internal.hs+12−6
@@ -13,8 +13,12 @@ import Foreign.Storable (pokeByteOff, peekByteOff)
13import Foreign.C (CInt(..), CShort(..))
13import Foreign.C (CInt(..), CShort(..))
14import Foreign.ForeignPtr (withForeignPtr)
14import Foreign.ForeignPtr (withForeignPtr)
15import Data.ByteString (ByteString)
15import Data.ByteString (ByteString)
16+import qualified Data.ByteString as BS (length)
16import qualified Data.ByteString.Internal as BSI (create, toForeignPtr)
17import qualified Data.ByteString.Internal as BSI (create, toForeignPtr)
18+import Control.Monad (mzero)
19+import Control.Monad.IO.Class (liftIO)
17import Control.Monad.Error (ErrorT, Error, throwError)
20import Control.Monad.Error (ErrorT, Error, throwError)
21+import Control.Monad.Trans.Maybe (MaybeT)
18
22
19foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
23foreign import ccall unsafe "htonl" htonl :: CInt -> CInt
20foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
24foreign import ccall unsafe "ntohl" ntohl :: CInt -> CInt
@@ -28,9 +32,10 @@ encodeInt32 i32 =
28 pokeByteOff p 0 (htonl (fromIntegral i32))
32 pokeByteOff p 0 (htonl (fromIntegral i32))
29
33
30-- | Deserialize 32-bit from network byte order
34-- | Deserialize 32-bit from network byte order
31−decodeInt32 :: ByteString -> IO Int32
35+decodeInt32 :: ByteString -> MaybeT IO Int32
32−decodeInt32 bs =
36+decodeInt32 bs | BS.length bs /= 4 = mzero
37+decodeInt32 bs = liftIO $ do
33− let (fp, _, _) = BSI.toForeignPtr bs in
38+ let (fp, _, _) = BSI.toForeignPtr bs
34 withForeignPtr fp $ \p -> do
39 withForeignPtr fp $ \p -> do
35 w32 <- peekByteOff p 0
40 w32 <- peekByteOff p 0
36 return (fromIntegral (ntohl w32))
41 return (fromIntegral (ntohl w32))
@@ -42,9 +47,10 @@ encodeInt16 i16 =
42 pokeByteOff p 0 (htons (fromIntegral i16))
47 pokeByteOff p 0 (htons (fromIntegral i16))
43
48
44-- | Deserialize 16-bit from network byte order
49-- | Deserialize 16-bit from network byte order
45−decodeInt16 :: ByteString -> IO Int16
50+decodeInt16 :: ByteString -> MaybeT IO Int16
46−decodeInt16 bs =
51+decodeInt16 bs | BS.length bs /= 2 = mzero
52+decodeInt16 bs = liftIO $ do
47− let (fp, _, _) = BSI.toForeignPtr bs in
53+ let (fp, _, _) = BSI.toForeignPtr bs
48 withForeignPtr fp $ \p -> do
54 withForeignPtr fp $ \p -> do
49 w16 <- peekByteOff p 0
55 w16 <- peekByteOff p 0
50 return (fromIntegral (ntohs w16))
56 return (fromIntegral (ntohs w16))
network-transport-2/src/Network/Transport/TCP.hs+54−31
@@ -26,20 +26,22 @@ import qualified Network.Socket as N ( HostName
26 , sClose
26 , sClose
27 , accept
27 , accept
28 )
28 )
29−import qualified Network.Socket.ByteString as NSB (sendMany, recv)
29+import qualified Network.Socket.ByteString as NBS (sendMany, recv)
30import Control.Concurrent (forkIO, ThreadId)
30import Control.Concurrent (forkIO, ThreadId)
31import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
31import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
32import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, readMVar)
32import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, readMVar)
33import Control.Category ((>>>))
33import Control.Category ((>>>))
34import Control.Applicative ((<*>), (*>), (<$>), pure)
34import Control.Applicative ((<*>), (*>), (<$>), pure)
35−import Control.Monad (forever)
35+import Control.Monad (forever, mzero)
36import Control.Monad.Error (ErrorT, liftIO, runErrorT)
36import Control.Monad.Error (ErrorT, liftIO, runErrorT)
37+import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
37import Data.ByteString (ByteString)
38import Data.ByteString (ByteString)
38−import qualified Data.ByteString as BS (length, concat)
39+import qualified Data.ByteString as BS (length, concat, null)
39import qualified Data.ByteString.Char8 as BSC (pack, unpack)
40import qualified Data.ByteString.Char8 as BSC (pack, unpack)
40import Data.IntMap (IntMap)
41import Data.IntMap (IntMap)
41import qualified Data.IntMap as IntMap (empty, size)
42import qualified Data.IntMap as IntMap (empty, size)
42import Data.Lens.Lazy (Lens, lens, intMapLens, (^.), (^=), (^+=))
43import Data.Lens.Lazy (Lens, lens, intMapLens, (^.), (^=), (^+=))
44+import Data.Int (Int32)
43import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
45import Text.Regex.Applicative (RE, few, anySym, sym, many, match)
44
46
45data TransportState = TransportState { _endPoints :: IntMap (Chan Event, MVar EndPointState) }
47data TransportState = TransportState { _endPoints :: IntMap (Chan Event, MVar EndPointState) }
@@ -91,28 +93,31 @@ forkServer host port server = do
91transportServer :: MVar TransportState -> N.Socket -> IO ()
93transportServer :: MVar TransportState -> N.Socket -> IO ()
92transportServer state sock = forever $ do
94transportServer state sock = forever $ do
93 (clientSock, _) <- N.accept sock
95 (clientSock, _) <- N.accept sock
94− forkIO $ do
96+ forkIO $ handleClient state clientSock >> N.sClose clientSock
97+
98+-- | Handle a new incoming connection
99+--
100+-- Returns when the client closes the socket and on invalid input.
101+handleClient :: MVar TransportState -> N.Socket -> IO ()
102+handleClient state sock = do
103+ runMaybeT $ do
95 -- Our endpoint the other side is interested in
104 -- Our endpoint the other side is interested in
96− endPointBS <- NSB.recv clientSock 2
105+ endPointBS <- recvExact sock 2
97− endPointIx <- decodeInt16 endPointBS
106+ endPointIx <- decodeInt16 (BS.concat $ endPointBS)
98 -- Full endpoint address of the other side
107 -- Full endpoint address of the other side
99− theirAddress <- BS.concat <$> recvWithLength clientSock
108+ theirAddress <- BS.concat <$> recvWithLength sock
100 -- Start handling incoming messages
109 -- Start handling incoming messages
101− endPointMb <- (^. endPointAt (fromIntegral endPointIx)) <$> readMVar state
110+ (endPointCh, endPointSt) <- MaybeT $ (^. endPointAt (fromIntegral endPointIx)) <$> readMVar state
102− case endPointMb of
111+ endpointServer sock (EndPointAddress theirAddress) endPointCh endPointSt
103− Just (endPointCh, endPointSt) -> do
112+ return ()
104− endpointServer clientSock (EndPointAddress theirAddress) endPointCh endPointSt
113+
105− Nothing -> -- TODO: should we do more than just close the socket?
106− N.sClose clientSock
107−
108−-- | Handle requests for one endpoint
114+-- | Handle requests for one endpoint. Returns only if an error occurs
109−endpointServer :: N.Socket -> EndPointAddress -> Chan Event -> MVar EndPointState -> IO ()
115+endpointServer :: N.Socket -> EndPointAddress -> Chan Event -> MVar EndPointState -> MaybeT IO ()
110−endpointServer sock theirAddress chan state = forever $ do
116+endpointServer sock theirAddress chan state = forever $ do
111− -- Get connection number
112− connBS <- NSB.recv sock 2
117+ connBS <- recvExact sock 2
113− connIx <- fromIntegral <$> decodeInt16 connBS
118+ connIx <- fromIntegral <$> decodeInt16 (BS.concat connBS)
114 if connIx == 0
119 if connIx == 0
115− then createNewConnection
120+ then liftIO $ createNewConnection
116 else readMessage connIx
121 else readMessage connIx
117 where
122 where
118 -- Create a new connection
123 -- Create a new connection
@@ -120,14 +125,14 @@ endpointServer sock theirAddress chan state = forever $ do
120 createNewConnection = do
125 createNewConnection = do
121 newIx <- modifyMVar state $ \st -> return (nextConnectionIx ^+= 1 $ st, st ^. nextConnectionIx)
126 newIx <- modifyMVar state $ \st -> return (nextConnectionIx ^+= 1 $ st, st ^. nextConnectionIx)
122 newBs <- encodeInt16 (fromIntegral newIx)
127 newBs <- encodeInt16 (fromIntegral newIx)
123− NSB.sendMany sock [newBs]
128+ NBS.sendMany sock [newBs]
124 writeChan chan (ConnectionOpened newIx ReliableOrdered theirAddress)
129 writeChan chan (ConnectionOpened newIx ReliableOrdered theirAddress)
125
130
126 -- Read a message and output it on the endPoint's channel
131 -- Read a message and output it on the endPoint's channel
127− readMessage :: Int -> IO ()
132+ readMessage :: Int -> MaybeT IO ()
128 readMessage connIx = do
133 readMessage connIx = do
129 payload <- recvWithLength sock
134 payload <- recvWithLength sock
130− writeChan chan (Received connIx payload)
135+ liftIO $ writeChan chan (Received connIx payload)
131
136
132-- | Create a new endpoint
137-- | Create a new endpoint
133tcpNewEndPoint :: MVar TransportState -> N.HostName -> N.ServiceName -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
138tcpNewEndPoint :: MVar TransportState -> N.HostName -> N.ServiceName -> IO (Either (FailedWith NewEndPointErrorCode) EndPoint)
@@ -156,8 +161,8 @@ tcpConnect myAddress theirAddress _ = runErrorT $ do
156-- | Request a new connection
161-- | Request a new connection
157requestNewConnection :: N.Socket -> IO ByteString
162requestNewConnection :: N.Socket -> IO ByteString
158requestNewConnection sock = do
163requestNewConnection sock = do
159− pure <$> encodeInt16 0 >>= NSB.sendMany sock
164+ pure <$> encodeInt16 0 >>= NBS.sendMany sock
160− NSB.recv sock 2
165+ NBS.recv sock 2
161
166
162-- | Find a socket to the specified endpoint
167-- | Find a socket to the specified endpoint
163--
168--
@@ -191,15 +196,14 @@ sendWithLength :: N.Socket -- ^ Socket to send on
191sendWithLength sock header payload = do
196sendWithLength sock header payload = do
192 lengthBs <- encodeInt32 (fromIntegral . sum . map BS.length $ payload)
197 lengthBs <- encodeInt32 (fromIntegral . sum . map BS.length $ payload)
193 let msg = maybe id (:) header $ lengthBs : payload
198 let msg = maybe id (:) header $ lengthBs : payload
194− NSB.sendMany sock msg
199+ NBS.sendMany sock msg
195
200
196-- | Read a length and then a payload of that length
201-- | Read a length and then a payload of that length
197-- TODO: error handling
202-- TODO: error handling
198−recvWithLength :: N.Socket -> IO [ByteString]
203+recvWithLength :: N.Socket -> MaybeT IO [ByteString]
199recvWithLength sock = do
204recvWithLength sock = do
200− msgLengthBs <- NSB.recv sock 4
205+ msgLengthBs <- recvExact sock 4
201− msgLength <- fromIntegral <$> decodeInt32 msgLengthBs
206+ decodeInt32 (BS.concat msgLengthBs) >>= recvExact sock
202− pure <$> NSB.recv sock msgLength
203
207
204-- | Decode end point address
208-- | Decode end point address
205-- TODO: This uses regular expression parsing, which is nice, but unnecessary
209-- TODO: This uses regular expression parsing, which is nice, but unnecessary
@@ -208,3 +212,22 @@ decodeEndPointAddress bs = match endPointAddressRE (BSC.unpack bs)
208 where
212 where
209 endPointAddressRE :: RE Char (N.HostName, N.ServiceName, Int)
213 endPointAddressRE :: RE Char (N.HostName, N.ServiceName, Int)
210 endPointAddressRE = (,,) <$> few anySym <*> (sym ':' *> few anySym) <*> (sym ':' *> (read <$> many anySym))
214 endPointAddressRE = (,,) <$> few anySym <*> (sym ':' *> few anySym) <*> (sym ':' *> (read <$> many anySym))
215+
216+-- | Read an exact number of bytes from a socket
217+--
218+-- Returns 'Nothing' if the socket closes prematurely.
219+recvExact :: N.Socket -- ^ Socket to read from
220+ -> Int32 -- ^ Number of bytes to read
221+ -> MaybeT IO [ByteString]
222+recvExact sock len = do
223+ (socketClosed, input) <- liftIO $ go [] len
224+ if socketClosed then mzero else return input
225+ where
226+ -- Returns input read and whether the socket closed prematurely
227+ go :: [ByteString] -> Int32 -> IO (Bool, [ByteString])
228+ go acc 0 = return (False, reverse acc)
229+ go acc l = do
230+ bs <- NBS.recv sock (fromIntegral l `min` 4096)
231+ if BS.null bs
232+ then return (True, reverse acc)
233+ else go (bs : acc) (l - (fromIntegral $ BS.length bs))
network-transport-2/tests/TestTransport.hs+6−3
@@ -14,9 +14,12 @@ type Test = IO Bool
14
14
15-- Logging (for debugging)
15-- Logging (for debugging)
16tlog :: String -> IO ()
16tlog :: String -> IO ()
17+tlog msg = return ()
18+{-
17tlog msg = do
19tlog msg = do
18 tid <- myThreadId
20 tid <- myThreadId
19 putStrLn $ show tid ++ ": " ++ msg
21 putStrLn $ show tid ++ ": " ++ msg
22+-}
20
23
21-- Server that echoes messages straight back to the origin endpoint.
24-- Server that echoes messages straight back to the origin endpoint.
22echoServer :: EndPoint -> IO ()
25echoServer :: EndPoint -> IO ()
@@ -152,7 +155,7 @@ testConnections transport numPings = do
152testTransport :: Transport -> IO Bool
155testTransport :: Transport -> IO Bool
153testTransport transport = do
156testTransport transport = do
154 tlog "Starting transport tests"
157 tlog "Starting transport tests"
155− foldl (liftM2 (&&)) (return True) [ testPingPong transport 1
158+ foldl (liftM2 (&&)) (return True) [ testPingPong transport 10000
156− , testEndPoints transport 1
159+ , testEndPoints transport 10000
157− , testConnections transport 1
160+ , testConnections transport 10000
158 ]
161 ]