@@ -3,6 +3,7 @@
33-- TODOs:
44-- * Various errors are still left as "undefined"
55-- * Many possible errors are not yet dealt with
6+
67module Network.Transport.TCP ( -- * Main API
78 createTransport
89 , -- TCP specific functionality
@@ -12,38 +13,55 @@ module Network.Transport.TCP ( -- * Main API
1213
1314import Network.Transport
1415import Network.Transport.Internal.TCP ( forkServer
15− , connectTo
1616 , recvWithLength
17− , sendWithLength
18− , recvInt16
17+ , sendMany
1918 , recvInt32
20− , sendInt32
2119 )
22−import Network.Transport.Internal ( encodeInt16
23− , encodeInt32
20+import Network.Transport.Internal ( encodeInt32
21+ , decodeInt32
22+ , prependLength
2423 , failWith
25− , failWithT
24+ , failWithIO
2625 )
2726import qualified Network.Socket as N ( HostName
2827 , ServiceName
2928 , Socket
3029 , sClose
3130 , accept
31+ , getAddrInfo
32+ , socket
33+ , addrFamily
34+ , addrAddress
35+ , SocketType(Stream)
36+ , defaultProtocol
37+ , setSocketOption
38+ , SocketOption(ReuseAddr)
39+ , connect
3240 )
3341import Control.Concurrent (forkIO)
3442import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
35−import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, modifyMVar_, readMVar, takeMVar, putMVar)
43+import Control.Concurrent.MVar ( MVar
44+ , newMVar
45+ , modifyMVar
46+ , modifyMVar_
47+ , readMVar
48+ , takeMVar
49+ , putMVar
50+ , newEmptyMVar
51+ )
3652import Control.Category ((>>>))
3753import Control.Applicative ((<*>), (*>), (<$>))
3854import Control.Monad (forever, forM_, void)
39−import Control.Monad.Error (ErrorT, liftIO, runErrorT)
55+import Control.Monad.Error (MonadError, ErrorT, liftIO, runErrorT, throwError)
4056import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT)
4157import Control.Monad.Trans.Writer (WriterT, execWriterT)
4258import Control.Monad.Writer.Class (tell)
4359import Control.Monad.IO.Class (MonadIO)
60+import Control.Exception (IOException)
61+import Data.ByteString (ByteString)
4462import qualified Data.ByteString as BS (concat)
4563import qualified Data.ByteString.Char8 as BSC (pack, unpack)
46−import Data.Int (Int16)
64+import Data.Int (Int32)
4765import Data.IntMap (IntMap)
4866import qualified Data.IntMap as IntMap (empty, size)
4967import Data.Map (Map)
@@ -64,15 +82,14 @@ data LocalEndPoint = LocalEndPoint { _localAddress :: EndPointAddress
6482 }
6583data RemoteEndPoint = RemoteEndPoint { _remoteAddress :: EndPointAddress
6684 , _remoteSocket :: N.Socket
67− , _remoteConnectionIds :: Chan ConnectionId
6885 , _remoteLock :: MVar ()
6986 }
7087data EndPointState = EndPointState { _nextConnectionId :: ConnectionId
88+ , _pendingCtrlRequests :: IntMap (MVar [ByteString])
89+ , _nextCtrlRequestId :: ControlRequestId
7190 }
72−type EndPointId = Int16
73−
74−#define REQUEST_CONNECTION_ID 0
75−#define RESPOND_CONNECTION_ID 1
91+type EndPointId = Int32
92+type ControlRequestId = Int32
7693
7794-- | Create a TCP transport
7895--
@@ -81,10 +98,8 @@ type EndPointId = Int16
8198-- connects to A two socket pairs will be created that will both be used
8299-- unidirectionally, at degraded performance).
83100--
84−-- TODOs:
85−-- * Perhaps we should allow to use something different other than 'defaultHints'
86−-- * Deal with all exceptions that may occur
87−createTransport :: N.HostName -> N.ServiceName -> IO Transport
101+-- TODOs: deal with hints
102+createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
88103createTransport host port = do
89104 state <- newMVar TransportState { _localEndPoints = IntMap.empty
90105 , _remoteEndPoints = Map.empty
@@ -93,8 +108,9 @@ createTransport host port = do
93108 , _transportHost = host
94109 , _transportPort = port
95110 }
96− forkServer host port (transportServer transport)
97− return Transport { newEndPoint = tcpNewEndPoint transport }
111+ runErrorT $ do
112+ forkServer host port (transportServer transport)
113+ return Transport { newEndPoint = tcpNewEndPoint transport }
98114
99115-- | The transport server handles incoming connections for all endpoints
100116--
@@ -104,39 +120,50 @@ transportServer transport sock = forever $ do
104120 (clientSock, _) <- N.accept sock
105121 handleConnectionRequest transport clientSock
106122
123+-- | Set up a new TCP connection (at the request of a remote endpoint)
107124handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
108125handleConnectionRequest transport sock = do
109126 request <- runMaybeT $ do
110− ourEndPointId <- recvInt16 sock
127+ ourEndPointId <- recvInt32 sock
111128 theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
112129 ourEndPoint <- MaybeT $ (^. localEndPointAt ourEndPointId) <$> readMVar (transport ^. transportState)
113130 return (ourEndPoint, theirAddress)
114131 case request of
115132 Just (ourEndPoint, theirAddress) ->
133+ -- We ignore the return value of 'forkRemoteEndPoint' here because _they_ are
134+ -- connecting to _us_. 'forkRemoteEndPoint' will have stored the remote endpoint
135+ -- in the transport state, however, in case _we_ later connect to _them_.
116136 void $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
117137 Nothing ->
118138 return ()
119139
140+-- | Set up a new remote endpoint (either because we connected to a
141+-- new remote endpoint, or because a remote endpoint connected to us)
120142forkRemoteEndPoint :: TCPTransport
121143 -> LocalEndPoint
122144 -> EndPointAddress
123145 -> N.Socket
124146 -> IO RemoteEndPoint
125−forkRemoteEndPoint transport ourEndPoint theirAddress theirSock = do
126− theirConnectionIds <- newChan
127− theirLock <- newMVar ()
128− let theirEndPoint = RemoteEndPoint { _remoteAddress = theirAddress
129− , _remoteSocket = theirSock
130− , _remoteConnectionIds = theirConnectionIds
131− , _remoteLock = theirLock
147+forkRemoteEndPoint transport ourEndPoint theirAddress sock = do
148+ lock <- newMVar ()
149+ let theirEndPoint = RemoteEndPoint { _remoteAddress = theirAddress
150+ , _remoteSocket = sock
151+ , _remoteLock = lock
132152 }
133153 modifyMVar_ (transport ^. transportState) $ return . (remoteEndPointAt theirAddress ^= Just theirEndPoint)
134154 forkIO $ do
135155 unclosedConnections <- handleIncomingMessages ourEndPoint theirEndPoint
136156 forM_ unclosedConnections $ \cix -> writeChan (ourEndPoint ^. localChannel) (ConnectionClosed cix)
137157 -- TODO: should we close the socket here? (forkRemoteEndPoint gets called from multiple places)
158+ -- TODO: and should we remove it from the transaction state?
138159 return theirEndPoint
139160
161+-- Control headers
162+data ControlHeaders =
163+ RequestConnectionId -- ^ Request a new connection ID from the remote endpoint
164+ | ControlResponse -- ^ Respond to a control request _from_ the remote endpoint
165+ deriving Enum
166+
140167-- | Handle requests from a remote endpoint.
141168--
142169-- Returns only if the remote party closes the socket or if an error occurs.
@@ -145,37 +172,45 @@ forkRemoteEndPoint transport ourEndPoint theirAddress theirSock = do
145172handleIncomingMessages :: LocalEndPoint -> RemoteEndPoint -> IO [ConnectionId]
146173handleIncomingMessages ourEndPoint theirEndPoint = execWriterT . runMaybeT . forever $ do
147174 connId <- recvInt32 sock
148− case connId of
149− REQUEST_CONNECTION_ID -> createNewConnection
150− RESPOND_CONNECTION_ID -> readNewConnectionId
151− _ -> readMessage connId
175+ if connId >= firstNonReservedConnectionId
176+ then readMessage connId
177+ else do
178+ reqId <- recvInt32 sock
179+ case toEnum (fromIntegral connId) of
180+ RequestConnectionId -> createNewConnection reqId
181+ ControlResponse -> readControlResponse reqId
152182 where
153183 -- A brief note on the types:
154184 -- These functions may fail (connection error): hence MaybeT
155185 -- We also need to keep track of which connections are still open: hence WriterT
156186
157187 -- Create a new connection
158− createNewConnection :: MaybeT (WriterT [ConnectionId] IO) ()
159− createNewConnection = do
188+ createNewConnection :: ControlRequestId -> MaybeT (WriterT [ConnectionId] IO) ()
189+ createNewConnection reqId = do
160190 newId <- liftIO $ getNextConnectionId ourEndPoint
161191 withRemoteLock theirEndPoint $ do
162− sendInt32 sock RESPOND_CONNECTION_ID
163− sendInt32 sock newId
192+ sendMany sock (encodeInt32 ControlResponse : encodeInt32 reqId : prependLength [encodeInt32 newId])
164193 liftIO $ writeChan ourChannel (ConnectionOpened newId ReliableOrdered (theirEndPoint ^. remoteAddress))
165194 -- We add the new connection ID to the list of open connections only once the
166195 -- endpoint has been notified of the new connection (sendInt32 may fail)
167196 tell [newId]
168197
198+ -- Read a control response
199+ readControlResponse :: ControlRequestId -> MaybeT (WriterT [ConnectionId] IO) ()
200+ readControlResponse reqId = do
201+ response <- recvWithLength sock
202+ mmvar <- liftIO $ (^. pendingCtrlRequestsAt reqId) <$> readMVar ourState
203+ case mmvar of
204+ Nothing -> return () -- Invalid request ID. TODO: We just ignore it?
205+ Just mvar -> liftIO $ putMVar mvar response
206+
169207 -- Read a message and output it on the endPoint's channel
170208 readMessage :: ConnectionId -> MaybeT (WriterT [ConnectionId] IO) ()
171209 readMessage connId = recvWithLength sock >>= liftIO . writeChan ourChannel . Received connId
172210
173− -- Read a new connection ID
174− readNewConnectionId :: MaybeT (WriterT [ConnectionId] IO) ()
175− readNewConnectionId = recvInt32 sock >>= liftIO . writeChan (theirEndPoint ^. remoteConnectionIds)
176−
177211 -- Breakdown of the arguments
178212 ourChannel = ourEndPoint ^. localChannel
213+ ourState = ourEndPoint ^. localState
179214 sock = theirEndPoint ^. remoteSocket
180215
181216-- | Get the next connection ID
@@ -189,7 +224,10 @@ tcpNewEndPoint :: TCPTransport -> IO (Either (FailedWith NewEndPointErrorCode) E
189224tcpNewEndPoint transport = do
190225 endPoint <- modifyMVar (transport ^. transportState) $ \st -> do
191226 chan <- newChan
192− state <- newMVar EndPointState { _nextConnectionId = firstNonReservedConnectionId }
227+ state <- newMVar EndPointState { _nextConnectionId = firstNonReservedConnectionId
228+ , _pendingCtrlRequests = IntMap.empty
229+ , _nextCtrlRequestId = 0
230+ }
193231 let ix = fromIntegral $ IntMap.size (st ^. localEndPoints)
194232 let host = transport ^. transportHost
195233 let port = transport ^. transportPort
@@ -220,22 +258,41 @@ tcpConnect :: TCPTransport -- ^ Transport
220258 -> Reliability -- ^ Reliability (ignored)
221259 -> IO (Either (FailedWith ConnectErrorCode) Connection)
222260tcpConnect transport ourEndPoint theirAddress _ = runErrorT $ do
223− theirEndPoint <- remoteEndPoint transport ourEndPoint theirAddress
224− connId <- failWithT undefined $ requestNewConnection theirEndPoint
225− connBs <- encodeInt32 connId
226− let sock = theirEndPoint ^. remoteSocket
227− return Connection { send = runErrorT .
228− failWithT (FailedWith SendFailed "Send failed") .
229− withRemoteLock theirEndPoint .
230− sendWithLength sock (Just connBs)
231− , close = N.sClose sock
232− }
261+ theirEndPoint <- remoteEndPoint transport ourEndPoint theirAddress
262+ connId <- requestNewConnection ourEndPoint theirEndPoint
263+ let sock = theirEndPoint ^. remoteSocket
264+ return Connection { send = runErrorT .
265+ failWithIO (FailedWith SendFailed . show) .
266+ withRemoteLock theirEndPoint .
267+ sendMany sock .
268+ (\payload -> encodeInt32 connId : prependLength payload)
269+ , close = -- TODO: we should not close the TCP connection until all
270+ -- lightweight connections have been closed
271+ N.sClose sock
272+ }
233273
234274-- | Request a new connection
235−requestNewConnection :: RemoteEndPoint -> MaybeT IO ConnectionId
236−requestNewConnection endPoint = do
237− withRemoteLock endPoint $ sendInt32 (endPoint ^. remoteSocket) 0
238− liftIO $ readChan (endPoint ^. remoteConnectionIds)
275+requestNewConnection :: (MonadIO m, MonadError (FailedWith ConnectErrorCode) m)
276+ => LocalEndPoint
277+ -> RemoteEndPoint
278+ -> m ConnectionId
279+requestNewConnection ourEndPoint theirEndPoint = do
280+ mcid <- failWithIO (failed . show) $ do
281+ reply <- newEmptyMVar
282+ reqId <- modifyMVar state $ \st -> do
283+ let reqId = st ^. nextCtrlRequestId
284+ return ((nextCtrlRequestId ^+= 1) . (pendingCtrlRequestsAt reqId ^= Just reply) $ st, reqId)
285+ withRemoteLock theirEndPoint $ sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId]
286+ response <- decodeInt32 . BS.concat <$> takeMVar reply
287+ modifyMVar_ state $ return . (pendingCtrlRequestsAt reqId ^= Nothing)
288+ return response
289+ case mcid of
290+ Nothing -> throwError (failed "Invalid integer")
291+ Just cid -> return cid
292+ where
293+ sock = theirEndPoint ^. remoteSocket
294+ failed = FailedWith ConnectFailed
295+ state = ourEndPoint ^. localState
239296
240297-- | Find a remote endpoint
241298--
@@ -250,14 +307,23 @@ remoteEndPoint transport ourEndPoint theirAddress = do
250307 Just endpoint ->
251308 return endpoint
252309 Nothing -> do
310+ -- Create a new connection
253311 -- liftIO $ putStrLn $ "Creating socket from " ++ show ourAddress ++ " to " ++ show theirAddress
254− (host, port, endPointId) <- failWith invalidAddress (decodeEndPointAddress theirAddress)
255− sock <- failWithT undefined (connectTo host port)
256− endPointBS <- encodeInt16 endPointId
257− failWithT undefined $ sendWithLength sock (Just endPointBS) [ourAddress]
312+ (host, port, endPointId) <- failWith (invalidAddress "Could not parse") $
313+ (decodeEndPointAddress theirAddress)
314+ addr:_ <- failWithIO (invalidAddress . show) $
315+ N.getAddrInfo Nothing (Just host) (Just port)
316+ sock <- failWithIO (insufficientResources . show) $
317+ N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
318+ liftIO $ N.setSocketOption sock N.ReuseAddr 1
319+ failWithIO (failed . show) $ N.connect sock (N.addrAddress addr)
320+ -- Send the endpoint ID we're interested in and our own address
321+ failWithIO (failed . show) $ sendMany sock (encodeInt32 endPointId : prependLength [ourAddress])
258322 liftIO $ forkRemoteEndPoint transport ourEndPoint theirAddress sock
259323 where
260− invalidAddress = FailedWith ConnectInvalidAddress "Invalid address"
324+ invalidAddress = FailedWith ConnectInvalidAddress
325+ insufficientResources = FailedWith ConnectInsufficientResources
326+ failed = FailedWith ConnectFailed
261327 EndPointAddress ourAddress = ourEndPoint ^. localAddress
262328
263329-- | We must avoid scrambling concurrent sends to the same endpoint
@@ -313,17 +379,23 @@ remoteAddress = lens _remoteAddress (\addr ep -> ep { _remoteAddress = addr })
313379remoteSocket :: Lens RemoteEndPoint N.Socket
314380remoteSocket = lens _remoteSocket (\sock ep -> ep { _remoteSocket = sock })
315381
316−remoteConnectionIds :: Lens RemoteEndPoint (Chan ConnectionId)
317−remoteConnectionIds = lens _remoteConnectionIds (\ch ep -> ep { _remoteConnectionIds = ch })
318−
319382remoteLock :: Lens RemoteEndPoint (MVar ())
320383remoteLock = lens _remoteLock (\lock ep -> ep { _remoteLock = lock })
321384
322385nextConnectionId :: Lens EndPointState ConnectionId
323386nextConnectionId = lens _nextConnectionId (\cix st -> st { _nextConnectionId = cix })
324387
388+pendingCtrlRequests :: Lens EndPointState (IntMap (MVar [ByteString]))
389+pendingCtrlRequests = lens _pendingCtrlRequests (\rep st -> st { _pendingCtrlRequests = rep })
390+
391+nextCtrlRequestId :: Lens EndPointState ControlRequestId
392+nextCtrlRequestId = lens _nextCtrlRequestId (\cid st -> st { _nextCtrlRequestId = cid })
393+
325394localEndPointAt :: EndPointId -> Lens TransportState (Maybe LocalEndPoint)
326395localEndPointAt ix = localEndPoints >>> intMapLens (fromIntegral ix)
327396
328397remoteEndPointAt :: EndPointAddress -> Lens TransportState (Maybe RemoteEndPoint)
329398remoteEndPointAt addr = remoteEndPoints >>> mapLens addr
399+
400+pendingCtrlRequestsAt :: ControlRequestId -> Lens EndPointState (Maybe (MVar [ByteString]))
401+pendingCtrlRequestsAt ix = pendingCtrlRequests >>> intMapLens (fromIntegral ix)