packages feed

courier 0.1.0.15 → 0.1.1.1

raw patch · 24 files changed

+1674/−1882 lines, 24 filesnew-component:exe:echo-clientnew-component:exe:echo-serverPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Network.Endpoints: bindEndpoint :: Endpoint -> Name -> IO (Either String ())
- Network.Endpoints: bindEndpoint_ :: Endpoint -> Name -> IO ()
- Network.Endpoints: broadcastMessage_ :: Endpoint -> [Name] -> Message -> IO ()
- Network.Endpoints: dispatchMessage :: Endpoint -> (Message -> Maybe v) -> (v -> IO r) -> IO r
- Network.Endpoints: dispatchMessageTimeout :: Endpoint -> Int -> (Message -> Maybe v) -> (v -> IO r) -> IO (Maybe r)
- Network.Endpoints: sendMessage_ :: Endpoint -> Name -> Message -> IO ()
- Network.Endpoints: unbindEndpoint :: Endpoint -> Name -> IO (Either String ())
- Network.Endpoints: unbindEndpoint_ :: Endpoint -> Name -> IO ()
- Network.Transport: Envelope :: Name -> Message -> Envelope
- Network.Transport: data Envelope
- Network.Transport: data Resolver
- Network.Transport: envelopeContents :: Envelope -> Message
- Network.Transport: envelopeDestination :: Envelope -> Name
- Network.Transport: handles :: Transport -> Name -> IO Bool
- Network.Transport: instance Constructor C1_0Envelope
- Network.Transport: instance Datatype D1Envelope
- Network.Transport: instance Eq Envelope
- Network.Transport: instance Generic Envelope
- Network.Transport: instance Selector S1_0_0Envelope
- Network.Transport: instance Selector S1_0_1Envelope
- Network.Transport: instance Serialize Envelope
- Network.Transport: instance Show Envelope
- Network.Transport: resolve :: Resolver -> Name -> IO (Maybe Address)
- Network.Transport: resolverFromList :: [(Name, Address)] -> Resolver
- Network.Transport: scheme :: Transport -> String
- Network.Transport: sendTo :: Transport -> Name -> Message -> IO ()
- Network.Transport: type Address = String
- Network.Transport: type Message = ByteString
- Network.Transport: type Name = String
- Network.Transport: type Scheme = String
- Network.Transport.TCP: lookupAddresses :: Family -> SocketType -> Address -> IO [SockAddr]
- Network.Transport.TCP: lookupTCPAddress :: Address -> Family -> IO SockAddr
- Network.Transport.TCP: lookupWildcardTCPAddress :: Address -> Family -> IO SockAddr
- Network.Transport.TCP: newTCPTransport :: Resolver -> IO Transport
- Network.Transport.TCP: newTCPTransport6 :: Resolver -> IO Transport
- Network.Transport.UDP: lookupAddresses :: Family -> SocketType -> Address -> IO [SockAddr]
- Network.Transport.UDP: lookupUDPAddress :: Address -> Family -> IO SockAddr
- Network.Transport.UDP: lookupWildcardUDPAddress :: Address -> Family -> IO SockAddr
- Network.Transport.UDP: newUDPTransport :: Resolver -> IO Transport
- Network.Transport.UDP: newUDPTransport6 :: Resolver -> IO Transport
+ Network.Endpoints: BindingDoesNotExist :: Name -> BindException
+ Network.Endpoints: BindingExists :: Name -> BindException
+ Network.Endpoints: Endpoint :: Mailbox Message -> Mailbox Envelope -> TVar (Set Name) -> Endpoint
+ Network.Endpoints: Envelope :: Maybe Name -> Name -> Message -> Envelope
+ Network.Endpoints: Name :: String -> Name
+ Network.Endpoints: bindName :: Endpoint -> Name -> STM ()
+ Network.Endpoints: boundEndpointNames :: Endpoint -> TVar (Set Name)
+ Network.Endpoints: data BindException
+ Network.Endpoints: data Envelope
+ Network.Endpoints: endpointInbound :: Endpoint -> Mailbox Message
+ Network.Endpoints: endpointOutbound :: Endpoint -> Mailbox Envelope
+ Network.Endpoints: envelopeMessage :: Envelope -> Message
+ Network.Endpoints: instance Constructor C1_0Name
+ Network.Endpoints: instance Datatype D1Name
+ Network.Endpoints: instance Eq Envelope
+ Network.Endpoints: instance Eq Name
+ Network.Endpoints: instance Exception BindException
+ Network.Endpoints: instance Generic Name
+ Network.Endpoints: instance Ord Name
+ Network.Endpoints: instance Serialize Name
+ Network.Endpoints: instance Show BindException
+ Network.Endpoints: instance Show Envelope
+ Network.Endpoints: instance Show Name
+ Network.Endpoints: instance Typeable BindException
+ Network.Endpoints: messageDestination :: Envelope -> Name
+ Network.Endpoints: messageOrigin :: Envelope -> Maybe Name
+ Network.Endpoints: newtype Name
+ Network.Endpoints: type Message = ByteString
+ Network.Endpoints: unbindName :: Endpoint -> Name -> STM ()
+ Network.Endpoints: withName :: Endpoint -> Name -> IO () -> IO ()
+ Network.Transport: Connection :: IO () -> Connection
+ Network.Transport: DataUnderflow :: TransportException
+ Network.Transport: Dispatcher :: IO () -> Dispatcher
+ Network.Transport: NoDataRead :: TransportException
+ Network.Transport: connect :: Transport -> Endpoint -> Name -> IO Connection
+ Network.Transport: data Connection
+ Network.Transport: data Dispatcher
+ Network.Transport: data TransportException
+ Network.Transport: disconnect :: Connection -> IO ()
+ Network.Transport: dispatch :: Transport -> Endpoint -> IO Dispatcher
+ Network.Transport: dispatchMessage :: Mailboxes -> Name -> Message -> STM ()
+ Network.Transport: dispatcher :: Mailboxes -> Endpoint -> IO Dispatcher
+ Network.Transport: instance Exception TransportException
+ Network.Transport: instance Show TransportException
+ Network.Transport: instance Typeable TransportException
+ Network.Transport: pullMessage :: Mailboxes -> Name -> STM Message
+ Network.Transport: stop :: Dispatcher -> IO ()
+ Network.Transport: type Mailboxes = TVar (Map Name (Mailbox Message))
+ Network.Transport: withBinding :: Transport -> Endpoint -> Name -> IO () -> IO ()
+ Network.Transport: withBinding2 :: Transport -> (Endpoint, Name) -> (Endpoint, Name) -> IO () -> IO ()
+ Network.Transport: withBinding3 :: Transport -> (Endpoint, Name) -> (Endpoint, Name) -> (Endpoint, Name) -> IO () -> IO ()
+ Network.Transport: withBinding4 :: Transport -> (Endpoint, Name) -> (Endpoint, Name) -> (Endpoint, Name) -> (Endpoint, Name) -> IO () -> IO ()
+ Network.Transport: withConnection :: Transport -> Endpoint -> Name -> IO () -> IO ()
+ Network.Transport: withConnection2 :: Transport -> Endpoint -> Name -> Name -> IO () -> IO ()
+ Network.Transport: withConnection3 :: Transport -> Endpoint -> Name -> Name -> Name -> IO () -> IO ()
+ Network.Transport: withConnection4 :: Transport -> Endpoint -> Name -> Name -> Name -> Name -> IO () -> IO ()
+ Network.Transport: withEndpoint :: Transport -> Endpoint -> IO () -> IO ()
+ Network.Transport: withTransport :: IO Transport -> (Transport -> IO ()) -> IO ()
+ Network.Transport.Sockets: SocketConnection :: TMVar Name -> (Message -> IO ()) -> IO Message -> IO () -> SocketConnection
+ Network.Transport.Sockets: connectionDestination :: SocketConnection -> TMVar Name
+ Network.Transport.Sockets: connector :: Mailboxes -> Endpoint -> Name -> Connect -> IO ()
+ Network.Transport.Sockets: data ResolverException
+ Network.Transport.Sockets: data SocketConnection
+ Network.Transport.Sockets: disconnectSocket :: SocketConnection -> IO ()
+ Network.Transport.Sockets: instance Constructor C1_0SocketMessage
+ Network.Transport.Sockets: instance Constructor C1_1SocketMessage
+ Network.Transport.Sockets: instance Datatype D1SocketMessage
+ Network.Transport.Sockets: instance Exception ResolverException
+ Network.Transport.Sockets: instance Generic SocketMessage
+ Network.Transport.Sockets: instance Serialize SocketMessage
+ Network.Transport.Sockets: instance Show ResolverException
+ Network.Transport.Sockets: instance Typeable ResolverException
+ Network.Transport.Sockets: messenger :: Mailboxes -> Endpoint -> SocketConnection -> IO ()
+ Network.Transport.Sockets: receiveSocketMessage :: SocketConnection -> IO Message
+ Network.Transport.Sockets: resolve1 :: Resolver -> Name -> IO SockAddr
+ Network.Transport.Sockets: sendSocketMessage :: SocketConnection -> Message -> IO ()
+ Network.Transport.Sockets: socketConnect :: Mailboxes -> Connect -> Endpoint -> Name -> IO Connection
+ Network.Transport.Sockets: socketListen :: Family -> SocketType -> Resolver -> Name -> IO Socket
+ Network.Transport.Sockets: socketResolver4 :: SocketType -> Name -> IO [SockAddr]
+ Network.Transport.Sockets: socketResolver6 :: SocketType -> Name -> IO [SockAddr]
+ Network.Transport.Sockets: type Connections = TVar (Map Name Connection)
+ Network.Transport.Sockets: type Resolver = Name -> IO [SockAddr]
+ Network.Transport.Sockets: wildcard :: SockAddr -> IO SockAddr
+ Network.Transport.Sockets.TCP: newTCPTransport4 :: Resolver -> IO Transport
+ Network.Transport.Sockets.TCP: newTCPTransport6 :: Resolver -> IO Transport
+ Network.Transport.Sockets.TCP: tcpSocketResolver4 :: Name -> IO [SockAddr]
+ Network.Transport.Sockets.TCP: tcpSocketResolver6 :: Name -> IO [SockAddr]
+ Network.Transport.Sockets.UDP: newUDPTransport4 :: Resolver -> IO Transport
+ Network.Transport.Sockets.UDP: newUDPTransport6 :: Resolver -> IO Transport
+ Network.Transport.Sockets.UDP: udpSocketResolver4 :: Name -> IO [SockAddr]
+ Network.Transport.Sockets.UDP: udpSocketResolver6 :: Name -> IO [SockAddr]
- Network.Endpoints: broadcastMessage :: Endpoint -> [Name] -> Message -> IO [(Either String ())]
+ Network.Endpoints: broadcastMessage :: Endpoint -> [Name] -> Message -> IO ()
- Network.Endpoints: newEndpoint :: [Transport] -> IO Endpoint
+ Network.Endpoints: newEndpoint :: IO Endpoint
- Network.Endpoints: postMessage :: Endpoint -> Message -> IO ()
+ Network.Endpoints: postMessage :: Endpoint -> Message -> STM ()
- Network.Endpoints: sendMessage :: Endpoint -> Name -> Message -> IO (Either String ())
+ Network.Endpoints: sendMessage :: Endpoint -> Name -> Message -> IO ()
- Network.Transport: Transport :: String -> (Name -> IO Bool) -> (Mailbox Message -> Name -> IO (Either String Binding)) -> (Name -> Message -> IO ()) -> IO () -> Transport
+ Network.Transport: Transport :: (Endpoint -> Name -> IO Binding) -> (Endpoint -> IO Dispatcher) -> (Endpoint -> Name -> IO Connection) -> IO () -> Transport
- Network.Transport: bind :: Transport -> Mailbox Message -> Name -> IO (Either String Binding)
+ Network.Transport: bind :: Transport -> Endpoint -> Name -> IO Binding

Files

changes.md view
@@ -1,3 +1,17 @@+0.1.1.1++There are significant changes in this release, due to a complete rewrite of TCP connection handling, and,+in so doing, a rewrite of the basic model for how transports and endpoints interact. This rewrite fixes+bug #10.++Endpoints now simply have inbound and outbound mailboxes, and a set of names to which the endpoint is bound+via a transport. The intent is that endpoints are discrete entities orthogonal to the transport which moves+messages between them. Its the job of the transport to move messages from the outbound mailbox of one endpoint+to the inbound mailbox of the endpoint bound to the name specified in the envelope for a given message.++New helpers for managing the lifecycle of bindings and connections now exist, and also a helper for setting the name+of a client.+ 0.1.0.15      * Initial implementation of IPv6 transports (TCP and UDP)@@ -102,16 +116,16 @@    types of messages. Each message pump can operate independently of one another.   * Fixed issue #2 and other interim bugs all resulting from multiple name bindings that resolve-   the same underlying address.  Now, bound sockets are reused (with reference counting) so +   the same underlying address.  Now, bound sockets are reused (with reference counting) so    that if there are multiple bindings to the same address only 1 socket is created and user. - * Unit tests passing on Mac OS X again, mostly due to correct management of sockets and + * Unit tests passing on Mac OS X again, mostly due to correct management of sockets and    appropriate reuse.- + 0.1.0.7    * First inclusion of changelog in package-  + 0.1.0.6   * Added simple implementation of transport for UDP
courier.cabal view
@@ -1,10 +1,10 @@ name:                courier-version:             0.1.0.15+version:             0.1.1.1 synopsis:           A message-passing library for simplifying network applications-description:         Inspired by Erlang's simple message-passing facilities, courier provides roughly similar -                     capabilities. Applications simply create one or more endpoints, -                     bind each to a transport using a given name, then can freely -                     send / receive messages to other endpoints just by referencing the name each endpoint +description:         Inspired by Erlang's simple message-passing facilities, courier provides roughly similar+                     capabilities. Applications simply create one or more endpoints,+                     bind each to a transport using a given name, then can freely+                     send / receive messages to other endpoints just by referencing the name each endpoint                      bound to its transport.                      .                      A primary driver of this package's design is separation of concerns: many algorithms@@ -14,12 +14,12 @@                      the distributed algorithm itself.  With this in mind, this package aims                      to provide a variety of transports as well as support for common communication                      idioms (e.g., in order message delivery, selective out of order message delivery,-                     synchronous RPC, etc.). +                     synchronous RPC, etc.).                      .                      Applications may process messages in the order received at an endpoint, or use                      selective message reception to process the first message arriving at an endpoint                      that also matches a provided selection function. Through selective message reception,-                     applications may approximate the style of an Erlang application, and enjoy better +                     applications may approximate the style of an Erlang application, and enjoy better                      composability of message reception with multiple independent dispatch routines or                       message pumps. @@ -30,8 +30,8 @@                      tests/TestMemory.hs                      tests/TestRPC.hs                      tests/TestTCP.hs-                     tests/TestTransports.hs                      tests/TestUDP.hs+                     tests/TransportTestSuite.hs                      tests/TestUtils.hs  homepage:          http://github.com/hargettp/courier@@ -56,15 +56,21 @@                   Network.Endpoints                   Network.Transport                   Network.Transport.Memory-                  Network.Transport.TCP-                  Network.Transport.UDP+                  Network.Transport.Sockets+                  Network.Transport.Sockets.TCP+                  Network.Transport.Sockets.UDP                   Control.Concurrent.Mailbox                   Network.RPC -  ghc-options: -Wall -fno-state-hack-  other-modules:-                  Network.Transport.Internal-                  Network.Transport.Sockets+  ghc-options:+    -Wall+    -fno-state-hack+    -threaded+    -- -O2+    -- -fprof-auto++  -- other-modules:+   build-depends:       base >=4.6 && <5,                        async,                        bytestring,@@ -76,16 +82,38 @@                        text,                        uuid +executable echo-server+  hs-source-dirs: echo+  main-is: EchoServer.hs+  ghc-options:+    -Wall+    -fno-state-hack+    -threaded+  build-depends:       base >=4.6 && <5,+                       cereal,+                       courier++executable echo-client+  hs-source-dirs: echo+  main-is: EchoClient.hs+  ghc-options:+    -Wall+    -fno-state-hack+    -threaded+  build-depends:      base >=4.6 && <5,+                      cereal,+                      courier+ Test-Suite test-courier     type: exitcode-stdio-1.0     hs-source-dirs: tests     main-is: Tests.hs-    build-depends: +    build-depends:          -- base modules-        base, +        base,         -- testing support-        HUnit, +        HUnit,         test-framework,         test-framework-hunit,         -- Cabal,@@ -100,4 +128,9 @@         -- this project's modules         courier -    ghc-options: -Wall -threaded+    ghc-options:+      -Wall+      -threaded+      -- -O2+      -- -fprof-auto+      -- "-with-rtsopts=-N -p -s -h -i0.1"
+ echo/EchoClient.hs view
@@ -0,0 +1,58 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  EchoClient+-- Copyright   :  (c) Phil Hargett 2015+-- License     :  MIT (see LICENSE file)+--+-- Maintainer  :  phil@haphazardhouse.net+-- Stability   :  experimental+-- Portability :  non-portable (requires STM)+--+-- Simple echo client for testing out courier.+--+-----------------------------------------------------------------------------++module Main where++-- local imports++import Network.Endpoints+import Network.RPC+import Network.Transport.Sockets.TCP++-- external imports++import Control.Exception++import Data.Serialize++import System.Environment+import System.IO++-----------------------------------------------------------------------------+-----------------------------------------------------------------------------++main :: IO ()+main = do+  [serverNameStr,clientNameStr] <- getArgs+  let server = Name serverNameStr+      client = Name clientNameStr+  endpoint <- newEndpoint+  withTransport (newTCPTransport4 tcpSocketResolver4) $ \transport ->+    withEndpoint transport endpoint $+      withName endpoint client $+        withConnection transport endpoint server $ do+          hPutStrLn stdout $ "Started echo client on " ++ (show client) ++ " for " ++ (show server)+          finally (echo endpoint server client)+            (hPutStrLn stdout $ "\nStopped echo client on " ++ (show client) ++ " for " ++ (show server))++echo :: Endpoint -> Name -> Name -> IO ()+echo endpoint server client = do+  text <- hGetLine stdin+  let cs = newCallSite endpoint client+  response <- call cs server "echo" $ encode text+  case decode response of+    Left _ -> error "Could not decode message"+    Right responseText -> do+      hPutStrLn stdout $ "> " ++ responseText+      echo endpoint server client
+ echo/EchoServer.hs view
@@ -0,0 +1,55 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  EchoServer+-- Copyright   :  (c) Phil Hargett 2015+-- License     :  MIT (see LICENSE file)+--+-- Maintainer  :  phil@haphazardhouse.net+-- Stability   :  experimental+-- Portability :  non-portable (requires STM)+--+-- Simple echo server for testing out courier.+--+-----------------------------------------------------------------------------++module Main where++-- local imports++import Network.Endpoints+import Network.RPC+import Network.Transport.Sockets.TCP++-- external imports++import Control.Exception++import Data.Serialize++import System.Environment+import System.IO++-----------------------------------------------------------------------------+-----------------------------------------------------------------------------++main :: IO ()+main = do+  [nameStr] <- getArgs+  let server = Name nameStr+  endpoint <- newEndpoint+  withTransport (newTCPTransport4 tcpSocketResolver4) $ \transport ->+    withEndpoint transport endpoint $+      withBinding transport endpoint server $ do+        hPutStrLn stdout $ "Started echo server on " ++ (show server)+        finally (echo endpoint server)+          (hPutStrLn stdout $ "\nStopped echo server on " ++ (show server))++echo :: Endpoint -> Name -> IO ()+echo endpoint server = do+  (bytes,reply) <- hear endpoint server "echo"+  case decode bytes of+    Left _ -> error "Could not decode message"+    Right text -> do+      hPutStrLn stdout text+      reply $ encode text+      echo endpoint server
examples/HelloWorld.hs view
@@ -1,31 +1,54 @@-module HelloWorld (-    main-) where+-----------------------------------------------------------------------------+-- |+-- Module      :  HelloWorld+-- Copyright   :  (c) Phil Hargett 2015+-- License     :  MIT (see LICENSE file)+--+-- Maintainer  :  phil@haphazardhouse.net+-- Stability   :  experimental+-- Portability :  non-portable (requires STM)+--+-- Basic code sample for explaining how to use courier.+--+----------------------------------------------------------------------------- - -- Just import this package to access the primary APIs+module Main where++ -- Import this package to manage endpoints import Network.Endpoints   -- A specific transport is necessary, however-import Network.Transport.TCP+import Network.Transport.Sockets.TCP  -- Needed for serialization import Data.Serialize  main :: IO () main = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-        resolver = resolverFromList [(name1,"localhost:2000"),-                                (name2,"localhost:2001")]-    transport <- newTCPTransport resolver-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    sendMessage_ endpoint1 name2 $ encode "hello world!"-    msg <- receiveMessage endpoint2-    let Right txt = decode msg-        in print (txt :: String)-    Right () <- unbindEndpoint endpoint1 name1-    Right () <- unbindEndpoint endpoint2 name2-    shutdown transport+  -- each endpoint needs a name; since we're using TCP+  -- as our transport, they need to be host/port pairs+  let name1 = Name "localhost:9001"+      name2 = Name "localhost:9002"+      -- the default resolvers just pull apart a name into separate+      -- host and port components; more elaborate resolvers could+      -- perform name lookups or other translations+      resolver = tcpSocketResolver4+  -- we need endpoints for each end of the communication+  endpoint1 <- newEndpoint+  endpoint2 <- newEndpoint+  -- we need a transport to move messages between endpoints+  withTransport (newTCPTransport4 resolver) $ \transport ->+    withEndpoint transport endpoint1 $+      withEndpoint transport endpoint2 $+      -- the first endpoint is just a client, so it needs a name to receive+      -- responses, but does not need a binding since it isn't accept connections+        withName endpoint1 name1 $+          -- the second endpoint is a server, so it needs a binding+          withBinding transport endpoint2 name2 $+            -- a connection between the first endpoint and the name of the second+            -- creates a bi-directional path for messages to flow between the endpoints+            withConnection transport endpoint1 name2 $ do+              sendMessage endpoint1 name2 $ encode "hello world!"+              msg <- receiveMessage endpoint2+              let Right txt = decode msg+                  in print (txt :: String)
src/Control/Concurrent/Mailbox.hs view
@@ -3,7 +3,7 @@ -- Module      :  Control.Concurrent.Mailbox -- Copyright   :  (c) Phil Hargett 2014 -- License     :  MIT (see LICENSE file)--- +-- -- Maintainer  :  phil@haphazardhouse.net -- Stability   :  experimental -- Portability :  non-portable (uses STM)@@ -11,7 +11,7 @@ -- A 'Mailbox' is a drop-in replacement for 'TQueue' in "Control.Concurrent.STM", except that -- it also supports selective out of order message reception: that is, it allows the caller -- to dequeue the first message among the messages available in the queue that matches--- a supplied test function, or block if no such match is possible with the messages currently +-- a supplied test function, or block if no such match is possible with the messages currently -- in the queue. -- -- As 'Mailbox' implements the same basic @read / write / peek@ group of functions as a 'TQueue',@@ -76,7 +76,10 @@ -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -data Mailbox m = Mailbox +{-|+A 'Mailbox' is a variation of a 'Control.Concurrent.TQueue', with the added feature of selective message retrieval.+-}+data Mailbox m = Mailbox     {-# UNPACK #-} !(TVar [m])     {-# UNPACK #-} !(TVar [m]) @@ -234,7 +237,7 @@                 Nothing -> return Nothing  {-|-Put a data item back onto a channel, where it will be the next item read.+Put a data item back onto a mailbox, where it will be the next item read. -} unGetMailbox :: Mailbox m -> m -> STM () unGetMailbox (Mailbox _read _write) msg = do@@ -259,7 +262,7 @@ --  {-|-Extract the first element from a list matching the +Extract the first element from a list matching the provided test and return a new list without the matching element. -}@@ -272,7 +275,7 @@         Just v -> (Just v,xs)  {-|-Find the first element from a list matching the +Find the first element from a list matching the provided test, or Nothing if there is no match. -} find :: (m -> Maybe v) -> [m] -> Maybe v
src/Network/Endpoints.hs view
@@ -1,11 +1,14 @@+{-# LANGUAGE DeriveDataTypeable     #-}+{-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE OverloadedStrings #-}  ----------------------------------------------------------------------------- -- | -- Module      :  Network.Endpoints -- Copyright   :  (c) Phil Hargett 2013 -- License     :  MIT (see LICENSE file)--- +-- -- Maintainer  :  phil@haphazardhouse.net -- Stability   :  experimental -- Portability :  non-portable (requires STM)@@ -14,29 +17,27 @@ -- whether on the same physical host or distributed over a network. 'Endpoint's are intended -- to simplify the development of network-centric applications by providing a small transport-independent -- message-passing interface, and application writers can independently alter their implementation--- by enabling their 'Endpoint's with different 'Transport's without modifying the logic of their+-- by enabling their 'Endpoint's with different 'Network.Transport's without modifying the logic of their -- application that sends / receives 'Message's. -- -----------------------------------------------------------------------------  module Network.Endpoints (-  +   -- * How to use courier in an application   -- $use-  +   -- * Primary API-  Endpoint,+  Endpoint(..),   newEndpoint,-  -  bindEndpoint,-  bindEndpoint_,-  unbindEndpoint,-  unbindEndpoint_,-  ++  withName,+  bindName,+  unbindName,+  BindException(..),+   sendMessage,-  sendMessage_,   broadcastMessage,-  broadcastMessage_,   receiveMessage,   receiveMessageTimeout,   postMessage,@@ -46,193 +47,196 @@   selectMessageTimeout,   detectMessage,   detectMessageTimeout,-  dispatchMessage,-  dispatchMessageTimeout,-  -  -- * Transports-  {-|-  Transports define specific implementations of message-passing techniques (e.g.,-  memory-based, TCP, UDP, HTTP, etc.). Typical use of the 'Endpoint's does not-  require direct use of 'Transport's, beyond creating specific 'Transport's (such as-  found in "Network.Transport.Memory" and "Network.Transport.TCP") and adding-  them to an 'Endpoint'.-  -}-  module Network.Transport-  ++  -- * Other types+  Envelope(..),+  Message,+  Name(..),+   ) where  -- local imports -import Network.Transport- -- external imports  import Control.Concurrent import Control.Concurrent.Async+import Control.Concurrent.Mailbox import Control.Concurrent.STM+import Control.Exception -import qualified Data.Map as M+import qualified Data.ByteString as B+import Data.Serialize+import qualified Data.Set as S+import Data.Typeable +import GHC.Generics+ -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- +_log :: String+_log = "transport.endpoints"+ -- $use--- +-- -- A sample of how to use this library:--- --- > module HelloWorld (--- >     main--- > ) where+--+-- > module Main where -- >--- > -- Just import this package to access the primary APIs+-- >  -- Import this package to manage endpoints -- > import Network.Endpoints -- >--- > -- A specific transport is necessary, however--- > import Network.Transport.TCP+-- >  -- A specific transport is necessary, however+-- > import Network.Transport.Sockets.TCP -- > -- > -- Needed for serialization -- > import Data.Serialize -- > -- > main :: IO () -- > main = do--- >    let name1 = "endpoint1"--- >        name2 = "endpoint2"--- >        resolver = resolverFromList [(name1,"localhost:2000"),--- >                                (name2,"localhost:2001")]--- >    transport <- newTCPTransport resolver--- >    endpoint1 <- newEndpoint [transport]--- >    endpoint2 <- newEndpoint [transport]--- >    Right () <- bindEndpoint endpoint1 name1--- >    Right () <- bindEndpoint endpoint2 name2--- >    sendMessage_ endpoint1 name2 $ encode "hello world!"--- >    msg <- receiveMessage endpoint2--- >    let Right txt = decode msg--- >        in print (txt :: String)--- >    Right () <- unbindEndpoint endpoint1 name1--- >    Right () <- unbindEndpoint endpoint2 name2--- >    shutdown transport+-- >   -- each endpoint needs a name; since we're using TCP+-- >   -- as our transport, they need to be host/port pairs+-- >   let name1 = Name "localhost:9001"+-- >       name2 = Name "localhost:9002"+-- >       -- the default resolvers just pull apart a name into separate+-- >       -- host and port components; more elaborate resolvers could+-- >       -- perform name lookups or other translations+-- >       resolver = tcpSocketResolver4+-- >   -- we need endpoints for each end of the communication+-- >   endpoint1 <- newEndpoint+-- >   endpoint2 <- newEndpoint+-- >   -- we need a transport to move messages between endpoints+-- >   withTransport (newTCPTransport4 resolver) $ \transport ->+-- >     withEndpoint transport endpoint1 $+-- >       withEndpoint transport endpoint2 $+-- >       -- the first endpoint is just a client, so it needs a name to receive+-- >       -- responses, but does not need a binding since it isn't accept connections+-- >         withName endpoint1 name1 $+-- >           -- the second endpoint is a server, so it needs a binding+-- >           withBinding transport endpoint2 name2 $+-- >             -- a connection between the first endpoint and the name of the second+-- >             -- creates a bi-directional path for messages to flow between the endpoints+-- >             withConnection transport endpoint1 name2 $ do+-- >               sendMessage endpoint1 name2 $ encode "hello world!"+-- >               msg <- receiveMessage endpoint2+-- >               let Right txt = decode msg+-- >                   in print (txt :: String)+-- >+--  {-| Endpoints are a locus of communication, used for sending and receive messages. -} data Endpoint = Endpoint {-  endpointTransports :: TVar [Transport],-  endpointBindings :: TVar (M.Map Name Binding),-  endpointMailbox :: Mailbox Message+  -- | The 'Transport' used by this 'Endpoint'+  -- endpointTransport :: Transport,+  -- | The 'Mailbox' where inbound 'Message's that the 'Endpoint' will receive are queued+  endpointInbound :: Mailbox Message,+  endpointOutbound :: Mailbox Envelope,+  -- | The 'Name's to which the 'Endpoint' is bound+  boundEndpointNames :: TVar (S.Set Name )   }  {-|-Create a new 'Endpoint' using the provided transports.+Messages are containers for arbitrary data that may be sent to other 'Network.Endpoints.Endpoint's. -}-newEndpoint :: [Transport] -> IO Endpoint-newEndpoint trans = do-  transports <- atomically $ newTVar trans-  bindings <- atomically $ newTVar M.empty-  mailbox <- atomically $ newMailbox-  return Endpoint {-    endpointTransports = transports,-    endpointBindings = bindings,-    endpointMailbox = mailbox-    }+type Message = B.ByteString  {-|-Binding an 'Endpoint' to a 'Name' prepares the 'Endpoint' to receive-messages sent to the bound name.  Upon success, the result will be @Right ()@, but-if failed, @Left text-of-error-message@.+Name for uniquely identifying an 'Endpoint'; suitable for identifying+the target destination for a 'Message'. The specific interpretation of a name+is left to each 'Network.Transport.Transport' -}-bindEndpoint :: Endpoint -> Name -> IO (Either String ())-bindEndpoint endpoint name = do -  maybeTransport <- findTransport endpoint name-  case maybeTransport of-    Nothing -> return $ Left $ "No transport to handle name: " ++ (show name)-    Just transport -> do-      eitherBinding <- bind transport (endpointMailbox endpoint) name-      case eitherBinding of-        Left err -> return $ Left err-        Right binding -> do -          atomically $ modifyTVar (endpointBindings endpoint)-            (\bindings -> M.insert name binding bindings)-          return $ Right ()+newtype Name = Name String deriving (Eq,Ord,Show,Generic) +instance Serialize Name+ {-|-Invoke 'bindEndpoint', but ignore any returned result (success or failure).+ An 'Envelope' wraps a 'Message' with the 'Name's of the destination for the message and (optionally)+the origin. -}-bindEndpoint_ :: Endpoint -> Name -> IO ()-bindEndpoint_ endpoint name = do-    _ <- bindEndpoint endpoint name-    return () +data Envelope = Envelope {+  messageOrigin :: Maybe Name,+  messageDestination :: Name,+  envelopeMessage :: Message+} deriving (Eq,Show)+ {-|-Unbind an 'Endpoint' from a 'Name', after which the 'Endpoint' will eventually not -receive messages sent to that 'Name'. Note that there is no guarantee that after 'Unbind'-succeeds that additional messages to that 'Name' will not be delivered: the only guarantee-is that eventually messages will no longer be delivered.-Upon success, the result will be @Right ()@ but-if failed, @Left text-of-error-message@.+Create a new 'Endpoint' using the provided transports. -}-unbindEndpoint :: Endpoint -> Name -> IO (Either String ())-unbindEndpoint endpoint name = do-  bindings <- atomically $ readTVar $ endpointBindings endpoint-  let maybeBinding = M.lookup name bindings-  case maybeBinding of-    Nothing -> return $ Left $ "Endpoint not bound to address: " ++ (show name)-    Just binding -> do -      unbind binding-      return $ Right ()+newEndpoint :: IO Endpoint+newEndpoint = atomically $ do+  inbound <- newMailbox+  outbound <- newMailbox+  names <- newTVar S.empty+  return Endpoint {+    endpointInbound = inbound,+    endpointOutbound = outbound,+    boundEndpointNames = names+    }  {-|-Invoke 'unbindEndpoint', but ignore any returned result (success or failure).+Declare an 'Endpoint' as having the specified 'Name' while the supplied function executes. This can+often be useful for establishing the 'Name' of a client or initiator of a 'Network.Transport.Connection',+without requiring the client also have a 'Network.Transport.Binding'.+ -}-unbindEndpoint_ :: Endpoint -> Name -> IO ()-unbindEndpoint_ endpoint name = do-    _ <- unbindEndpoint endpoint name-    return ()+withName :: Endpoint -> Name -> IO () -> IO ()+withName endpoint origin actor = do+  atomically $ bindName endpoint origin+  finally actor $ atomically $ unbindName endpoint origin  {-|-Send a 'Message' to specific 'Name' via the indicated 'Endpoint'. While a successful-response (indicated by returning @Right ()@) indicates that there was no error initiating-transport of the message, success does not guarantee that an 'Endpoint' received the message.-Failure initiating transport is indicated by returning @Left text-of-error-message@.+Establish 'Name' as one of the 'boundEndpointNames' for an 'Endpoint'. Throws 'BindingExists' if+the 'Endpoint' is already bound to the 'Name'. -}-sendMessage :: Endpoint -> Name -> Message -> IO (Either String ())-sendMessage endpoint name msg  = do-  maybeTransport <- findTransport endpoint name-  case maybeTransport of-    Nothing -> return $ Left $ "No transport to handle name: " ++ (show name)-    Just transport -> do -      sendTo transport name msg-      return $ Right ()+bindName :: Endpoint -> Name -> STM ()+bindName endpoint name = do+  bindings <- readTVar $ boundEndpointNames endpoint+  if S.member name bindings+    then throw $ BindingExists name+    else modifyTVar (boundEndpointNames endpoint) $ S.insert name  {-|-A variant of 'sendMessage' for use when the return value can be ignored.-+Remove 'Name' as one of the 'boundEndpointNames' for an 'Endpoint'. Throws 'BindingDoesNotExist'+if the 'Endpoint' is not bound to the 'Name'. -}-sendMessage_ :: Endpoint -> Name -> Message -> IO ()-sendMessage_ endpoint name msg = do-  _ <- sendMessage endpoint name msg-  return ()+unbindName :: Endpoint -> Name -> STM ()+unbindName endpoint name = modifyTVar (boundEndpointNames endpoint) $ \bindings -> do+  case S.member name bindings of+    False -> throw $ BindingDoesNotExist name+    True -> S.delete name bindings  {-|-Helper for sending a single 'Message' to several 'Endpoint's.+Exceptions generated when `Network.Transport.bind`ing a 'Name'. -}-broadcastMessage :: Endpoint -> [Name] -> Message -> IO [(Either String ())]-broadcastMessage endpoint names msg = do-  mapM (\name -> sendMessage endpoint name msg) names+data BindException =+  BindingExists Name |+  BindingDoesNotExist Name+  deriving (Show,Typeable) +instance Exception BindException+ {-|-Variant of 'broadcastMessage' that ignores the results of sending.+Send a 'Message' to specific 'Name' via the indicated 'Endpoint'.+-}+sendMessage :: Endpoint -> Name -> Message -> IO ()+sendMessage endpoint name msg  = atomically $+    writeMailbox (endpointOutbound endpoint) $ Envelope Nothing name msg +{-|+Helper for sending a single 'Message' to several 'Endpoint's. -}-broadcastMessage_ :: Endpoint -> [Name] -> Message -> IO ()-broadcastMessage_ endpoint names msg = do-  _ <- broadcastMessage endpoint names msg-  return ()+broadcastMessage :: Endpoint -> [Name] -> Message -> IO ()+broadcastMessage endpoint names msg = mapM_ (\name -> sendMessage endpoint name msg) names  {-| Receive the next 'Message' sent to the 'Endpoint', blocking until a message is available. -} receiveMessage :: Endpoint -> IO Message-receiveMessage endpoint = atomically $ readMailbox $ endpointMailbox endpoint+receiveMessage endpoint = atomically $ readMailbox $ endpointInbound endpoint  {-| Wait for a message to be received within the timeout, blocking until either a message@@ -251,9 +255,8 @@ may be useful for applications that prefer to use the 'Endpoint''s 'Mailbox' as a general queue of ordered messages. -}-postMessage :: Endpoint -> Message -> IO ()-postMessage endpoint message = do-    atomically $ writeMailbox (endpointMailbox endpoint) message+postMessage :: Endpoint -> Message -> STM ()+postMessage endpoint = writeMailbox (endpointInbound endpoint)  {-| Select the next available message in the 'Endpoint' 'Mailbox' matching@@ -261,9 +264,7 @@ differs from 'receiveMessage' in that it supports out of order message reception. -} selectMessage :: Endpoint -> (Message -> Maybe v) -> IO v-selectMessage endpoint testFn = do-    msg <- atomically $ selectMailbox (endpointMailbox endpoint) testFn-    return msg+selectMessage endpoint testFn = atomically $ selectMailbox (endpointInbound endpoint) testFn  {-| Wait for a message to be selected within the timeout, blocking until either a message@@ -285,9 +286,7 @@ message if it is not consumed immediately. -} detectMessage :: Endpoint -> (Message -> Maybe v) -> IO v-detectMessage endpoint testFn = do-    msg <- atomically $ findMailbox (endpointMailbox endpoint) testFn-    return msg+detectMessage endpoint testFn = atomically $ findMailbox (endpointInbound endpoint) testFn  {-| Find a 'Message' in the 'Endpoint' 'Mailbox' matching the supplied@@ -301,33 +300,3 @@   case resultOrTimeout of     Left result -> return $ Just result     Right () -> return Nothing--{-|-Dispatch the next available message in the 'Endpoint' 'Mailbox' matching-the supplied test function, or blocking until one is available. Once a-matching message is found, handle the message with the supplied handler-and return any result obtained. This function differs from 'receiveMessage'-in that it supports out of order message reception.--}-dispatchMessage :: Endpoint -> (Message -> Maybe v) -> (v -> IO r) -> IO r-dispatchMessage endpoint = handleMailbox (endpointMailbox endpoint)--dispatchMessageTimeout :: Endpoint -> Int -> (Message -> Maybe v) -> (v -> IO r) -> IO (Maybe r)-dispatchMessageTimeout endpoint delay testFn handleFn = do-  resultOrTimeout <- race (dispatchMessage endpoint testFn handleFn) (threadDelay delay)-  case resultOrTimeout of-    Left result -> return $ Just result-    Right () -> return Nothing--findTransport :: Endpoint -> Name -> IO (Maybe Transport)-findTransport endpoint name = do-  transports <- atomically $ readTVar $ endpointTransports endpoint-  findM canHandle transports-    where-      canHandle transport = (handles transport) name-      findM mf (a:as) = do-        result <- mf a-        if result-          then return $ Just a-          else findM mf as-      findM _ [] = return Nothing
src/Network/RPC.hs view
@@ -83,13 +83,17 @@ -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- +{-|+An identifier for what method to invoke on the receiving 'Endpoint'. If 'hear' has been invoked on the 'Endpoint'+with a matching identifier, then calls will be delivered to that invocation of 'hear'.+-} type Method = String  data RPCMessageType = Req | Rsp deriving (Eq,Show,Enum,Generic)  instance Serialize RPCMessageType -{-+{-| A unique identifier for a 'Request' -} newtype RequestId = RequestId (Word32, Word32, Word32, Word32) deriving (Generic,Eq,Show)@@ -104,13 +108,17 @@     ruuid <- nextRandom     return $ RequestId $ toWords ruuid +{-|+Encapsulates the initiating side of a 'call': every invocation of 'call' produces a 'Request' that is sent+to the destination 'Endpoint', where the 'hear'ing side will generate a 'Response' after completing the request'+-} data Request = Request {     requestId :: RequestId,     requestCaller :: Name,     requestMethod :: Method,     requestArgs :: Message } deriving (Eq,Show)- + instance Serialize Request where     put req = do         put Req@@ -125,7 +133,10 @@         method <- get         args <- get         return $ Request rid caller method args-+{-|+Encapsulates the completion side of a 'call': every invocation of 'call' produces a 'Request' that is sent+to the destination 'Endpoint', where the 'hear'ing side will generate a 'Response' after completing the request'+-} data Response = Response {     responseId :: RequestId,     responseFrom :: Name,@@ -168,7 +179,7 @@ call (CallSite endpoint from) name method args = do     rid <- mkRequestId     let req = Request {requestId = rid,requestCaller = from,requestMethod = method, requestArgs = args}-    sendMessage_ endpoint name $ encode req+    sendMessage endpoint name $ encode req     selectMessage endpoint $ \msg -> do         case decode msg of             Left _ -> Nothing@@ -207,7 +218,7 @@     recvAll req M.empty     where         sendAll req = do-            forM_ names $ \name -> sendMessage_ endpoint name $ encode req+            forM_ names $ \name -> sendMessage endpoint name $ encode req         recv req = selectMessage endpoint $ \msg -> do                 case decode msg of                     Left _ -> Nothing@@ -248,7 +259,7 @@             return $ complete partialResults     where         sendAll req = do-            forM_ names $ \name -> sendMessage_ endpoint name $ encode req+            forM_ names $ \name -> sendMessage endpoint name $ encode req         recv req = selectMessage endpoint $ \msg -> do                 case decode msg of                     Left _ -> Nothing@@ -287,7 +298,7 @@     recvAny req     where         sendAll req = do-            forM_ names $ \name -> sendMessage_ endpoint name $ encode req+            forM_ names $ \name -> sendMessage endpoint name $ encode req         recvAny req = selectMessage endpoint $ \msg -> do                 case decode msg of                     Left _ -> Nothing@@ -332,7 +343,7 @@     return (args, reply caller rid)     where         reply caller rid result = do-            sendMessage_ endpoint caller $ encode $ Response rid name result+            sendMessage endpoint caller $ encode $ Response rid name result   {-|@@ -348,7 +359,7 @@         Nothing -> return Nothing     where         reply caller rid result = do-            sendMessage_ endpoint caller $ encode $ Response rid name result+            sendMessage endpoint caller $ encode $ Response rid name result  {-| A 'HandleSite' is a just reference to the actual handler of a specific method.
src/Network/Transport.hs view
@@ -1,105 +1,195 @@-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveDataTypeable     #-}  ----------------------------------------------------------------------------- -- | -- Module      :  Network.Transport--- Copyright   :  (c) Phil Hargett 2013+-- Copyright   :  (c) Phil Hargett 2015 -- License     :  MIT (see LICENSE file)--- +-- -- Maintainer  :  phil@haphazardhouse.net -- Stability   :  experimental -- Portability :  non-portable (requires STM) -- -- A 'Transport' abstracts the details of message delivery, and defines the interfaces--- that specific 'Transport' implementations should provide in order to deliver messages--- for 'Endpoint's.+-- that specific 'Transport' implementations should provide in order to move messages+-- between 'Endpoint's. -- -- The definition of a transport is deliberately low-level in nature.  Unless a specific -- transport describes itself as supporting features like guaranteed delivery, applications -- should NOT assume that message delivery is reliable. ----- For example, if a sender sends a message to a name that has not yet been bound, then --- immediately waits on the response for that message, then the application may hang, +-- For example, if a sender sends a message to a name that has not yet been bound, then+-- immediately waits on the response for that message, then the application may hang, -- as the original message may have been dropped. -- -- However, many application may find it easier to push features such as reliable -- message delivery into a custom transport, leaving the application only having -- to concern itself with the messages being delivered rather than how they arrive. --+-- The main abstractions common to all transports:+--+-- * 'Endpoint's may not receive messages until either 'bind' has been called on an available+-- 'Transport', or a 'bindName' has been called on the 'Endpoint'. The latter is typically useful+-- for 'Endpoint's that originate connections but do not accept them directly.+-- * A 'Connection' defines a bi-directional pathway for messages to flow between 'Endpoint's. The initiator+-- of the connection is a client, and the destination for the connection is a server. Server 'Endpoint's+-- may have to have called 'bind' on the 'Transport' before being able to accept connections or receive messages;+-- a client only has to have called 'bindName' in order to receive responses from the server.+-- * Client's proactively attempt to maintain 'Connection's to the server; in the event a server breaks the connection+-- (other because of a crash or deliberate exit), the client will continue to restore the connection.+-- * 'Connection's essentially define the topology over which messages will propagate between 'Endpoint's.+-- ------------------------------------------------------------------------------ module Network.Transport (-  Address,-  Binding(..),-  Envelope(..),-  Message,-  Name,-  Resolver,-  resolve,-  resolverFromList,-  Scheme,++  -- * Transport   Transport(..),+  TransportException(..), -  module Control.Concurrent.Mailbox-  -  ) where+  Dispatcher(..),+  dispatcher, +  Mailboxes,+  pullMessage,+  dispatchMessage,++  withTransport,++  withEndpoint,++  Binding(..),+  withBinding,+  withBinding2,+  withBinding3,+  withBinding4,++  Connection(..),+  withConnection,+  withConnection2,+  withConnection3,+  withConnection4,++) where+ -- local imports-import Control.Concurrent.Mailbox +import Network.Endpoints+ -- external imports -import Data.ByteString as B-import Data.Serialize+import Control.Concurrent.Async+import Control.Concurrent.Mailbox+import Control.Concurrent.STM+import Control.Exception -import GHC.Generics+import qualified Data.Map as M+import qualified Data.Set as S+import Data.Typeable  -------------------------------------------------------------------------------- --------------------------------------------------------------------------------  {-|-Messages are containers for arbitrary data that may be sent to other 'Network.Endpoints.Endpoint's.+A 'Transport' defines a specific method for establishing connections+between 'Endpoint's. -}-type Message = B.ByteString+data Transport = Transport {+  bind :: Endpoint -> Name -> IO Binding,+  dispatch :: Endpoint -> IO Dispatcher,+  connect :: Endpoint -> Name -> IO Connection,+  shutdown :: IO ()+  }  {-|-Name for uniquely identifying an 'Endpoint'; suitable for identifying-the target destination for a 'Message'.+A 'Dispatcher' encapsulates a 'Transport's interaction with an 'Endpoint' for outbound message flow.+-}+data Dispatcher = Dispatcher {+  stop :: IO ()+} +{-|+An exception encountered by a 'Transport'. -}-type Name = String+data TransportException =+  NoDataRead |+  DataUnderflow+  deriving (Show,Typeable) +instance Exception TransportException+ {-|-An 'Envelope' is a container for a 'Message' with the 'Address' of the 'Message''s destination.+Wraps in a continually repeating call to 'dispatchMessage' in a 'Dispatcher' so that dispatching+can be stopped when no longer needed.+-}+dispatcher :: Mailboxes -> Endpoint -> IO Dispatcher+dispatcher mailboxes endpoint = do+  d <- async disp+  return Dispatcher {+    stop = cancel d+  }+  where+    disp = do+      atomically $ do+        envelope <- readMailbox $ endpointOutbound endpoint+        let name = messageDestination envelope+            msg = envelopeMessage envelope+        dispatchMessage mailboxes name msg+      disp +{-|+A mutable 'Map' of 'Name's to 'Mailbox'es of 'Message's. -}-data Envelope = Envelope {-  envelopeDestination :: Name,-  envelopeContents :: Message-  } deriving (Eq,Show,Generic)+type Mailboxes = TVar (M.Map Name (Mailbox Message)) -instance Serialize Envelope+{-|+Pull a 'Message' intended for a destination name from a 'Mailboxes' directly,+without the use of a 'Transport'+-}+pullMessage :: Mailboxes -> Name -> STM Message+pullMessage mailboxes destination = do+  outbound <- readTVar mailboxes+  case M.lookup destination outbound of+    Nothing -> retry+    Just mailbox -> readMailbox mailbox  {-|-A 'Mailbox' is a place where transports can put messages for 'Network.Endpoint.Endpoint's-to receive.  Typically 'Network.Endpoint.Endpoint's will use the same 'Mailbox' when-binding or connecting with a 'Transport'.+A simple function to multiplex messages (each wrapped in an 'Envelope') in the 'endpointOutbound'+mailbox of an 'Endpoint' to one or more 'Mailboxes' by extract the 'messageDestination' from the 'Envelope' and+finding or creating a 'Mailbox' containing messages only for that destination. This is often useful for 'Transport's,+who then only have to monitor a specific 'Mailbox' to know when there are messages to send to a particular destination. -}+dispatchMessage :: Mailboxes -> Name -> Message -> STM ()+dispatchMessage mailboxes name message = do+  outbound <- readTVar mailboxes+  mailbox <- case M.lookup name outbound of+    Nothing -> do+      mailbox <- newMailbox+      modifyTVar mailboxes $ M.insert name mailbox+      return mailbox+    Just mailbox -> return mailbox+  writeMailbox mailbox message  {-|-An address is a logical identifier suitable for establishing a connection to-another 'Endpoint' over a 'Transport'. It's use (if at all) is specific to the 'Transport'-in question.+Within the body of the function, ensures that 'Message's are dispatched as necessary. -}-type Address = String+withTransport :: IO Transport -> (Transport -> IO ()) -> IO ()+withTransport factory fn = do+  transport <- factory+  finally (fn transport)+   (shutdown transport)  {-|-A scheme is an identifier for a discrete type of transport.+Within the body of the function, ensure that there is a 'Dispatcher' for the 'Endpoint'.+ -}-type Scheme = String+withEndpoint :: Transport -> Endpoint -> IO ()  -> IO ()+withEndpoint transport endpoint fn = do+  d <- dispatch transport endpoint+  finally fn+    (stop d)  {-|-Bindings are a site for receiving messages on a particular 'Address'+Bindings are a site for receiving messages on a particular 'Name' through a 'Transport'. -} data Binding = Binding {@@ -108,35 +198,91 @@   }  {-|-A 'Transport' defines a specific method for establishing connections-between 'Endpoint's.+A helper for ensuring there is a 'Binding' of a specific 'Endpoint' to a specific 'Name'+on the provided 'Transport' during a function.+ -}-data Transport = Transport {-  scheme :: String,-  handles :: Name -> IO Bool,-  bind :: Mailbox Message -> Name -> IO (Either String Binding),-  sendTo :: Name -> Message -> IO (),-  shutdown :: IO ()-  }+withBinding :: Transport -> Endpoint -> Name -> IO () -> IO ()+withBinding transport endpoint name actor = do+  atomically $ do+    bindings <- readTVar $ boundEndpointNames endpoint+    if S.member name bindings+      then throw $ BindingExists name+      else modifyTVar (boundEndpointNames endpoint) $ S.insert name+  binding <- bind transport endpoint name+  finally actor $ do+    unbind binding+    atomically $ modifyTVar (boundEndpointNames endpoint) $ S.delete name  {-|-A 'Resolver' translates a name into an 'Address', if possible. -'Transport's may find resolvers useful for determing-where to reach a specific 'Endpoint', given it''s 'Name'.+A helper for ensuring there are 'Binding's of a specific 'Endpoint' to specific 'Name's+on the provided 'Transport' during a function. -}-newtype Resolver = Resolver (Name -> IO (Maybe Address))+withBinding2 :: Transport -> (Endpoint,Name) -> (Endpoint,Name) -> IO () -> IO ()+withBinding2 transport (endpoint1,name1) (endpoint2,name2) fn =+  withBinding transport endpoint1 name1 $+    withBinding transport endpoint2 name2 fn  {-|-Ask the 'Resolver' to find one or more 'Address'es for the provided-'Name', if any are available from this resolver.+A helper for ensuring there are 'Binding's of a specific 'Endpoint' to specific 'Name's+on the provided 'Transport' during a function.+-}+withBinding3 :: Transport -> (Endpoint,Name) -> (Endpoint,Name) -> (Endpoint,Name) -> IO () -> IO ()+withBinding3 transport (endpoint1,name1) (endpoint2,name2) (endpoint3,name3) fn =+  withBinding transport endpoint1 name1 $+    withBinding transport endpoint2 name2 $+      withBinding transport endpoint3 name3 fn +{-|+A helper for ensuring there are 'Binding's of a specific 'Endpoint' to specific 'Name's+on the provided 'Transport' during a function. -}-resolve :: Resolver -> Name -> IO (Maybe Address)-resolve (Resolver resolver) name = resolver name+withBinding4 :: Transport -> (Endpoint,Name) -> (Endpoint,Name) -> (Endpoint,Name) -> (Endpoint,Name) -> IO () -> IO ()+withBinding4 transport (endpoint1,name1) (endpoint2,name2) (endpoint3,name3) (endpoint4,name4) fn =+  withBinding transport endpoint1 name1 $+    withBinding transport endpoint2 name2 $+      withBinding transport endpoint3 name3 $+        withBinding transport endpoint4 name4 fn  {-|-A simple 'Resolver' that accepts an association list of 'Name's to 'Address'es-and returns the addresses associated with a given name in the list.+Connections are bi-directional pathways for exchanging 'Message's with another 'Endpoint'+that is bound to a specific 'Name' on a shared 'Transport'. -}-resolverFromList :: [(Name,Address)] -> Resolver-resolverFromList addresses = Resolver (\name -> return $ lookup name addresses)+data Connection = Connection {+  disconnect :: IO ()+}++{-|+A helper for ensuring that a 'Connection' is maintained during execution of a function.+-}+withConnection :: Transport -> Endpoint -> Name -> IO () -> IO ()+withConnection transport endpoint name fn = do+  connection <- connect transport endpoint name+  finally fn $ disconnect connection++{-|+A helper for ensuring that 2 'Connection's are maintained during execution of a function.+-}+withConnection2 :: Transport -> Endpoint -> Name -> Name -> IO () -> IO ()+withConnection2 transport endpoint name1 name2 fn =+  withConnection transport endpoint name1 $+    withConnection transport endpoint name2 fn++{-|+A helper for ensuring that 3 'Connection's are maintained during execution of a function.+-}+withConnection3 :: Transport -> Endpoint -> Name -> Name -> Name -> IO () -> IO ()+withConnection3 transport endpoint name1 name2 name3 fn =+  withConnection transport endpoint name1 $+    withConnection transport endpoint name2 $+      withConnection transport endpoint name3 fn++{-|+A helper for ensuring that 4 'Connection's are maintained during execution of a function.+-}+withConnection4 :: Transport -> Endpoint -> Name -> Name -> Name -> Name -> IO () -> IO ()+withConnection4 transport endpoint name1 name2 name3 name4 fn =+  withConnection transport endpoint name1 $+    withConnection transport endpoint name2 $+      withConnection transport endpoint name3 $+        withConnection transport endpoint name4 fn
− src/Network/Transport/Internal.hs
@@ -1,46 +0,0 @@--------------------------------------------------------------------------------- |--- Module      :  Network.Transport.Internal--- Copyright   :  (c) Phil Hargett 2013--- License     :  MIT (see LICENSE file)--- --- Maintainer  :  phil@haphazardhouse.net--- Stability   :  experimental--- Portability :  non-portable (uses STM)------ Internal definitions used for implementing transports.--------------------------------------------------------------------------------module Network.Transport.Internal (-    catchExceptions-)--where---- local imports--- external imports--import Control.Exception---------------------------------------------------------------------------------------------------------------------------------------------------------------------{-|-Silently ignore 'BlockedIndefinitelyOnSTM' and 'ThreadKilled' exceptions, as-most cases in which they occur are benign and not worth the noise.  Use with -caution, as this function could mask undelrying design issues.--}-catchExceptions :: IO () -> (SomeException -> IO ()) -> IO ()-catchExceptions blk handler = do-    catch (do-        catch (catch blk ignoreBlocked)-            ignoreThreadKilled)-        handler-    where-        ignoreBlocked :: BlockedIndefinitelyOnSTM -> IO ()-        ignoreBlocked _ = return ()-        ignoreThreadKilled :: AsyncException -> IO ()-        ignoreThreadKilled e =-            case e of-                ThreadKilled -> return ()-                _ -> handler $ SomeException e
src/Network/Transport/Memory.hs view
@@ -3,7 +3,7 @@ -- Module      :  Network.Transport.Memory -- Copyright   :  (c) Phil Hargett 2013 -- License     :  MIT (see LICENSE file)--- +-- -- Maintainer  :  phil@haphazardhouse.net -- Stability   :  experimental -- Portability :  non-portable (requires STM)@@ -11,7 +11,7 @@ -- Memory transports deliver messages to other 'Network.Endpoints.Endpoint's within the same shared -- address space, or operating system process. --- Internally memory transports use a set of 'TQueue's to deliver messages to 'Network.Endpoint.Endpoint's. +-- Internally memory transports use a set of 'TQueue's to deliver messages to 'Network.Endpoint.Endpoint's. -- Memory transports are not global in nature: 'Network.Endpoint.Endpoint's can only communicate with -- one another if each has added the same memory 'Transport' and each invoked 'bind' on that shared -- transport.@@ -19,66 +19,78 @@ -----------------------------------------------------------------------------  module Network.Transport.Memory (-  newMemoryTransport-  ) where+  newMemoryTransport, +  module Network.Transport++) where+ -- local imports +import Control.Concurrent.Mailbox+import Network.Endpoints import Network.Transport  -- external imports +import Control.Concurrent.Async import Control.Concurrent.STM+import Control.Exception+ import qualified Data.Map as M  -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -memoryScheme :: Scheme-memoryScheme = "mem"--data MemoryTransport = MemoryTransport {-  boundMailboxes :: TVar (M.Map Name (Mailbox Message))-  }-                        {-| Create a new memory 'Transport' for use by 'Network.Endpoint.Endpoint's. -} newMemoryTransport :: IO Transport-newMemoryTransport = do -  bindings <- atomically $ newTVar M.empty-  let transport = MemoryTransport {-        boundMailboxes = bindings-        }+newMemoryTransport = do+  vBindings <- atomically $ newTVar M.empty   return Transport {-      scheme = memoryScheme,-      handles = memoryHandles transport,-      bind = memoryBind transport,-      sendTo = memorySendTo transport,+      bind = memoryBind vBindings,+      dispatch = memoryDispatcher vBindings,+      connect = memoryConnect,       shutdown = return ()       } -memoryBind :: MemoryTransport -> Mailbox Message -> Name -> IO (Either String Binding)-memoryBind transport mailbox name = do-  atomically $ modifyTVar (boundMailboxes transport) -    (\mailboxes -> M.insert name mailbox mailboxes)-  return $ Right Binding {-    bindingName = name,-    unbind = memoryUnbind transport name-    }-                                       -memoryHandles :: MemoryTransport -> Name -> IO Bool--- memoryHandles transport name = True-memoryHandles _ _ = return True+memoryDispatcher :: Bindings -> Endpoint -> IO Dispatcher+memoryDispatcher vBindings endpoint = do+  d <- async disp+  return Dispatcher {+    stop = cancel d+  }+  where+    disp = do+      atomically $ do+        bindings <- readTVar vBindings+        env <- readMailbox $ endpointOutbound endpoint+        case M.lookup (messageDestination env) bindings  of+          Nothing -> return ()+          Just destination -> postMessage destination (envelopeMessage env)+      disp -memorySendTo :: MemoryTransport -> Name -> Message -> IO ()-memorySendTo transport name msg = do-  mailboxes <- atomically $ readTVar $ boundMailboxes transport-  case M.lookup name mailboxes of-    Just mailbox -> atomically $ writeMailbox mailbox msg-    Nothing -> return () -- error $ "No mailbox for " ++ name+memoryBind :: Bindings -> Endpoint -> Name -> IO Binding+memoryBind vBindings endpoint name = atomically $ do+  bindings <- readTVar vBindings+  case M.lookup name bindings of+    Nothing -> do+      modifyTVar vBindings $ M.insert name endpoint+      return Binding {+        bindingName = name,+        unbind = memoryUnbind vBindings endpoint name+      }+    Just _ -> throw $ BindingExists name -memoryUnbind :: MemoryTransport -> Name -> IO ()-memoryUnbind transport name = do-  atomically $ modifyTVar (boundMailboxes transport) deleteBinding-  where deleteBinding m = M.delete name m+memoryUnbind :: Bindings -> Endpoint -> Name -> IO ()+memoryUnbind vBindings _ name = atomically $ do+  modifyTVar vBindings $ M.delete name++type Bindings = TVar (M.Map Name Endpoint)++memoryConnect :: Endpoint -> Name -> IO Connection+memoryConnect _ _ =+  return Connection {+    disconnect = return ()+  }
src/Network/Transport/Sockets.hs view
@@ -1,512 +1,229 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-}  ----------------------------------------------------------------------------- -- | -- Module      :  Network.Transport.Sockets--- Copyright   :  (c) Phil Hargett 2013+-- Copyright   :  (c) Phil Hargett 2015 -- License     :  MIT (see LICENSE file)--- +-- -- Maintainer  :  phil@haphazardhouse.net -- Stability   :  experimental--- Portability :  non-portable (uses STM)+-- Portability :  non-portable (requires STM) ----- Common facilities for socket-based transports, such as UDP and TCP/IP.+-- Helpers for socket transports. -- ------------------------------------------------------------------------------ module Network.Transport.Sockets (--    Bindings,--    newSocketBindings,-    SocketBindings,-    bindAddress,-    unbindAddress,-    closeBindings,--    SocketTransport(..),-    SocketConnectionFactory,-    SocketMessengerFactory,-    SocketBindingFactory,-    newSocketTransport,--    Connection(..),+  SocketConnection(..),+  Connections,+  socketConnect, -    IdentifyMessage(..),+  socketListen, -    Messenger(..),-    newMessenger,-    addMessenger,-    replaceMessenger,-    deliver,-    closeMessenger,+  messenger,+  connector, -    dispatcher,-    sender,-    socketSendTo,-    receiver,-    receiveSocketBytes,-    receiveSocketMessage,-    receiveSocketMessages,-    SocketSend,+  Resolver,+  ResolverException,+  resolve1,+  socketResolver4,+  socketResolver6,+  wildcard, -    parseSocketAddress,-    lookupAddresses,-    lookupAddress,-    lookupWildcardAddress+  module Network.Transport -  ) where+) where  -- local imports +import Network.Endpoints import Network.Transport-import Network.Transport.Internal  -- external imports+ import Control.Concurrent.Async import Control.Concurrent.STM- import Control.Exception+import Control.Monad -import qualified Data.ByteString as B import qualified Data.Map as M-import Data.Serialize import qualified Data.Set as S-import qualified Data.Text as T+import Data.Serialize+import Data.Typeable  import GHC.Generics -import Network.Socket hiding (recv,socket,bind,sendTo,shutdown)-import qualified Network.Socket.ByteString as NSB--import System.Log.Logger+import qualified Network.Socket as NS  -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -_log :: String-_log = "transport.sockets"+type Connect = Endpoint -> Name -> IO SocketConnection -type SocketConnectionFactory = Address -> IO Connection-type SocketMessengerFactory = Bindings -> Resolver -> Connection -> Mailbox Message -> IO Messenger-type SocketBindingFactory = SocketTransport -> SocketBindings -> Mailbox Message -> Name -> IO (Either String Binding)+{-|+A 'Resolver' translates a name into a list of 'NS.SockAddr' for use with a socket-based 'Transport'.+-}+type Resolver = Name -> IO [NS.SockAddr] -newSocketTransport :: Resolver -> Scheme -> SocketBindingFactory -> SocketConnectionFactory -> SocketMessengerFactory -> IO Transport-newSocketTransport resolver socketScheme binder connectionFactory messengerFactory = do-  messengers <- atomically $ newTVar M.empty-  bindings <- atomically $ newTVar M.empty-  sockets <- newSocketBindings-  inbound <- atomically $ newMailbox-  dispatch <- async $ dispatcher bindings inbound-  let transport = SocketTransport {-        socketMessengers = messengers,-        socketBindings = bindings,-        socketConnection = connectionFactory,-        socketMessenger = messengerFactory bindings resolver,-        socketInbound = inbound,-        socketDispatchers = S.fromList [dispatch],-        socketResolver = resolver-        }-  return Transport {-      scheme = socketScheme,-      handles = socketTransportHandles transport,-      bind = binder transport sockets,-      sendTo = socketSendTo transport,-      shutdown = socketTransportShutdown transport sockets-      } -type Bindings = TVar (M.Map Name (Mailbox Message))--data SocketBinding = SocketBinding {-    socketCount :: TVar Int,-    socketSocket :: TMVar Socket,-    socketListener :: TMVar (Async ())-}--socketTransportHandles :: SocketTransport -> Name -> IO Bool-socketTransportHandles transport name = do -  resolved <- resolve (socketResolver transport) name-  return $ isJust resolved-  where-    isJust (Just _) = True-    isJust _ = False--type SocketBindings = TVar (M.Map Address SocketBinding)--newSocketBindings :: IO SocketBindings-newSocketBindings = atomically $ newTVar M.empty--bindAddress :: SocketBindings -> Address -> IO (Socket,Async ()) -> IO ()-bindAddress bindings address factory = do-    (count,binding) <- atomically $ do-        bmap <- readTVar bindings-        binding <- case M.lookup address bmap of-            Nothing -> do-                count <- newTVar 1-                listener <- newEmptyTMVar-                sock <- newEmptyTMVar-                let binding = SocketBinding {-                    socketCount = count,-                    socketListener = listener,-                    socketSocket = sock-                    }-                modifyTVar bindings $ \bs -> M.insert address binding bs-                return binding-            Just binding -> do-                modifyTVar (socketCount binding) $ \c -> c + 1-                return binding-        count <- readTVar $ socketCount binding-        return (count,binding)-    if count == 1-        then do-            infoM _log $ "Opening binding for " ++ (show address)-            (sock,listener) <- factory-            infoM _log $ "Opened binding for " ++ (show address)-            atomically $ do-                putTMVar (socketSocket binding) sock-                putTMVar (socketListener binding) listener-            return ()-        else return ()--unbindAddress :: SocketBindings -> Address -> IO ()-unbindAddress bindings address = do-    (count,maybeBinding) <- atomically $ do-        bmap <- readTVar bindings-        case M.lookup address bmap of-            Nothing -> return (0,Nothing)-            Just binding -> do-                modifyTVar (socketCount binding) $ \count -> count - 1-                count <- readTVar (socketCount binding)-                if count == 0-                    then do-                        modifyTVar bindings $ \bm -> M.delete address bm-                        sock <- takeTMVar $ socketSocket binding-                        listener <- takeTMVar $ socketListener binding-                        return (0,Just (sock,listener))-                    else return (count,Nothing)-    case maybeBinding of-        -- no binding to shutdown; can just return-        Nothing -> do-            infoM _log $ "No binding to shutdown for " ++ (show address) ++ "; count is " ++ (show count)-            return ()-        -- we're the last, so close the binding-        Just (sock,listener) -> do-            infoM _log $ "Closing binding for " ++ (show address) ++ "; count is " ++ (show count)-            cancel listener-            sClose sock-            infoM _log $ "Closed binding for " ++ (show address)--data SocketTransport = SocketTransport {-  socketMessengers :: TVar (M.Map Address Messenger),-  socketBindings :: Bindings,-  socketConnection :: Address -> IO Connection,-  socketMessenger :: Connection -> Mailbox Message -> IO Messenger,-  socketInbound :: Mailbox Message,-  socketDispatchers :: S.Set (Async ()),-  socketResolver :: Resolver+{-|+A 'SocketConnection' encapsulates the state of a single outbound connection.+-}+data SocketConnection = SocketConnection {+  connectionDestination :: TMVar Name,+  sendSocketMessage :: Message -> IO (),+  receiveSocketMessage :: IO Message,+  disconnectSocket :: IO () }  {-|-A connection specializes the use of a transport for a particular-destination.+Exceptions thrown by a 'Resolver'. -}-data Connection = Connection {-  connAddress :: Address,-  connSocket :: TMVar Socket,-  connConnect :: IO Socket,-  connSend :: Socket -> B.ByteString -> IO (),-  connReceive :: Socket -> Int -> IO (Maybe B.ByteString),-  connClose :: IO ()-  }+data ResolverException = CannotResolveName Name+  deriving (Show,Typeable) +instance Exception ResolverException+ {-|-A messenger is a facility that actual uses the mechanisms of a transport-(and more specifically, of a connection on a transport) to deliver and receive-messages. The messenger uses 'Mailbox'es internally so that the sending/receiving-happens asynchronously, allowing applications to move on without regard for-when any send / receive action actually completes.+Socket-based protocols need a way to unambiguously identify who is communicating on a particular 'SocketConnection';+this type accomodates both identifying the sender and sending an actual 'Message'. -}-data Messenger = Messenger {-    messengerDone :: TVar Bool,-    messengerOut :: Mailbox Message,-    messengerAddress :: Address,-    messengerSender :: Async (),-    messengerReceiver :: Async (),-    messengerConnection :: Connection-    }+data SocketMessage =+  IdentifySender Name+  | SocketMessage Message+  deriving (Generic) -data IdentifyMessage = IdentifyMessage Address deriving (Generic)+instance Serialize SocketMessage -instance Serialize IdentifyMessage+{-|+Helper to resolve a 'Name' to a single 'NS.SockAddr' for use with a socket-based 'Transport'.+-}+resolve1 :: Resolver -> Name -> IO NS.SockAddr+resolve1 resolve name = do+  addresses <- resolve name+  case addresses of+    [] -> throw $ CannotResolveName name+    (address:_) -> return address  {-|-Parse a TCP 'Address' into its respective 'HostName' and 'PortNumber' components, on the-assumption the 'Address' has an identifer in the format @host:port@. If-the port number is missing from the supplied address, it will default to 0.  If the-hostname component is missing from the identifier (e.g., just @:port@), then hostname-is assumed to be @localhost@.+Simple 'Resolver' for socket-based 'Transport's that just parse's the 'String' inside the 'Name', with the expected+format "host:port" -}-parseSocketAddress :: Address -> (HostName,ServiceName)-parseSocketAddress address = -  let identifer = T.pack $ address -      parts = T.splitOn ":" identifer-  in if (length parts) > 1 then-       (host $ T.unpack $ parts !! 0, port $ T.unpack $ parts !! 1)-     else (host $ T.unpack $ parts !! 0, "0")+socketResolver :: NS.Family -> NS.SocketType -> Name -> IO [NS.SockAddr]+socketResolver family socketType name =+  let (host,port) = split nm+      Name nm = name+      hints = NS.defaultHints { NS.addrFlags = [NS.AI_ADDRCONFIG, NS.AI_CANONNAME, NS.AI_NUMERICSERV] }+  in do+      addresses <- NS.getAddrInfo (Just hints) (Just host) (Just port)+      return $ map NS.addrAddress $ filter (\addrInfo -> NS.addrFamily addrInfo == family && NS.addrSocketType addrInfo == socketType) addresses   where-    host h = if h == "" then-               "localhost"-             else h-    port p = p--lookupAddresses :: Family -> SocketType -> Address -> IO [SockAddr]-lookupAddresses family socketType address =-    let (host,port) = parseSocketAddress address-        hints = defaultHints { addrFlags = [AI_ADDRCONFIG, AI_CANONNAME, AI_NUMERICSERV] }-    in do-        addresses <- getAddrInfo (Just hints) (Just host) (Just port)-        return $ map addrAddress $ filter (\addrInfo -> addrFamily addrInfo == family && addrSocketType addrInfo == socketType) addresses--lookupAddress :: Family -> SocketType -> Address -> IO SockAddr-lookupAddress family socketType address = do-    addresses <- lookupAddresses family socketType address-    return $ addresses !! 0--lookupWildcardAddress :: Family -> SocketType -> Address -> IO SockAddr-lookupWildcardAddress family socketType address = do-    sockAddr <- lookupAddress family socketType address-    case sockAddr of-        SockAddrInet port _ -> return $ SockAddrInet port iNADDR_ANY-        SockAddrInet6 port flow _ scope -> return $ SockAddrInet6 port flow iN6ADDR_ANY scope-        _ -> return sockAddr--type SocketSend = Socket -> B.ByteString -> IO ()--instance Show Messenger where-  show msngr = "Messenger(" ++ (show $ messengerAddress msngr) ++ ")"--newMessenger :: Connection -> Mailbox Message -> IO Messenger-newMessenger conn inc = do-  out <- atomically $ newMailbox-  done <- atomically $ newTVar False-  sndr <- async $ sender conn done out-  rcvr <- async $ receiver conn done inc-  return Messenger {-    messengerDone = done,-    messengerOut = out,-    messengerAddress = connAddress conn,-    messengerSender = sndr,-    messengerReceiver = rcvr,-    messengerConnection = conn-    }--addMessenger :: SocketTransport -> Address -> Messenger -> IO ()-addMessenger transport address msngr = do-  msngrs <- atomically $ do-        modifyTVar (socketMessengers transport) $ \msngrs -> M.insert address msngr msngrs-        msngrs <- readTVar (socketMessengers transport)-        return msngrs-  infoM _log $ "Added messenger to " ++ (show address) ++ "; messengers are " ++ (show msngrs)--replaceMessenger :: SocketTransport -> Address -> Messenger -> IO ()-replaceMessenger transport address msngr = do-    found <- atomically $ do-        msngrs <- readTVar $ socketMessengers transport-        return $ M.lookup address msngrs-    case found of-        Just _ -> do-              infoM _log $ "Already have messenger for " ++ (show address)-              closeMessenger msngr-        Nothing -> do-            addMessenger transport address msngr+    split [] = ("","")+    split (w:ws) = case w of+      ':' -> ("",ws)+      _ -> let (w',ws') = split ws+        in (w:w',ws') +{-|+Variation of 'socketResolve' for standard IP addresses.+-}+socketResolver4 :: NS.SocketType -> Name -> IO [NS.SockAddr]+socketResolver4 = socketResolver NS.AF_INET +{-|+Variation of 'socketResolve' for standard IPv6 addresses.+-}+socketResolver6 ::NS.SocketType ->  Name -> IO [NS.SockAddr]+socketResolver6 = socketResolver NS.AF_INET6 -deliver :: Messenger -> Message -> IO ()-deliver msngr message = atomically $ writeMailbox (messengerOut msngr) message+{-|+Returns the wildcard 'NS.SockAddr' for the specified 'NS.SockAddr', if possible.+-}+wildcard :: NS.SockAddr -> IO NS.SockAddr+wildcard address =+    case address of+        NS.SockAddrInet port _ -> return $ NS.SockAddrInet port NS.iNADDR_ANY+        NS.SockAddrInet6 port flow _ scope -> return $ NS.SockAddrInet6 port flow NS.iN6ADDR_ANY scope+        _ -> return address -dispatcher :: TVar (M.Map Name (Mailbox Message)) -> Mailbox Message -> IO ()-dispatcher bindings mbox = dispatchMessages-  where-    dispatchMessages = catchExceptions (do-                                 infoM _log $ "Dispatching messages"-                                 env <- atomically $ readMailbox mbox-                                 dispatchMessage env-                                 dispatchMessages)-                       (\e -> do-                           warningM _log $ "Dispatch error: " ++ (show (e :: SomeException)))-    dispatchMessage env = do-      infoM _log $ "Dispatching message"-      let envelopeOrErr = decode env-      case envelopeOrErr of-        Left err -> do-          errorM _log $ "Error decoding message for dispatch: " ++ err-          return ()-        Right (Envelope destination msg) -> do -          atomically $ do -            dests <- readTVar bindings-            let maybeDest = M.lookup destination dests-            case maybeDest of-              Nothing -> return ()-              Just dest -> do -                writeMailbox dest msg-                return ()+{-|+Listens for new connections on a new 'NS.Socket', and return the 'NS.Socket'.+-}+socketListen :: NS.Family -> NS.SocketType -> Resolver -> Name -> IO NS.Socket+socketListen family socketType resolver name = do+  address <- resolve1 resolver name+  socket <- NS.socket family socketType NS.defaultProtocol+  NS.setSocketOption socket NS.NoDelay 1+  NS.setSocketOption socket NS.ReuseAddr 1+  when (NS.isSupportedSocketOption NS.ReusePort)+    $ NS.setSocketOption socket NS.ReusePort 1+  wildcard address >>= NS.bind socket+  NS.listen socket 2048 -- TODO think about a configurable backlog+  return socket -sender :: Connection -> TVar Bool -> Mailbox Message -> IO ()-sender conn done mailbox = sendMessages-  where-    sendMessages = do-      reconnect-      catchExceptions (do-                infoM _log $ "Waiting to send to " ++ (show $ connAddress conn)-                msg <- atomically $ readMailbox mailbox-                infoM _log $ "Sending message to " ++ (show $ connAddress conn)-                connected <- atomically $ tryReadTMVar $ connSocket conn-                case connected of-                  Just socket -> do-                      (connSend conn) socket msg-                  Nothing -> return ()-            )-            (\e -> do-                warningM _log $ "Send error: " ++ (show (e :: SomeException))-                disconnect)-      isDone <- atomically $ readTVar done-      if isDone -        then return ()-        else sendMessages-    reconnect = do-      -- TODO need a timeout here, in case connecting always fails-      infoM _log $ "Reconnecting to " ++ (show $ connAddress conn)-      connected <- atomically $ tryReadTMVar $ connSocket conn-      case connected of-        Just _ -> do-          infoM _log $ "Reconnected to " ++ (show $ connAddress conn)-          return ()-        Nothing -> do-          let (host,port) = parseSocketAddress $ connAddress conn-          infoM _log $ "Connecting to " ++ (show host) ++ ":" ++ (show port) -- (show address)-          socket <- connConnect conn-          infoM _log $ "Connected to " ++ (show $ connAddress conn)-          atomically $ putTMVar (connSocket conn) socket-    disconnect = do-      connected <- atomically $ tryTakeTMVar $ connSocket conn-      case connected of-        Just socket -> sClose socket-        Nothing -> return ()+{-|+Mutble 'M.Map' of 'Name's to 'Connection's.+-}+type Connections = TVar (M.Map Name Connection) +{-|+Establish a 'connector' for exchanging messages from 'Endpoint' with a destination specified by 'Name'.+-}+socketConnect :: Mailboxes -> Connect -> Endpoint -> Name  -> IO Connection+socketConnect mailboxes sConnect endpoint name = do+  connr <- async $ connector mailboxes endpoint name sConnect+  let conn = Connection {+    disconnect = cancel connr+  }+  return conn -socketSendTo :: SocketTransport -> Name -> Message -> IO ()-socketSendTo transport name msg = do-  isLocal <- local-  if isLocal-    then return ()-    else remote+{-|+Maintain a 'Connection' between an 'Endpoint' and a destination specified by 'Name'. Should the connection,+the connection will automatically be re-attempted.+-}+connector :: Mailboxes -> Endpoint -> Name -> Connect -> IO ()+connector mailboxes endpoint name transportConnect = loopUntilKilled $ do+  connection <- transportConnect endpoint name+  origins <- atomically $ do+    putTMVar (connectionDestination connection) name+    readTVar $ boundEndpointNames endpoint+  forM_ (S.elems origins) $ \origin ->+    -- atomically $ dispatchMessage mailboxes name $ encode $ IdentifySender origin+    sendSocketMessage connection $ encode $ IdentifySender origin+  finally (messenger mailboxes endpoint connection) $+    disconnectSocket connection   where-    local = do-      found <- atomically $ do-        bindings <- readTVar $ socketBindings transport-        return $ M.lookup name bindings-      case found of-        Nothing -> return False-        Just mbox -> do-          atomically $ writeMailbox mbox msg-          return True-    remote = do-      Just address <- resolve (socketResolver transport) name-      let env = encode $ Envelope {-            envelopeDestination = name,-            envelopeContents = msg-            }-      amsngr <- atomically $ do-        msngrs <- readTVar $ socketMessengers transport-        return $ M.lookup address msngrs-      case amsngr of-        Nothing -> do-          msngrs <- atomically $ readTVar $ socketMessengers transport-          infoM _log $ "No messenger for " ++ (show address) ++ " in " ++ (show msngrs)-          socketVar <- atomically $ newEmptyTMVar-          newConn <- (socketConnection transport) address-          let conn = newConn {connSocket = socketVar}-          msngr <- (socketMessenger transport) conn (socketInbound transport)-          addMessenger transport address msngr-          deliver msngr env-          return ()-        Just msngr -> deliver msngr env--receiver :: Connection -> TVar Bool -> Mailbox Message -> IO ()-receiver conn done mailbox  = do -    socket <- atomically $ readTMVar $ connSocket conn-    receiveSocketMessages socket done (connAddress conn) mailbox--receiveSocketMessages :: Socket -> TVar Bool -> Address -> Mailbox Message -> IO ()-receiveSocketMessages sock done addr mailbox = do -    catchExceptions (do-          infoM _log $ "Waiting to receive on " ++ (show addr)-          maybeMsg <- receiveSocketMessage sock-          infoM _log $ "Received message on " ++ (show addr)-          case maybeMsg of-            Nothing -> do-              sClose sock-              return ()-            Just msg -> do-              atomically $ writeMailbox mailbox msg-          isDone <- atomically $ readTVar done-          if isDone-            then return ()-            else receiveSocketMessages sock done addr mailbox)-          (\e -> do-              isDone <- atomically $ readTVar done-              if isDone-                then return ()-                -- Dropping this message to info, as even well-behaved applications-                -- may generate it...even though it is benign-                else infoM _log $ "Receive error: " ++ (show (e :: SomeException)))+    loopUntilKilled fn =+      catch (catch fn untilKilled)+        (loop fn)+    loop :: IO () -> SomeException -> IO ()+    loop fn _ = loopUntilKilled fn+    untilKilled :: AsyncException -> IO ()+    untilKilled _ = return () -receiveSocketMessage :: Socket -> IO (Maybe B.ByteString)-receiveSocketMessage socket = do-  maybeLen <- receiveSocketBytes socket 8 -- TODO must figure out what defines length of an integer in bytes -  case maybeLen of-    Nothing -> do-      infoM _log $ "No length received"-      return Nothing-    Just len -> do -      maybeMsg <- receiveSocketBytes socket $ msgLength (decode len)-      infoM _log $ "Received message"-      return maybeMsg+{-|+Exchange 'Messages' with the 'Endpoint' on the other side of a 'SocketConnection'.+-}+messenger :: Mailboxes -> Endpoint -> SocketConnection -> IO ()+messenger mailboxes endpoint connection =+  -- counting on race_ to kill reader & writer+  -- if messenger is killed; since it uses withAsync, it should+  race_ receiver sender   where-    msgLength (Right size) = size-    msgLength (Left err) = error err--receiveSocketBytes :: Socket -> Int -> IO (Maybe B.ByteString)-receiveSocketBytes sock maxBytes = do-    bs <- NSB.recv sock maxBytes-    if B.null bs-        then return Nothing-        else return $ Just bs--closeMessenger :: Messenger -> IO ()-closeMessenger msngr = do-  infoM _log $ "Closing mesenger to " ++ (messengerAddress msngr)-  atomically $ modifyTVar (messengerDone msngr) (\_ -> True)-  cancel $ messengerSender msngr-  cancel $ messengerReceiver msngr-  connClose $ messengerConnection msngr-  infoM _log $ "Closed messenger to " ++ (messengerAddress msngr)--closeBindings :: SocketBindings -> IO ()-closeBindings sockets = do-  infoM _log $ "Closing bindings"-  bindings <- atomically $ readTVar sockets-  mapM_ (unbindAddress sockets)  $ M.keys bindings-  infoM _log $ "Closed bindings"--socketTransportShutdown :: SocketTransport -> SocketBindings -> IO ()-socketTransportShutdown transport sockets = do-  closeBindings sockets-  infoM _log $ "Closing messengers"-  msngrs <- atomically $ readTVar $ socketMessengers transport-  mapM_ closeMessenger $ M.elems msngrs-  infoM _log $ "Closing dispatcher"-  mapM_ cancel $ S.toList $ socketDispatchers transport-  mapM_ wait $ S.toList $ socketDispatchers transport+    receiver = do+      smsg <- receiveSocketMessage connection+      -- TODO consider a way of using a message to identify the name of+      -- the endpoint on the other end of the connection+      case decode smsg of+        Left _ -> return ()+        Right (IdentifySender name) -> atomically $ putTMVar (connectionDestination connection) name+        Right (SocketMessage msg) -> atomically $ postMessage endpoint msg+      receiver+    sender = do+      msg <- atomically $ do+        -- this basically means we wait until we have a name+        name <- readTMVar $ connectionDestination connection+        pullMessage mailboxes name+      sendSocketMessage connection $ encode $ SocketMessage msg+      sender
+ src/Network/Transport/Sockets/TCP.hs view
@@ -0,0 +1,163 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Network.Transport.Sockets.TCP+-- Copyright   :  (c) Phil Hargett 2015+-- License     :  MIT (see LICENSE file)+--+-- Maintainer  :  phil@haphazardhouse.net+-- Stability   :  experimental+-- Portability :  non-portable (requires STM)+--+-- TCP transport.+--+-----------------------------------------------------------------------------+module Network.Transport.Sockets.TCP (+  newTCPTransport4,+  newTCPTransport6,++  tcpSocketResolver4,+  tcpSocketResolver6,++  module Network.Transport+) where++-- local imports++import Network.Endpoints+import Network.Transport+import Network.Transport.Sockets++-- external imports++import Control.Concurrent.Async+import Control.Concurrent.STM+import Control.Exception+import Control.Monad++import qualified Data.ByteString as BS+import qualified Data.Map as M+import Data.Serialize++import qualified Network.Socket as NS+import qualified Network.Socket.ByteString as NSB++--------------------------------------------------------------------------------+--------------------------------------------------------------------------------++type SocketConnections = TVar (M.Map NS.SockAddr Connection)++newTCPTransport :: NS.Family -> Resolver -> IO Transport+newTCPTransport family resolver = atomically $ do+  vPeers <- newTVar M.empty+  mailboxes <- newTVar M.empty+  return Transport {+    bind = tcpBind mailboxes family resolver,+    dispatch = dispatcher mailboxes,+    connect =  socketConnect mailboxes $ tcpConnect family resolver,+    shutdown = tcpShutdown vPeers+  }++{-|+Create a 'Transport' for exchanging 'Message's between endpoints via TCP over IP+-}+newTCPTransport4 :: Resolver -> IO Transport+newTCPTransport4 = newTCPTransport NS.AF_INET++{-|+Create a 'Transport' for exchanging 'Message's between endpoints via TCP over IPv6+-}+newTCPTransport6 :: Resolver -> IO Transport+newTCPTransport6 = newTCPTransport NS.AF_INET6++{-|+Create a 'Resolve' for resolving 'Name's for use with TCP over IP.+-}+tcpSocketResolver4 :: Name -> IO [NS.SockAddr]+tcpSocketResolver4 = socketResolver4 NS.Stream++{-|+Create a 'Resolve' for resolving 'Name's for use with TCP over IPv6.+-}+tcpSocketResolver6 :: Name -> IO [NS.SockAddr]+tcpSocketResolver6 = socketResolver6 NS.Stream++tcpBind :: Mailboxes -> NS.Family -> Resolver -> Endpoint -> Name -> IO Binding+tcpBind mailboxes family resolver endpoint name = do+  vConnections <- atomically $ newTVar M.empty+  listener <- async $ tcpListen mailboxes family resolver vConnections endpoint name+  return Binding {+    bindingName = name,+    unbind = cancel listener+  }++tcpListen :: Mailboxes -> NS.Family -> Resolver -> SocketConnections -> Endpoint -> Name -> IO ()+tcpListen mailboxes family resolver vConnections endpoint name = do+  socket <- socketListen family NS.Stream resolver name+  finally (accept mailboxes socket vConnections endpoint)+    (tcpUnbind socket)++accept :: Mailboxes -> NS.Socket -> SocketConnections -> Endpoint -> IO ()+accept mailboxes socket vConnections endpoint = do+  (peer,peerAddress) <- NS.accept socket+  connection <- tcpConnection peer+  msngr <- async $ messenger mailboxes endpoint connection+  let conn = Connection {+    disconnect = cancel msngr+  }+  maybeOldConn <- atomically $ do+    connections <- readTVar vConnections+    let oldConn = M.lookup peerAddress connections+    modifyTVar vConnections $ M.insert peerAddress conn+    return oldConn+  case maybeOldConn of+    Just oldConn -> disconnect oldConn+    Nothing -> return ()+  accept mailboxes socket vConnections endpoint++tcpUnbind :: NS.Socket -> IO ()+tcpUnbind = NS.close++tcpConnect :: NS.Family -> Resolver -> Endpoint -> Name -> IO SocketConnection+tcpConnect family resolver _ name = do+  socket <- NS.socket family NS.Stream NS.defaultProtocol+  address <- resolve1 resolver name+  NS.connect socket address+  tcpConnection socket++tcpConnection :: NS.Socket -> IO SocketConnection+tcpConnection socket = do+  vName <- atomically newEmptyTMVar+  return SocketConnection {+    connectionDestination = vName,+    sendSocketMessage = tcpSend socket,+    receiveSocketMessage = tcpReceive socket,+    disconnectSocket = tcpDisconnect socket+  }++tcpSend :: NS.Socket -> Message -> IO ()+tcpSend socket message = do+  let len = BS.length message+  NSB.sendAll socket $ encode len+  NSB.sendAll socket message++tcpReceive :: NS.Socket -> IO Message+tcpReceive socket = readBytesWithLength+  where+    readBytesWithLength = do+      lengthBytes <- readBytes 8 -- TODO must figure out what defines length of an integer in bytes+      case decode lengthBytes of+        Left _ -> throw NoDataRead+        Right len -> readBytes len+    readBytes = NSB.recv socket++tcpDisconnect :: NS.Socket -> IO ()+tcpDisconnect = NS.close++tcpShutdown :: SocketConnections -> IO ()+tcpShutdown vPeers = do+  peers <- atomically $ readTVar vPeers+  -- this is how we disconnect incoming connections+  -- we don't have to disconnect  outbound connectinos, because+  -- they should already be disconnected before here+  forM_ (M.elems peers) disconnect+  return ()
+ src/Network/Transport/Sockets/UDP.hs view
@@ -0,0 +1,161 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Network.Transport.Sockets.UDP+-- Copyright   :  (c) Phil Hargett 2015+-- License     :  MIT (see LICENSE file)+--+-- Maintainer  :  phil@haphazardhouse.net+-- Stability   :  experimental+-- Portability :  non-portable (requires STM)+--+-- UDP transport.+--+-----------------------------------------------------------------------------+module Network.Transport.Sockets.UDP (+  newUDPTransport4,+  newUDPTransport6,++  udpSocketResolver4,+  udpSocketResolver6,++  module Network.Transport+) where++-- local imports++import Network.Endpoints+import Network.Transport+import Network.Transport.Sockets++-- external imports++import Control.Concurrent.Async+import Control.Concurrent.Mailbox+import Control.Concurrent.STM+import Control.Exception+import Control.Monad++import qualified Data.Map as M++import qualified Network.Socket as NS+import qualified Network.Socket.ByteString as NSB++--------------------------------------------------------------------------------+--------------------------------------------------------------------------------+type SocketConnections = TVar (M.Map NS.SockAddr Connection)++newUDPTransport :: NS.Family -> Resolver -> IO Transport+newUDPTransport family resolver = do+  socket <- NS.socket family NS.Datagram NS.defaultProtocol+  atomically $ do+    vPeers <- newTVar M.empty+    mailboxes <- newTVar M.empty+    vSocket <- newTMVar socket+    return Transport {+      bind = udpBind family resolver,+      dispatch = udpDispatcher vSocket resolver,+      connect = udpConnect mailboxes family resolver,+      shutdown = udpShutdown vPeers+    }++{-|+Create a 'Transport' for exchanging 'Message's between endpoints via UDP over IP+-}+newUDPTransport4 :: Resolver -> IO Transport+newUDPTransport4 = newUDPTransport NS.AF_INET++{-|+Create a 'Transport' for exchanging 'Message's between endpoints via UDP over IPv6+-}+newUDPTransport6 :: Resolver -> IO Transport+newUDPTransport6 = newUDPTransport NS.AF_INET6++{-|+Create a 'Resolve' for resolving 'Name's for use with UDP over IP.+-}+udpSocketResolver4 :: Name -> IO [NS.SockAddr]+udpSocketResolver4 = socketResolver4 NS.Datagram++{-|+Create a 'Resolve' for resolving 'Name's for use with UDP over IPv6.+-}+udpSocketResolver6 :: Name -> IO [NS.SockAddr]+udpSocketResolver6 = socketResolver6 NS.Datagram++udpBind :: NS.Family -> Resolver -> Endpoint -> Name -> IO Binding+udpBind family resolver endpoint name = do+  socket <- NS.socket family NS.Datagram NS.defaultProtocol+  address <- resolve1 resolver name+  NS.setSocketOption socket NS.ReuseAddr 1+  when (NS.isSupportedSocketOption NS.ReusePort)+    $ NS.setSocketOption socket NS.ReusePort 1+  NS.bindSocket socket address+  listener <- async $+    finally (receiver socket)+      (udpUnbind socket)+  return Binding {+    bindingName = name,+    unbind = cancel listener+  }+  where+    receiver socket = do+      msg <- udpReceive socket+      -- TODO consider a way of using a message to identify the name of+      -- the endpoint on the other end of the connection+      atomically $ postMessage endpoint msg+      receiver socket++udpDispatcher :: TMVar NS.Socket -> Resolver -> Endpoint -> IO Dispatcher+udpDispatcher vSocket resolver endpoint = do+  d <- async disp+  return Dispatcher {+    stop = cancel d+  }+  where+    disp = do+      (socket,name,msg) <- atomically $ do+        envelope <- readMailbox $ endpointOutbound endpoint+        let name = messageDestination envelope+            msg = envelopeMessage envelope+        socket <- readTMVar vSocket+        return (socket,name,msg)+      address <- resolve1 resolver name+      udpSend socket address msg+      disp++udpUnbind :: NS.Socket -> IO ()+udpUnbind = NS.close++udpConnect :: Mailboxes -> NS.Family -> Resolver -> Endpoint -> Name -> IO Connection+udpConnect mailboxes family resolver _ name = do+  socket <- NS.socket family NS.Datagram NS.defaultProtocol+  address <- resolve1 resolver name+  sender <- async $ finally (writer socket address) (udpDisconnect socket)+  return Connection {+    disconnect = cancel sender+  }+  where+    writer socket address = do+      msg <- atomically $ pullMessage mailboxes name+      udpSend socket address msg+      writer socket address++udpSend :: NS.Socket -> NS.SockAddr -> Message -> IO ()+udpSend socket address message = NSB.sendAllTo socket message address++udpReceive :: NS.Socket -> IO Message+udpReceive socket = do+  (msg,_) <- NSB.recvFrom socket 512+  return msg++udpDisconnect :: NS.Socket -> IO ()+udpDisconnect = NS.close++udpShutdown :: SocketConnections -> IO ()+udpShutdown vPeers = do+  peers <- atomically $ readTVar vPeers+  -- this is how we disconnect incoming connections+  -- we don't have to disconnect  outbound connectinos, because+  -- they should already be disconnected before here+  forM_ (M.elems peers) disconnect+  return ()
− src/Network/Transport/TCP.hs
@@ -1,203 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE DeriveGeneric #-}---------------------------------------------------------------------------------- |--- Module      :  Network.Transport.TCP--- Copyright   :  (c) Phil Hargett 2013--- License     :  MIT (see LICENSE file)--- --- Maintainer  :  phil@haphazardhouse.net--- Stability   :  experimental--- Portability :  non-portable (uses STM)------ TCP transports deliver messages to other 'Network.Endpoints.Endpoint's using TCP/IP.------ Each TCP transport manages both socket bindings and connections on behalf of--- 'Endpoint's, dynamically opening / closing new sockets as needed to deliver--- messages to other 'Endpoint's using TCP transports.--------------------------------------------------------------------------------module Network.Transport.TCP (-  newTCPTransport,-  newTCPTransport6,--  lookupAddresses,-  lookupTCPAddress,-  lookupWildcardTCPAddress-  ) where---- local imports--import Network.Transport-import Network.Transport.Internal-import Network.Transport.Sockets---- external imports--import Control.Concurrent.Async-import Control.Concurrent.STM-import Control.Exception--import qualified Data.ByteString as B-import qualified Data.Map as M-import Data.Serialize-import qualified Data.Set as S--import qualified Network.Socket as NS-import qualified Network.Socket.ByteString as NSB--import System.Log.Logger---------------------------------------------------------------------------------------------------------------------------------------------------------------------_log :: String-_log = "transport.tcp"--tcpScheme :: Scheme-tcpScheme = "tcp"--lookupTCPAddress :: Address -> NS.Family -> IO NS.SockAddr-lookupTCPAddress address family = lookupAddress family NS.Stream address--lookupWildcardTCPAddress :: Address -> NS.Family -> IO NS.SockAddr-lookupWildcardTCPAddress address family = lookupWildcardAddress family NS.Stream address--{-|-Create a new 'Transport' suitable for sending messages over TCP/IP (IPv4).  There can-be multiple instances of these 'Transport's: 'Network.Endpoints.Endpoint' using-different instances will still be able to communicate, provided they use-correct TCP/IP addresses (or hostnames) for communication.--}-newTCPTransport :: Resolver -> IO Transport-newTCPTransport resolver = let family = NS.AF_INET in newSocketTransport resolver tcpScheme (tcpBind family) (newTCPConnection family) newTCPMessenger--{-|-Create a new 'Transport' suitable for sending messages over TCP/IP (IPv4).  There can-be multiple instances of these 'Transport's: 'Network.Endpoints.Endpoint' using-different instances will still be able to communicate, provided they use-correct TCP/IP addresses (or hostnames) for communication.--}-newTCPTransport6 :: Resolver -> IO Transport-newTCPTransport6 resolver = let family = NS.AF_INET6 in newSocketTransport resolver tcpScheme (tcpBind family) (newTCPConnection family) newTCPMessenger--tcpBind :: NS.Family -> SocketTransport -> SocketBindings -> Mailbox Message -> Name -> IO (Either String Binding)-tcpBind family transport sockets inc name = do-    atomically $ modifyTVar (socketBindings transport) $ \ bindings ->-        M.insert name inc bindings-    Just address <- resolve (socketResolver transport) name-    bindAddress sockets address $ do-        sock <- NS.socket family NS.Stream NS.defaultProtocol-        listener <- do-            infoM _log $ "Binding to address " ++ (show address)-            tcpListen transport family address sock-        return (sock,listener)-    return $ Right Binding {-        bindingName = name,-        unbind = tcpUnbind sockets address-        }--tcpListen :: SocketTransport -> NS.Family -> Address -> NS.Socket -> IO (Async ())-tcpListen transport family address sock = do-            catchExceptions (do-                    NS.setSocketOption sock NS.NoDelay 1-                    NS.setSocketOption sock NS.ReuseAddr 1-                    sockAddr <- lookupWildcardTCPAddress address family-                    NS.bind sock sockAddr-                    NS.listen sock 2048) -- TODO think about a configurable backlog-                (\e -> do-                warningM _log $ "Listen error on port " ++ address ++ ": " ++ (show (e :: SomeException))-                NS.sClose sock)-            async $ tcpAccept transport family address sock--tcpAccept :: SocketTransport -> NS.Family -> Address -> NS.Socket -> IO ()-tcpAccept transport family address sock = do-    infoM _log $ "Listening for connections on " ++ (show address) ++ ": " ++ (show sock)-    (client,clientAddress) <- NS.accept sock-    _ <- async $ tcpDispatch transport family address client clientAddress-    tcpAccept transport family address sock--tcpDispatch :: SocketTransport -> NS.Family -> Address -> NS.Socket -> NS.SockAddr -> IO ()-tcpDispatch transport family address client socketAddress = do-  infoM _log $ "Accepted connection on " ++ (show address)-  identity <- tcpIdentify client socketAddress-  case identity of-        Nothing -> NS.sClose client-        Just (IdentifyMessage clientAddress) -> do-            infoM _log $ "Identified " ++ (show clientAddress)-            newConn <- (newTCPConnection family) clientAddress-            atomically $ putTMVar (connSocket newConn) client-            msngr <- newMessenger newConn (socketInbound transport)-            replaceMessenger transport clientAddress msngr--tcpIdentify :: NS.Socket -> NS.SockAddr -> IO (Maybe IdentifyMessage)-tcpIdentify client clientAddress = do-    infoM _log $ "Awaiting identity from " ++ (show clientAddress)-    maybeMsg <- receiveSocketMessage client-    case maybeMsg of-        Nothing -> return Nothing-        Just bytes -> do-            let msg = decode bytes-            case msg of-                Left _ -> return Nothing-                Right message -> return $ Just message--tcpUnbind :: SocketBindings -> Address -> IO ()-tcpUnbind sockets address = do-  infoM _log $ "Unbinding from TCP port " ++ (show address)-  unbindAddress sockets address-  infoM _log $ "Unbound from TCP port " ++ (show address)--newTCPConnection :: NS.Family -> Address -> IO Connection-newTCPConnection family address = do-    sock <- atomically $ newEmptyTMVar-    return Connection {-        connAddress = address,-        connSocket = sock,-        connConnect = do-            socket <- NS.socket family NS.Stream NS.defaultProtocol-            sockAddr <- lookupTCPAddress address family-            NS.connect socket sockAddr-            -- NS.connect socket $ NS.addrAddress $ head ipv4Addrs-            atomically $ putTMVar sock socket-            -- infoM _log $ "Initiated socket connection to " ++ (show $ head ipv4Addrs)-            return socket,-        connSend = tcpSend address,-        connReceive = receiveSocketBytes,-        connClose = do-            infoM _log $ "Closing connection to " ++ address-            open <- atomically $ tryTakeTMVar sock-            case open of-                Just socket -> do-                    infoM _log $ "Closing socket " ++ (show socket) ++ " for " ++ address-                    NS.sClose socket-                    infoM _log $ "Closed socket " ++ (show socket) ++ " for " ++ address-                Nothing -> do-                    infoM _log $ "No socket to close for " ++ address-                    return ()-            infoM _log $ "Connection to " ++ address ++ " closed"-        }--newTCPMessenger :: Bindings -> Resolver -> Connection -> Mailbox Message -> IO Messenger-newTCPMessenger bindings resolver conn mailbox = do-    msngr <- newMessenger conn mailbox-    identifyAll msngr-    return msngr-    where-        identifyAll msngr = do-            bs <- atomically $ readTVar bindings-            boundAddresses <- mapM (resolve resolver) (M.keys bs)-            let uniqueAddresses = S.toList $ S.fromList boundAddresses-            mapM_ (identify msngr) uniqueAddresses-        identify msngr maybeUniqueAddress= do-            case maybeUniqueAddress of-                Nothing -> return()-                Just uniqueAddress -> deliver msngr $ encode $ IdentifyMessage uniqueAddress--tcpSend :: Address -> NS.Socket -> B.ByteString -> IO ()-tcpSend addr sock bs = do-    NSB.sendAll sock $ encode (B.length bs)-    infoM _log $ "Length sent"-    NSB.sendAll sock bs-    infoM _log $ "Message sent to" ++ (show addr)
− src/Network/Transport/UDP.hs
@@ -1,154 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}---------------------------------------------------------------------------------- |--- Module      :  Network.Transport.UDP--- Copyright   :  (c) Phil Hargett 2013--- License     :  MIT (see LICENSE file)--- --- Maintainer  :  phil@haphazardhouse.net--- Stability   :  experimental--- Portability :  non-portable (uses STM)------ UDP transports deliver messages to other 'Network.Endpoints.Endpoint's using UDP/IP.------ Each UDP transport manages socket bindings on behalf of--- 'Endpoint's, dynamically opening / closing new sockets as needed to deliver--- messages to other 'Endpoint's using UDP transports.------ There is no reuse of sockets on the sending side, so while messages will be --- received on a known bound port, the remote sending port will vary arbitrarily.--- --- This transport only reads at most 512 bytes from incoming packets: constraining--- the packet size avoids fragmentation.  Applications using this transport should take--- responsibility for fragmentation, reassembly, retransmission of lost packets,--- and congestion control.--------------------------------------------------------------------------------module Network.Transport.UDP (-  newUDPTransport,-  newUDPTransport6,--  lookupAddresses,-  lookupUDPAddress,-  lookupWildcardUDPAddress-  ) where---- local imports--import Network.Transport-import Network.Transport.Internal-import Network.Transport.Sockets---- external imports--import Control.Concurrent.Async-import Control.Concurrent.STM-import Control.Exception--import qualified Data.ByteString as B-import qualified Data.Map as M--import qualified Network.Socket as NS-import Network.Socket.ByteString(sendAllTo,recvFrom)--import System.Log.Logger---------------------------------------------------------------------------------------------------------------------------------------------------------------------_log :: String-_log = "transport.udp"--udpScheme :: Scheme-udpScheme = "udp"--lookupUDPAddress :: Address -> NS.Family -> IO NS.SockAddr-lookupUDPAddress address family = lookupAddress family NS.Datagram address--lookupWildcardUDPAddress :: Address -> NS.Family -> IO NS.SockAddr-lookupWildcardUDPAddress address family = lookupWildcardAddress family NS.Datagram address--newUDPTransport :: Resolver -> IO Transport-newUDPTransport resolver = newSocketTransport resolver udpScheme (udpBind NS.AF_INET) (newUDPConnection NS.AF_INET) newUDPMessenger--newUDPTransport6 :: Resolver -> IO Transport-newUDPTransport6 resolver = newSocketTransport resolver udpScheme (udpBind NS.AF_INET6) (newUDPConnection NS.AF_INET6) newUDPMessenger--udpBind :: NS.Family -> SocketTransport -> SocketBindings -> Mailbox Message -> Name -> IO (Either String Binding)-udpBind family transport sockets inc name = do-    atomically $ modifyTVar (socketBindings transport) $ \bindings ->-        M.insert name inc bindings-    Just address <- resolve (socketResolver transport) name-    bindAddress sockets address $ do-        sockaddr <- lookupWildcardUDPAddress address family-        sock <-  NS.socket family NS.Datagram NS.defaultProtocol-        -- have to set this option in case we frequently rebind sockets-        infoM _log $ "Binding to " ++ (show address) ++ " over UDP"-        NS.setSocketOption sock NS.ReuseAddr 1-        NS.bindSocket sock sockaddr-        infoM _log $ "Bound to " ++ (show address) ++ " over UDP"-        rcvr <- async $ udpReceiveSocketMessages sock address (socketInbound transport)-        return (sock,rcvr)-    return $ Right Binding {-        bindingName = name,-        unbind = do-            infoM _log $ "Unbinding from UDP port " ++ (show address)-            unbindAddress sockets address-            infoM _log $ "Unbound from UDP port " ++ (show address)-    }--newUDPConnection :: NS.Family -> Address -> IO Connection-newUDPConnection family address = do-  sock <- atomically newEmptyTMVar-  return Connection {-    connAddress = address,-    connSocket = sock,-    connConnect = NS.socket family NS.Datagram NS.defaultProtocol,-    connSend = (\s bs -> do-        addr <- lookupUDPAddress address family-        infoM _log $ "Sending via UDP to " ++ (show addr)-        sendAllTo s bs addr-        infoM _log $ "Sent via UDP to " ++ (show addr)),-    connReceive = udpRecvFrom,-    connClose = do-        maybeSocket <- atomically $ tryTakeTMVar sock-        case maybeSocket of-            Just s -> NS.sClose s-            Nothing -> return ()-        return ()-    }--newUDPMessenger :: Bindings -> Resolver -> Connection -> Mailbox Message -> IO Messenger-newUDPMessenger _ _ conn mailbox = do-    msngr <- newMessenger conn mailbox-    return msngr--udpReceiveSocketMessages :: NS.Socket -> Address -> Mailbox Message -> IO ()-udpReceiveSocketMessages sock addr mailbox = catchExceptions -    (do-        infoM _log $ "Waiting to receive via UDP on " ++ (show addr)-        maybeMsg <- udpReceiveSocketMessage-        infoM _log $ "Received message via UDP on " ++ (show addr)-        case maybeMsg of-            Nothing -> do-                NS.sClose sock-                return ()-            Just msg -> do-                atomically $ writeMailbox mailbox msg-                udpReceiveSocketMessages sock addr mailbox) -    (\e -> do-        warningM _log $ "Receive error: " ++ (show (e :: SomeException)))-    where-        udpReceiveSocketMessage = do-            maybeMsg <- udpRecvFrom sock 512-            infoM _log $ "Received message"-            return maybeMsg--udpRecvFrom :: NS.Socket -> Int -> IO (Maybe B.ByteString)-udpRecvFrom sock count = do-    (bs,addr) <- recvFrom sock count-    infoM _log $ "Received UDP message from " ++ (show addr) ++ ": " ++ (show bs)-    if B.null bs-        then return Nothing-        else return $ Just bs
tests/TestMemory.hs view
@@ -5,79 +5,25 @@ import Network.Endpoints import Network.Transport.Memory --- external imports--import Control.Exception+import TransportTestSuite -import Data.Serialize+-- external imports  import Test.Framework-import Test.HUnit-import Test.Framework.Providers.HUnit--- import Test.Framework.Providers.QuickCheck2  ----------------------------------------------------------------------------- -----------------------------------------------------------------------------  tests :: [Test.Framework.Test]-tests = [-    testCase "mem-endpoints+transport" testEndpointTransport,-    testCase "mem-bind" testEndpointBind,-    testCase "mem-unbind" testEndpointBindUnbind,-    testCase "mem-sendReceive" testEndpointSendReceive,-    testCase "mem-transport" testMemoryTransport-  ]--testEndpointTransport :: Assertion-testEndpointTransport = do-  transport <- newMemoryTransport-  _ <- newEndpoint [transport]-  return ()--testEndpointBind :: Assertion-testEndpointBind = do-  let name1 = "endpoint1"-  transport <- newMemoryTransport-  finally (do-          endpoint <- newEndpoint [transport]-          Right () <- bindEndpoint endpoint name1-          return ())-      (shutdown transport)--testEndpointBindUnbind :: Assertion-testEndpointBindUnbind = do-  let name1 = "endpoint1"-  transport <- newMemoryTransport-  finally (do-          endpoint <- newEndpoint [transport]-          Right () <- bindEndpoint endpoint name1-          unbound <- unbindEndpoint endpoint name1-          case unbound of-            Left err -> assertFailure $ "Unbind failed: " ++ err-            Right () -> assertBool "Unbind succeeded" True-          return ())-      (shutdown transport)+tests = transportTestSuite+    newMemoryTransport+    "mem"+    (Name "name1")+    (Name "name2")+    (Name "name3")+    (Name "name4") -testEndpointSendReceive :: Assertion-testEndpointSendReceive = do-  let name1 = "endpoint1"-      name2 = "endpoint2"-  transport <- newMemoryTransport-  finally (do-          endpoint1 <- newEndpoint [transport]-          endpoint2 <- newEndpoint [transport]-          Right () <- bindEndpoint endpoint1 name1-          Right () <- bindEndpoint endpoint2 name2-          _ <- sendMessage endpoint1 name2 $ encode "hello!"-          msg <- receiveMessage endpoint2-          assertEqual "Received message not same as sent" (Right "hello!") (decode msg)-          return ())-      (shutdown transport)+_log :: String+_log = "_test_memory"  -- Memory tests--testMemoryTransport :: Assertion-testMemoryTransport = do-    transport <- newMemoryTransport-    shutdown transport-
tests/TestRPC.hs view
@@ -3,7 +3,7 @@ -- Module      :  TestRPC -- Copyright   :  (c) Phil Hargett 2014 -- License     :  MIT (see LICENSE file)--- +-- -- Maintainer  :  phil@haphazardhouse.net -- Stability   :  experimental -- Portability :  non-portable (requires STM)@@ -22,11 +22,11 @@ import Network.RPC import Network.Transport.Memory +import TestUtils+ -- external imports -import Control.Applicative import Control.Concurrent-import Control.Concurrent.Async  import qualified Data.Map as M import Data.Serialize@@ -43,253 +43,72 @@  tests :: [Test.Framework.Test] tests = [-    testCase "call-one-hear-call" testOneHearCall,-    testCase "call-one-call-hear" testOneCallHear,-    testCase "call-concurrent-call-hear" testConcurrentCallHear,-    testCase "call-one-handler" testOneHandler,-    testCase "call-two-handlers" testTwoHandlers,-    testCase "gcall-three-handlers" testGroupCall,-    testCase "anycall-three-handlers" testAnyCall,     testCase "call-one-with-timeout" testOneHandlerWithTimeout,     testCase "gcall-three-handlers-with-timeout"testGroupCallWithTimeout   ] -testOneHearCall :: Assertion-testOneHearCall = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    _ <- async $ do-        (bytes,reply) <- hear endpoint2 name2 "foo"-        let Right msg = decode bytes-        reply $ encode $ msg ++ "!"-    let cs = newCallSite endpoint1 name1-    bytes <- call cs name2 "foo" $ encode "hello"-    let Right result = decode bytes-    assertEqual "Result not expected value" "hello!" result--testOneCallHear :: Assertion-testOneCallHear = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    let cs = newCallSite endpoint1 name1-    acall <- async $ call cs name2 "foo" $ encode "hello"-    _ <- async $ do-        (bytes,reply) <- hear endpoint2 name2 "foo"-        let Right msg = decode bytes-        reply $ encode $ msg ++ "!"-    bytes <- wait acall-    let Right result = decode bytes-    assertEqual "Result not expected value" "hello!" result--testConcurrentCallHear :: Assertion-testConcurrentCallHear = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    let cs1 = newCallSite endpoint1 name1-        cs2 = newCallSite endpoint2 name2-    let call1 = call cs1 name2 "foo" $ encode "hello"-        hear1 = do-            (bytes,reply) <- hear endpoint2 name2 "foo"-            let Right msg = decode bytes-            reply $ encode $ msg ++ "!"-        call2 = call cs2 name1 "bar" $ encode "ciao"-        hear2 = do-            (bytes,reply) <- hear endpoint1 name1 "bar"-            let Right msg = decode bytes-            reply $ encode $ msg ++ "!"-    (result1,(),result2,()) <- runConcurrently $ (,,,)-        <$> Concurrently call1-        <*> Concurrently hear1-        <*> Concurrently call2-        <*> Concurrently hear2-    assertEqual "Result not expected value" (Right "hello!") (decode result1)-    assertEqual "Result not expected value" (Right "ciao!")  (decode result2)--testOneHandler :: Assertion-testOneHandler = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    h <- handle endpoint2 name2 "foo" $ \bytes ->-        let Right msg = decode bytes-        in return $ encode $ msg ++ "!"-    let cs = newCallSite endpoint1 name1-    bytes <- call cs name2 "foo" $ encode "hello"-    let Right result = decode bytes-    assertEqual "Result not expected value" "hello!" result-    hangup h--testTwoHandlers :: Assertion-testTwoHandlers = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    h1 <- handle endpoint2 name2 "foo" $ \bytes ->-        let Right msg = decode bytes-        in return $ encode $ msg ++ "!"-    h2 <- handle endpoint2 name2 "bar" $ \bytes ->-        let Right msg = decode bytes-        in return $ encode $ msg ++ "?"-    let cs = newCallSite endpoint1 name1-    bytes1 <- call cs name2 "foo" $ encode "hello"-    let Right result1 = decode bytes1-    assertEqual "Result not expected value" "hello!" result1-    bytes2 <- call cs name2 "bar" $ encode "hello"-    let Right result2 = decode bytes2-    assertEqual "Result not expected value" "hello?" result2-    hangup h1-    hangup h2--testGroupCall :: Assertion-testGroupCall = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-        name3 = "endpoint3"-        name4 = "endpoint4"-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    endpoint3 <- newEndpoint [transport]-    endpoint4 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    Right () <- bindEndpoint endpoint3 name3-    Right () <- bindEndpoint endpoint4 name4-    h2 <- handle endpoint2 name2 "foo" $ \bytes -> let Right msg = decode bytes in-                                                      return $ encode $ if msg == "hello" then "foo" else ""-    h3 <- handle endpoint3 name3 "foo" $ \bytes -> let Right msg = decode bytes in-                                                       return $ encode $ if msg == "hello" then "bar" else ""-    h4 <- handle endpoint4 name4 "foo" $ \bytes -> let Right msg = decode bytes in-                                                       return $ encode $ if msg == "hello" then "baz" else ""-    let cs = newCallSite endpoint1 name1-    results <- (gcall cs [name2,name3,name4] "foo" $ encode "hello")-    assertBool "Foo not present in results" (elem (encode "foo") $ M.elems results)-    assertBool "Bar not present in results" (elem (encode "bar") $ M.elems results)-    assertBool "Bar not present in results" (elem (encode "baz") $ M.elems results)-    assertEqual "Unxpected number of results" 3 (M.size results)-    hangup h2-    hangup h3-    hangup h4--testAnyCall :: Assertion-testAnyCall = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-        name3 = "endpoint3"-        name4 = "endpoint4"-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    endpoint3 <- newEndpoint [transport]-    endpoint4 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    Right () <- bindEndpoint endpoint3 name3-    Right () <- bindEndpoint endpoint4 name4-    h2 <- handle endpoint2 name2 "foo" $ \bytes -> let Right msg = decode bytes in-                                                      return $ encode $ if msg == "hello" then "foo" else ""-    h3 <- handle endpoint3 name3 "foo" $ \bytes -> let Right msg = decode bytes in-                                                       return $ encode $ if msg == "hello" then "foo" else ""-    h4 <- handle endpoint4 name4 "foo" $ \bytes -> let Right msg = decode bytes in-                                                       return $ encode $ if msg == "hello" then "foo" else ""-    let cs = newCallSite endpoint1 name1-    (result,responder) <- (anyCall cs [name2,name3,name4] "foo" $ encode "hello")-    assertEqual "Response should have been 'foo'" (encode "foo") result-    assertBool "Responder was not in original list of names" $ elem responder [name2,name3,name4]-    hangup h2-    hangup h3-    hangup h4- testOneHandlerWithTimeout :: Assertion testOneHandlerWithTimeout = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-        longer = 500 * 1000 -- half a second-        shorter = 250 * 1000 -- quarter second-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    -- first call with caller waiting longer than handler-    h1 <- handle endpoint2 name2 "foo" $ \bytes -> do-        let Right msg = decode bytes-        threadDelay shorter-        return $ encode $ msg ++ "!"-    let cs1 = newCallSite endpoint1 name1-    Just bytes1 <- callWithTimeout cs1 name2 "foo" longer $ encode "hello"-    let Right result1 = decode bytes1-    assertEqual "Result not expected value" (Just "hello!") (Just result1)-    hangup h1-    -- now call with handler waiting longer than caller-    h2 <- handle endpoint2 name2 "foo" $ \bytes -> do-        let Right msg = decode bytes-        threadDelay longer-        return $ encode $ msg ++ "!"-    let cs2 = newCallSite endpoint1 name1-    bytes2 <- (callWithTimeout cs2 name2 "foo" shorter $ encode "hello")-    assertEqual "Result not expected value" Nothing bytes2-    hangup h2+  let name1 = Name "endpoint1"+      name2 = Name "endpoint2"+      longer = (3 * 1000000 :: Int)+      shorter = (1 * 1000000 :: Int)+  withTransport newMemoryTransport $ \transport ->+    withNewEndpoint2 transport $ \endpoint1 endpoint2 ->+      withBinding transport endpoint1 name1 $+        withBinding transport endpoint2 name2 $+          withConnection transport endpoint1 name2 $ do+            -- first call with caller waiting longer than handler+            h1 <- handle endpoint2 name2 "foo" $ \bytes -> do+                let Right msg = decode bytes+                threadDelay shorter+                return $ encode $ msg ++ "!"+            let cs1 = newCallSite endpoint1 name1+            Just bytes1 <- callWithTimeout cs1 name2 "foo" longer $ encode "hello"+            let Right result1 = decode bytes1+            assertEqual "Result not expected value" (Just "hello!") (Just result1)+            hangup h1+            -- now call with handler waiting longer than caller+            h2 <- handle endpoint2 name2 "foo" $ \bytes -> do+                let Right msg = decode bytes+                threadDelay longer+                return $ encode $ msg ++ "!"+            let cs2 = newCallSite endpoint1 name1+            bytes2 <- (callWithTimeout cs2 name2 "foo" shorter $ encode "hello")+            assertEqual "Result not expected value" Nothing bytes2+            hangup h2  testGroupCallWithTimeout :: Assertion testGroupCallWithTimeout = do-    let name1 = "endpoint1"-        name2 = "endpoint2"-        name3 = "endpoint3"-        name4 = "endpoint4"-        longest = 750 * 1000 -- three quarters of a second-        longer = 500 * 1000 -- half a second-        shorter = 250 * 1000 -- quarter second-    transport <- newMemoryTransport-    endpoint1 <- newEndpoint [transport]-    endpoint2 <- newEndpoint [transport]-    endpoint3 <- newEndpoint [transport]-    endpoint4 <- newEndpoint [transport]-    Right () <- bindEndpoint endpoint1 name1-    Right () <- bindEndpoint endpoint2 name2-    Right () <- bindEndpoint endpoint3 name3-    Right () <- bindEndpoint endpoint4 name4-    h2 <- handle endpoint2 name2 "foo" $ \bytes -> do-        let Right msg = decode bytes-        threadDelay shorter-        return $ encode $ if msg == "hello" then "foo" else ""-    h3 <- handle endpoint3 name3 "foo" $ \bytes ->  do-        threadDelay shorter-        let Right msg = decode bytes-        return $ encode $ if msg == "hello" then "bar" else ""-    h4 <- handle endpoint4 name4 "foo" $ \bytes ->  do-        threadDelay longest-        let Right msg = decode bytes-        return $ encode $ if msg == "hello" then "baz" else ""-    let cs = newCallSite endpoint1 name1-    results <- gcallWithTimeout cs [name2,name3,name4] "foo" longer $ encode "hello"-    assertEqual "Foo not present in results" (Just $ Just $ encode "foo") (M.lookup name2 results)-    assertEqual "Bar not present in results" (Just $ Just $ encode "bar") (M.lookup name3 results)-    assertEqual "Baz shouldn't be present in results" (Just $ Nothing) (M.lookup name4 results)-    assertEqual "Unxpected number of results" 3 (M.size results)-    hangup h2-    hangup h3-    hangup h4+  let name1 = Name "endpoint1"+      name2 = Name "endpoint2"+      name3 = Name "endpoint3"+      name4 = Name "endpoint4"+      longest = 750 * 1000 -- three quarters of a second+      longer = 500 * 1000 -- half a second+      shorter = 250 * 1000 -- quarter second+  withTransport newMemoryTransport $ \transport ->+    withNewEndpoint4 transport $ \endpoint1 endpoint2 endpoint3 endpoint4 -> do+      withBinding4 transport (endpoint1,name1) (endpoint2,name2) (endpoint3,name3) (endpoint4,name4) $+        withConnection3 transport endpoint1 name2 name3 name4 $ do+          h2 <- handle endpoint2 name2 "foo" $ \bytes -> do+              let Right msg = decode bytes+              threadDelay shorter+              return $ encode $ if msg == "hello" then "foo" else ""+          h3 <- handle endpoint3 name3 "foo" $ \bytes ->  do+              threadDelay shorter+              let Right msg = decode bytes+              return $ encode $ if msg == "hello" then "bar" else ""+          h4 <- handle endpoint4 name4 "foo" $ \bytes ->  do+              threadDelay longest+              let Right msg = decode bytes+              return $ encode $ if msg == "hello" then "baz" else ""+          let cs = newCallSite endpoint1 name1+          results <- gcallWithTimeout cs [name2,name3,name4] "foo" longer $ encode "hello"+          assertEqual "Foo not present in results" (Just $ Just $ encode "foo") (M.lookup name2 results)+          assertEqual "Bar not present in results" (Just $ Just $ encode "bar") (M.lookup name3 results)+          assertEqual "Baz shouldn't be present in results" (Just $ Nothing) (M.lookup name4 results)+          assertEqual "Unxpected number of results" 3 (M.size results)+          hangup h2+          hangup h3+          hangup h4
tests/TestTCP.hs view
@@ -1,58 +1,49 @@-module TestTCP (tests) where+module TestTCP (+  tests4+  ,tests6+  ) where  -- local imports -import Network.Endpoints-import Network.Transport.TCP+import Network.Transport.Sockets.TCP -import TestTransports-import TestUtils+import TransportTestSuite  -- external imports -import Control.Exception- import Test.Framework-import Test.HUnit-import Test.Framework.Providers.HUnit--- import Test.Framework.Providers.QuickCheck2  ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -_log :: String-_log = "test.transport.tcp"+tests4 :: IO [Test.Framework.Test]+tests4 = do+  name1 <- newTCPAddress+  name2 <- newTCPAddress+  name3 <- newTCPAddress+  name4 <- newTCPAddress+  return $ transportTestSuite+      (newTCPTransport4 tcpSocketResolver4)+      "tcp4"+      name1+      name2+      name3+      name4 -tests :: [Test.Framework.Test]-tests =-  [-    testCase "tcp-endpoints+transport" $ testEndpointTransport newTCPTransport,-    testCase "tcp-bind-unbind" $ endpointBindUnbind _log newTCPTransport newTCPAddress,-    testCase "tcp-send-receive" $ endpointSendReceive _log newTCPTransport newTCPAddress,-    testCase "tcp-double-send-receive" $ endpointDoubleSendReceive _log newTCPTransport newTCPAddress,-    testCase "tcp-send-receive-reply" $ endpointSendReceiveReply _log newTCPTransport newTCPAddress,-    testCase "tcp-multiple-send-receive-reply" $ endpointMultipleSendReceiveReply _log newTCPTransport newTCPAddress,-    testCase "tcp-local-send-receive-reply" $ endpointLocalSendReceiveReply _log newTCPTransport newTCPAddress,+tests6 :: IO [Test.Framework.Test]+tests6 = do+  name1 <- newTCPAddress6+  name2 <- newTCPAddress6+  name3 <- newTCPAddress6+  name4 <- newTCPAddress6+  return $ transportTestSuite+    (newTCPTransport6 tcpSocketResolver6)+    "tcp6"+    name1+    name2+    name3+    name4 -    testCase "tcp6-endpoints+transport" $ whenIPv6 $ testEndpointTransport newTCPTransport6,-    testCase "tcp6-bind-unbind" $ whenIPv6 $ endpointBindUnbind _log newTCPTransport6 newTCPAddress6,-    testCase "tcp6-send-receive" $ whenIPv6 $ endpointSendReceive _log newTCPTransport6 newTCPAddress6,-    testCase "tcp6-double-send-receive" $ whenIPv6 $ endpointDoubleSendReceive _log newTCPTransport6 newTCPAddress6,-    testCase "tcp6-send-receive-reply" $ whenIPv6 $ endpointSendReceiveReply _log newTCPTransport6 newTCPAddress6,-    testCase "tcp6-multiple-send-receive-reply" $ whenIPv6 $ endpointMultipleSendReceiveReply _log newTCPTransport6 newTCPAddress6,-    testCase "tcp6-local-send-receive-reply" $ whenIPv6 $ endpointLocalSendReceiveReply _log newTCPTransport6 newTCPAddress6-  ] -testEndpointTransport :: (Resolver -> IO Transport) -> Assertion-testEndpointTransport transportFactory = do-  let name1 = "endpoint1"-      name2 = "endpoint2"-  address1 <- newTCPAddress-  address2 <- newTCPAddress-  let resolver = resolverFromList [(name1,address1),-                               (name2,address2)]-  bracket (transportFactory resolver)-          shutdown-          (\transport -> do-            _ <- newEndpoint [transport]-            return ())+_log :: String+_log = "_test_TCP"
− tests/TestTransports.hs
@@ -1,252 +0,0 @@-module TestTransports (-    testDelay,-    verifiedSend,-    whenIPv6,--    -- Common tests-    endpointTransport,-    endpointBindUnbind,-    endpointSendReceive,-    endpointDoubleSendReceive,-    endpointSendReceiveReply,-    endpointLocalSendReceiveReply,-    endpointMultipleSendReceiveReply-) where---- local imports--import Network.Endpoints--import Network.Transport.TCP---- external imports--import Control.Concurrent-import Control.Exception--import Data.Serialize--import qualified Network.Socket as NS--import System.Log.Logger--import Test.HUnit---------------------------------------------------------------------------------------------------------------------------------------------------------------_log :: String-_log = "test.transports"--testDelay :: Int-testDelay = 1 * 1000000--pause :: IO ()-pause = threadDelay testDelay--whenIPv6 :: Assertion -> Assertion-whenIPv6 assn = do-    addresses <- lookupAddresses NS.AF_INET6 NS.Stream "localhost:1"-    case addresses of-        [] -> do-            warningM _log $ "IPv6 not available"-            return ()-        _ -> assn--{--Common tests--just supply the transport factory and an address generator--}--endpointTransport :: String -> (Resolver -> IO Transport) -> IO Address -> Assertion-endpointTransport _log newTransport newAddress = do-  address1 <- newAddress-  address2 <- newAddress-  let name1 = "endpoint1"-      name2 = "endpoint2"-  let resolver = resolverFromList [(name1,address1),-                               (name2,address2)]-  bracket (newTransport resolver)-          shutdown-          (\transport -> do -            _ <- newEndpoint [transport]-            return ())--endpointBindUnbind :: String -> (Resolver -> IO Transport) -> IO Address -> Assertion-endpointBindUnbind _log newTransport newAddress = do-  infoM _log "Starting bind-unbind test"-  address1 <- newAddress-  address2 <- newAddress-  let name1 = "endpoint1"-      name2 = "endpoint2"-  let resolver = resolverFromList [(name1,address1),-                               (name2,address2)]-  bracket (newTransport resolver)-          shutdown-          (\transport -> do -            endpoint <- newEndpoint [transport]-            Right () <- bindEndpoint endpoint name1-            unbound <- unbindEndpoint endpoint name1-            case unbound of-              Left err -> assertFailure $ "Unbind failed: " ++ err-              Right () -> assertBool "Unbind succeeded" True-            return ())--endpointSendReceive :: String -> (Resolver -> IO Transport) -> IO Address -> Assertion-endpointSendReceive _log newTransport newAddress = do-  infoM _log "Starting send-receive test"-  address1 <- newAddress-  address2 <- newAddress-  let name1 = "endpoint1"-      name2 = "endpoint2"-  let resolver = resolverFromList [(name1,address1),-                               (name2,address2)]-  bracketTest _log resolver newTransport $ \transport1 transport2 -> do -      endpoint1 <- newEndpoint [transport1]-      endpoint2 <- newEndpoint [transport2]-      Right () <- bindEndpoint endpoint1 name1-      Right () <- bindEndpoint endpoint2 name2-      pause--      verifiedSend _log endpoint1 endpoint2 name1 name2 "hello"--      Right () <- unbindEndpoint endpoint1 name1-      Right () <- unbindEndpoint endpoint2 name2-      return ()-  infoM _log "Finished send-receive test"--endpointDoubleSendReceive :: String -> (Resolver -> IO Transport) -> IO Address -> Assertion-endpointDoubleSendReceive _log newTransport newAddress = do-  infoM _log "Starting double-send-receive test"-  address1 <- newAddress-  address2 <- newAddress-  let name1 = "endpoint1"-      name2 = "endpoint2"-      name3 = "endpoint3"-  let resolver = resolverFromList [(name1,address1),-                               (name2,address2),-                               (name3,address1)]-  bracketTest _log resolver newTransport $ \transport1 transport2 -> do -      endpoint1 <- newEndpoint [transport1]-      endpoint2 <- newEndpoint [transport2]-      endpoint3 <- newEndpoint [transport1]-      Right () <- bindEndpoint endpoint1 name1-      Right () <- bindEndpoint endpoint2 name2-      Right () <- bindEndpoint endpoint3 name3-      pause--      verifiedSend _log endpoint1 endpoint2 name1 name2 "hello"-      verifiedSend _log endpoint3 endpoint2 name3 name2 "ciao!"--      Right () <- unbindEndpoint endpoint1 name1--      verifiedSend _log endpoint3 endpoint2 name3 name2 "hi!"--      Right () <- unbindEndpoint endpoint2 name2-      Right () <- unbindEndpoint endpoint3 name3-      return ()-  infoM _log "Finished double-send-receive test"--endpointSendReceiveReply :: String -> (Resolver -> IO Transport) -> IO Address -> Assertion-endpointSendReceiveReply _log newTransport newAddress = do-  infoM _log "Starting send-receive-reply test"-  address1 <- newAddress-  address2 <- newAddress-  let name1 = "endpoint1"-      name2 = "endpoint2"-  let resolver = resolverFromList [(name1,address1),-                                   (name2,address2)]-  bracketTest _log resolver newTransport $ \transport1 transport2 -> do -      endpoint1 <- newEndpoint [transport1]-      endpoint2 <- newEndpoint [transport2]-      Right () <- bindEndpoint endpoint1 name1-      Right () <- bindEndpoint endpoint2 name2-      pause--      verifiedSend _log endpoint1 endpoint2 name1 name2 "hello"-      verifiedSend _log endpoint2 endpoint1 name2 name1 "hi!"--      Right () <- unbindEndpoint endpoint1 name1-      Right () <- unbindEndpoint endpoint2 name2--      return ()-  infoM _log "Finished send-receive-reply test"--endpointLocalSendReceiveReply :: String -> (Resolver -> IO Transport) -> IO Address -> Assertion-endpointLocalSendReceiveReply _log newTransport newAddress = do-  infoM _log "Starting local-send-receive-reply test"-  address1 <- newAddress-  let name1 = "endpoint1"-      name2 = "endpoint2"-  let resolver = resolverFromList [(name1,address1),-                                   (name2,address1)]-  bracket (newTransport resolver)-    shutdown-    (\transport1 -> do-        endpoint1 <- newEndpoint [transport1]-        endpoint2 <- newEndpoint [transport1]-        Right () <- bindEndpoint endpoint1 name1-        Right () <- bindEndpoint endpoint2 name2-        pause--        verifiedSend _log endpoint1 endpoint2 name1 name2 "hello"-        verifiedSend _log endpoint2 endpoint1 name2 name1 "hi!"--        Right () <- unbindEndpoint endpoint1 name1-        Right () <- unbindEndpoint endpoint2 name2--        return ())-  infoM _log "Finished local-send-receive-reply test"--endpointMultipleSendReceiveReply :: String -> (Resolver -> IO Transport) -> IO Address -> Assertion-endpointMultipleSendReceiveReply _log newTransport newAddress = do-  infoM _log "Starting multiple-send-receive-reply test"-  address1 <- newAddress-  address2 <- newAddress-  let name1 = "endpoint1"-      name2 = "endpoint2"-  let resolver = resolverFromList [(name1,address1),-                                   (name2,address2)]-  bracketTest _log resolver newTransport $ \transport1 transport2 -> do-      endpoint1 <- newEndpoint [transport1]-      endpoint2 <- newEndpoint [transport2]-      Right () <- bindEndpoint endpoint1 name1-      Right () <- bindEndpoint endpoint2 name2-      pause--      roundtrip endpoint1 endpoint2 name1 name2-      roundtrip endpoint2 endpoint1 name2 name1-      roundtrip endpoint1 endpoint2 name1 name2-      roundtrip endpoint2 endpoint1 name2 name1--      Right () <- unbindEndpoint endpoint1 name1-      Right () <- unbindEndpoint endpoint2 name2--      return ()-  infoM _log "Finished multiple-send-receive-reply test"-    where-      roundtrip endpoint1 endpoint2 name1 name2 = do-        verifiedSend _log endpoint1 endpoint2 name1 name2 "hello"-        verifiedSend _log endpoint2 endpoint1 name2 name1 "hi!"---- helpers--bracketTest :: String -> Resolver -> (Resolver -> IO Transport) -> (Transport -> Transport -> Assertion) -> Assertion-bracketTest _log resolver newTransport blk = do-    bracket (newTransport resolver)-        shutdown-        (\transport1 -> -            bracket (newTransport resolver)-                shutdown-                (\transport2 -> catch (blk transport1 transport2)-                                    (\e -> do -                                        errorM _log $ "Encountered error running test: " ++ (show (e :: SomeException))-                                        assertFailure "Unexpected error")))--verifiedSend :: String -> Endpoint -> Endpoint -> Name -> Name -> String -> Assertion-verifiedSend _log endpoint1 endpoint2 name1 name2 msg = do-  infoM _log $ "Sending message from " ++ name1 ++ " to " ++ name2-  sendMessage_ endpoint1 name2 $ encode msg-  maybeMsg <- receiveMessageTimeout endpoint2 testDelay-  case maybeMsg of-    Nothing -> assertFailure "No message received"-    Just msg1 -> assertEqual "Received message not same as sent" (Right msg) (decode msg1)
tests/TestUDP.hs view
@@ -1,59 +1,50 @@-module TestUDP where+module TestUDP (+  tests4+  ,tests6+) where  -- local imports -import Network.Endpoints-import Network.Transport.UDP+import Network.Transport.Sockets.UDP -import TestTransports-import TestUtils+import TransportTestSuite  -- external imports -import Control.Exception import Test.Framework-import Test.HUnit-import Test.Framework.Providers.HUnit--- import Test.Framework.Providers.QuickCheck2  ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -_log :: String-_log = "test.transport.udp"--tests :: [Test.Framework.Test]-tests = -  [-    testCase "udp-endpoints+transport" testEndpointTransport,+tests4 :: IO [Test.Framework.Test]+tests4 = do+  name1 <- newUDPAddress+  name2 <- newUDPAddress+  name3 <- newUDPAddress+  name4 <- newUDPAddress+  return $ transportTestSuite+      (newUDPTransport4 udpSocketResolver4)+      "udp4"+      name1+      name2+      name3+      name4 -    testCase "udp-bind-unbind" $ endpointBindUnbind _log newUDPTransport newUDPAddress,-    testCase "udp-send-receive" $ endpointSendReceive _log newUDPTransport newUDPAddress,-    testCase "udp-double-send-receive" $ endpointDoubleSendReceive _log newUDPTransport newUDPAddress,-    testCase "udp-send-receive-reply" $ endpointSendReceiveReply _log newUDPTransport newUDPAddress,-    testCase "udp-multiple-send-receive-reply" $ endpointMultipleSendReceiveReply _log newUDPTransport newUDPAddress,-    testCase "udp-local-send-receive-reply" $ endpointLocalSendReceiveReply _log newUDPTransport newUDPAddress,-    -    testCase "udp6-endpoints+transport" $ whenIPv6 $ testEndpointTransport,+tests6 :: IO [Test.Framework.Test]+tests6 = do+  name1 <- newUDPAddress6+  name2 <- newUDPAddress6+  name3 <- newUDPAddress6+  name4 <- newUDPAddress6+  return $ transportTestSuite+    (newUDPTransport6 udpSocketResolver6)+    "udp6"+    name1+    name2+    name3+    name4 -    testCase "udp6-bind-unbind" $ whenIPv6 $ endpointBindUnbind _log newUDPTransport6 newUDPAddress6,-    testCase "udp6-send-receive" $ whenIPv6 $ endpointSendReceive _log newUDPTransport6 newUDPAddress6,-    testCase "udp6-double-send-receive" $ whenIPv6 $ endpointDoubleSendReceive _log newUDPTransport6 newUDPAddress6,-    testCase "udp6-send-receive-reply" $ whenIPv6 $ endpointSendReceiveReply _log newUDPTransport6 newUDPAddress6,-    testCase "udp6-multiple-send-receive-reply" $ whenIPv6 $ endpointMultipleSendReceiveReply _log newUDPTransport6 newUDPAddress6,-    testCase "udp6-local-send-receive-reply" $ whenIPv6 $ endpointLocalSendReceiveReply _log newUDPTransport6 newUDPAddress6-  ]+_log :: String+_log = "_test_UDP" -testEndpointTransport :: Assertion-testEndpointTransport = do-  let name1 = "endpoint1"-      name2 = "endpoint2"-  address1 <- newUDPAddress-  address2 <- newUDPAddress-  let resolver = resolverFromList [(name1,address1),-                               (name2,address2)]-  bracket (newUDPTransport resolver)-          shutdown-          (\transport -> do -            _ <- newEndpoint [transport]-            return ())+-- UDP tests
tests/TestUtils.hs view
@@ -1,34 +1,83 @@ module TestUtils-    ( newTCPAddress-    , newUDPAddress-    , newTCPAddress6-    , newUDPAddress6)-    where+    (+    withNewEndpoint,+    withNewEndpoint2,+    withNewEndpoint3,+    withNewEndpoint4, +    newTCPAddress,+    newUDPAddress,+    newTCPAddress6,+    newUDPAddress6,+    isIPv6Available,++    timeBound,+    troubleshoot,++    )+  where++-- local imports++import Network.Endpoints+import Network.Transport.Sockets.TCP++-- external imports+import Control.Concurrent+import Control.Concurrent.Async import Control.Exception+ import qualified Network.Socket as NS -newTCPAddress :: IO String+import System.Log.Logger++import Test.HUnit++--------------------------------------------------------------------------------+--------------------------------------------------------------------------------++withNewEndpoint :: Transport -> (Endpoint -> IO ()) -> IO ()+withNewEndpoint transport fn = do+  endpoint <- newEndpoint+  withEndpoint transport endpoint $ fn endpoint++withNewEndpoint2 :: Transport -> (Endpoint -> Endpoint -> IO ()) -> IO ()+withNewEndpoint2 transport fn = do+  withNewEndpoint transport $ \endpoint1 ->+    withNewEndpoint transport $ \endpoint2 ->+      fn endpoint1 endpoint2++withNewEndpoint3 :: Transport -> (Endpoint -> Endpoint -> Endpoint -> IO ()) -> IO ()+withNewEndpoint3 transport fn = withNewEndpoint transport $ \endpoint1 ->+  withNewEndpoint2 transport $ \endpoint2 endpoint3 ->+    fn endpoint1 endpoint2 endpoint3++withNewEndpoint4 :: Transport -> (Endpoint -> Endpoint -> Endpoint -> Endpoint -> IO ()) -> IO ()+withNewEndpoint4 transport fn = withNewEndpoint2 transport $ \endpoint1 endpoint2 ->+  withNewEndpoint2 transport $ \endpoint3 endpoint4 ->+    fn endpoint1 endpoint2 endpoint3 endpoint4++newTCPAddress :: IO Name newTCPAddress = do-  NS.SockAddrInet (NS.PortNum p) _ <- availablePort NS.AF_INET NS.Stream-  return $ "localhost:" ++ show p+  NS.SockAddrInet p _ <- availablePort NS.AF_INET NS.Stream+  return $ Name $ "localhost:" ++ show p -newUDPAddress :: IO String+newUDPAddress :: IO Name newUDPAddress = do-  NS.SockAddrInet (NS.PortNum p) _ <- availablePort NS.AF_INET NS.Datagram-  return $ "localhost:" ++ show p+  NS.SockAddrInet p _ <- availablePort NS.AF_INET NS.Datagram+  return $ Name $ "localhost:" ++ show p -newTCPAddress6 :: IO String+newTCPAddress6 :: IO Name newTCPAddress6 = do-  NS.SockAddrInet6 (NS.PortNum p) _ _ _ <- availablePort NS.AF_INET6 NS.Stream-  return $ "localhost:" ++ show p+  NS.SockAddrInet6 p _ _ _ <- availablePort NS.AF_INET6 NS.Stream+  return $ Name $ "localhost:" ++ show p -newUDPAddress6 :: IO String+newUDPAddress6 :: IO Name newUDPAddress6 = do-  NS.SockAddrInet6 (NS.PortNum p) _ _ _ <- availablePort NS.AF_INET6 NS.Datagram-  return $ "localhost:" ++ show p+  NS.SockAddrInet6 p _ _ _ <- availablePort NS.AF_INET6 NS.Datagram+  return $ Name $ "localhost:" ++ show p -availablePort     :: NS.Family -> NS.SocketType -> IO NS.SockAddr+availablePort :: NS.Family -> NS.SocketType -> IO NS.SockAddr availablePort f t = do   let hints = NS.defaultHints { NS.addrFamily = f                               , NS.addrSocketType = t@@ -47,7 +96,25 @@          else return addr     ) +isIPv6Available :: IO Bool+isIPv6Available = do+    addresses <- tcpSocketResolver6 (Name "localhost:1")+    case addresses of+        [] -> return False+        _ -> return True+ isPrivileged :: NS.SockAddr -> Bool-isPrivileged (NS.SockAddrInet (NS.PortNum p) _) = p < 1025-isPrivileged (NS.SockAddrInet6 (NS.PortNum p) _ _ _) = p < 1025-isPrivileged (NS.SockAddrUnix _) = False+isPrivileged (NS.SockAddrInet p _) = p < 1025+isPrivileged (NS.SockAddrInet6 p _ _ _) = p < 1025+isPrivileged _ = False++timeBound :: Int -> IO () -> IO ()+timeBound delay action = do+    outcome <- race (threadDelay delay) action+    assertBool "Test should not block" $ outcome == Right ()++troubleshoot :: IO () -> IO ()+troubleshoot fn = do+    finally (do+        updateGlobalLogger rootLoggerName (setLevel INFO)+        fn) (updateGlobalLogger rootLoggerName (setLevel WARNING))
tests/Tests.hs view
@@ -2,7 +2,7 @@  -- local imports -import Network.Endpoints+import TestUtils  -- external imports @@ -13,15 +13,13 @@ import System.Log.Logger  import Test.Framework-import Test.HUnit-import Test.Framework.Providers.HUnit  -- Test modules import qualified TestMailbox as MB import qualified TestMemory as M+import qualified TestRPC as R import qualified TestTCP as T import qualified TestUDP as U-import qualified TestRPC as R  ----------------------------------------------------------------------------- -----------------------------------------------------------------------------@@ -29,7 +27,15 @@ main :: IO () main = do   initLogging-  defaultMain tests+  ipv6 <- isIPv6Available+  t4 <- T.tests4+  t6 <- T.tests6+  u4 <- U.tests4+  u6 <- U.tests6+  let allTests = tests ++ t4 ++ u4+  if ipv6+    then defaultMain (allTests ++ t6 ++ u6)+    else defaultMain allTests  initLogging :: IO () initLogging = do@@ -40,19 +46,6 @@  tests :: [Test.Framework.Test] tests =-  [-    testCase "hunit" (assertBool "HUnit assertion of truth is false" True),-    testCase "endpoints" testEndpoint-  ]-  ++ MB.tests+  MB.tests   ++ M.tests-  ++ T.tests-  ++ U.tests   ++ R.tests---- Endpoint tests--testEndpoint :: Assertion-testEndpoint = do-  _ <- newEndpoint []-  return ()
+ tests/TransportTestSuite.hs view
@@ -0,0 +1,275 @@+module TransportTestSuite+    (+    transportTestSuite,++    testTransportEndpointSendReceive,+    testTransportEndpointSend2Receive2,+    testTransportEndpointSendReceive2SerialServers,+    testTransportEndpointSendReceive2SerialClients,+    testTransportOneHearCall,+    testTransportOneCallHear,+    testTransportConcurrentCallHear,+    testTransportOneHandler,+    testTransportTwoHandlers,+    testTransportGroupCall,+    testTransportAnyCall,++    module TestUtils+  )+  where++-- local imports++import Network.Endpoints+import Network.RPC+import Network.Transport+import TestUtils++-- external imports++import Control.Applicative+import Control.Concurrent.Async++import qualified Data.Map as M+import Data.Serialize++import Test.Framework+import Test.HUnit+import Test.Framework.Providers.HUnit++--------------------------------------------------------------------------------+--------------------------------------------------------------------------------++transportTestSuite :: IO Transport -> String -> Name -> Name -> Name -> Name -> [Test.Framework.Test]+transportTestSuite transport transportLabel name1 name2 name3 name4 = [+  testCase (transportLabel ++ "-sendReceive") $+    testTransportEndpointSendReceive transport name1 name2,+  testCase (transportLabel ++ "-send2Receive2") $+    testTransportEndpointSend2Receive2 transport name1 name2,+  testCase (transportLabel ++ "-sendReceive-2-serial-servers") $+    testTransportEndpointSendReceive2SerialServers transport name1 name2,+  testCase  (transportLabel ++ "-sendReceive-2-serial-clients") $+    testTransportEndpointSendReceive2SerialClients transport name1 name2,+  testCase  (transportLabel ++ "-rpc-one-hear-call") $+    testTransportOneHearCall transport name1 name2,+  testCase  (transportLabel ++ "-rpc-one-call-hear") $+    testTransportOneCallHear transport name1 name2,+  testCase  (transportLabel ++ "-rpc-concurrent-call-hear") $+    testTransportConcurrentCallHear transport name1 name2,+  testCase  (transportLabel ++ "-rpc-one-handler") $+    testTransportOneHandler transport name1 name2,+  testCase  (transportLabel ++ "-rpc-two-handlers") $+    testTransportTwoHandlers transport name1 name2,+  testCase  (transportLabel ++ "-rpc-group-call") $+    testTransportGroupCall transport name1 name2 name3 name4,+  testCase  (transportLabel ++ "-rpc-any-call") $+    testTransportAnyCall transport name1 name2 name3 name4+  ]++timeLimited :: Assertion -> Assertion+timeLimited assn = timeBound (1 * 1000000 :: Int) assn++testTransportEndpointSendReceive :: IO Transport -> Name -> Name -> Assertion+testTransportEndpointSendReceive transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint2 transport $ \endpoint1 endpoint2 -> do+      withBinding2 transport (endpoint1,name1) (endpoint2,name2) $ do+        withConnection transport endpoint1 name2 $ do+          sendMessage endpoint1 name2 $ encode "hello!"+          msg <- receiveMessage endpoint2+          assertEqual "Received message not same as sent" (Right "hello!") (decode msg)+          return ()++testTransportEndpointSend2Receive2 :: IO Transport -> Name -> Name -> Assertion+testTransportEndpointSend2Receive2 transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint2 transport $ \endpoint1 endpoint2 -> do+      withBinding2 transport (endpoint1,name1) (endpoint2,name2) $ do+        withConnection transport endpoint1 name2 $ do+          sendMessage endpoint1 name2 $ encode "hello!"+          msg1 <- receiveMessage endpoint2+          assertEqual "Received message not same as sent" (Right "hello!") (decode msg1)+          sendMessage endpoint1 name2 $ encode "ciao!"+          msg2 <- receiveMessage endpoint2+          assertEqual "Received message not same as sent" (Right "ciao!") (decode msg2)+          return ()++testTransportEndpointSendReceive2SerialServers :: IO Transport -> Name -> Name -> Assertion+testTransportEndpointSendReceive2SerialServers transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint2 transport $ \endpoint1 endpoint2 -> do+      withName endpoint1 name1 $ do+        withBinding transport endpoint2 name2 $ do+          withConnection transport endpoint1 name2 $ do+            sendMessage endpoint1 name2 $ encode "hello!"+            msg <- receiveMessage endpoint2+            assertEqual "Received message not same as sent" (Right "hello!") (decode msg)+            return ()+        withBinding transport endpoint2 name2 $ do+          withConnection transport endpoint1 name2 $ do+            sendMessage endpoint1 name2 $ encode "hello!"+            msg <- receiveMessage endpoint2+            assertEqual "Received message not same as sent" (Right "hello!") (decode msg)+            return ()++testTransportEndpointSendReceive2SerialClients :: IO Transport -> Name -> Name -> Assertion+testTransportEndpointSendReceive2SerialClients transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint2 transport $ \endpoint1 endpoint2 ->+      withBinding transport endpoint2 name2 $ do+        withName endpoint1 name1 $+          withConnection transport endpoint1 name2 $ do+            sendMessage endpoint1 name2 $ encode "hello!"+            msg <- receiveMessage endpoint2+            assertEqual "Received message not same as sent" (Right "hello!") (decode msg)+            return ()+        withName endpoint1 name1 $+          withConnection transport endpoint1 name2 $ do+            sendMessage endpoint1 name2 $ encode "hello!"+            msg <- receiveMessage endpoint2+            assertEqual "Received message not same as sent" (Right "hello!") (decode msg)+            return ()++testTransportOneHearCall :: IO Transport -> Name -> Name -> Assertion+testTransportOneHearCall transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint2 transport $ \endpoint1 endpoint2 ->+      withBinding2 transport (endpoint1,name1) (endpoint2,name2) $ do+        withConnection transport endpoint1 name2 $ do+          _ <- async $ do+              (bytes,reply) <- hear endpoint2 name2 "foo"+              let Right msg = decode bytes+              reply $ encode $ msg ++ "!"+          let cs = newCallSite endpoint1 name1+          bytes <- call cs name2 "foo" $ encode "hello"+          let Right result = decode bytes+          assertEqual "Result not expected value" "hello!" result++testTransportOneCallHear :: IO Transport -> Name -> Name -> Assertion+testTransportOneCallHear transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint transport $ \endpoint1 ->+      withNewEndpoint transport $ \endpoint2 ->+        withBinding transport endpoint1 name1 $+          withBinding transport endpoint2 name2 $ do+            withConnection transport endpoint1 name2 $ do+              let cs = newCallSite endpoint1 name1+              acall <- async $ call cs name2 "foo" $ encode "hello"+              _ <- async $ do+                  (bytes,reply) <- hear endpoint2 name2 "foo"+                  let Right msg = decode bytes+                  reply $ encode $ msg ++ "!"+              bytes <- wait acall+              let Right result = decode bytes+              assertEqual "Result not expected value" "hello!" result++testTransportConcurrentCallHear :: IO Transport -> Name -> Name -> Assertion+testTransportConcurrentCallHear transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint transport $ \endpoint1 ->+      withNewEndpoint transport $ \endpoint2 ->+        withBinding transport endpoint1 name1 $+          withBinding transport endpoint2 name2 $+            withConnection transport endpoint1 name2 $ do+              let cs1 = newCallSite endpoint1 name1+                  cs2 = newCallSite endpoint2 name2+              let call1 = call cs1 name2 "foo" $ encode "hello"+                  hear1 = do+                      (bytes,reply) <- hear endpoint2 name2 "foo"+                      let Right msg = decode bytes+                      reply $ encode $ msg ++ "!"+                  call2 = call cs2 name1 "bar" $ encode "ciao"+                  hear2 = do+                      (bytes,reply) <- hear endpoint1 name1 "bar"+                      let Right msg = decode bytes+                      reply $ encode $ msg ++ "!"+              (result1,(),result2,()) <- runConcurrently $ (,,,)+                  <$> Concurrently call1+                  <*> Concurrently hear1+                  <*> Concurrently call2+                  <*> Concurrently hear2+              assertEqual "Result not expected value" (Right "hello!") (decode result1)+              assertEqual "Result not expected value" (Right "ciao!")  (decode result2)++testTransportOneHandler :: IO Transport -> Name -> Name -> Assertion+testTransportOneHandler transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint transport $ \endpoint1 ->+      withNewEndpoint transport $ \endpoint2 ->+        withBinding transport endpoint1 name1 $+          withBinding transport endpoint2 name2 $+            withConnection transport endpoint1 name2 $ do+              h <- handle endpoint2 name2 "foo" $ \bytes ->+                  let Right msg = decode bytes+                  in return $ encode $ msg ++ "!"+              let cs = newCallSite endpoint1 name1+              bytes <- call cs name2 "foo" $ encode "hello"+              let Right result = decode bytes+              assertEqual "Result not expected value" "hello!" result+              hangup h++testTransportTwoHandlers :: IO Transport -> Name -> Name -> Assertion+testTransportTwoHandlers transportFactory name1 name2 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint transport $ \endpoint1 ->+      withNewEndpoint transport $ \endpoint2 ->+        withBinding transport endpoint1 name1 $+          withBinding transport endpoint2 name2 $+            withConnection transport endpoint1 name2 $ do+              h1 <- handle endpoint2 name2 "foo" $ \bytes ->+                  let Right msg = decode bytes+                  in return $ encode $ msg ++ "!"+              h2 <- handle endpoint2 name2 "bar" $ \bytes ->+                  let Right msg = decode bytes+                  in return $ encode $ msg ++ "?"+              let cs = newCallSite endpoint1 name1+              bytes1 <- call cs name2 "foo" $ encode "hello"+              let Right result1 = decode bytes1+              assertEqual "Result not expected value" "hello!" result1+              bytes2 <- call cs name2 "bar" $ encode "hello"+              let Right result2 = decode bytes2+              assertEqual "Result not expected value" "hello?" result2+              hangup h1+              hangup h2++testTransportGroupCall :: IO Transport -> Name -> Name -> Name -> Name -> Assertion+testTransportGroupCall transportFactory name1 name2 name3 name4 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint4 transport $ \endpoint1 endpoint2 endpoint3 endpoint4 -> do+      withBinding4 transport (endpoint1,name1) (endpoint2,name2) (endpoint3,name3) (endpoint4,name4) $+        withConnection3 transport endpoint1 name2 name3 name4 $ do+          h2 <- handle endpoint2 name2 "foo" $ \bytes -> let Right msg = decode bytes in+                                                            return $ encode $ if msg == "hello" then "foo" else ""+          h3 <- handle endpoint3 name3 "foo" $ \bytes -> let Right msg = decode bytes in+                                                             return $ encode $ if msg == "hello" then "bar" else ""+          h4 <- handle endpoint4 name4 "foo" $ \bytes -> let Right msg = decode bytes in+                                                             return $ encode $ if msg == "hello" then "baz" else ""+          let cs = newCallSite endpoint1 name1+          results <- (gcall cs [name2,name3,name4] "foo" $ encode "hello")+          assertBool "Foo not present in results" (elem (encode "foo") $ M.elems results)+          assertBool "Bar not present in results" (elem (encode "bar") $ M.elems results)+          assertBool "Bar not present in results" (elem (encode "baz") $ M.elems results)+          assertEqual "Unxpected number of results" 3 (M.size results)+          hangup h2+          hangup h3+          hangup h4++testTransportAnyCall :: IO Transport -> Name -> Name -> Name -> Name -> Assertion+testTransportAnyCall transportFactory name1 name2 name3 name4 = timeLimited $ do+  withTransport transportFactory $ \transport ->+    withNewEndpoint4 transport $ \endpoint1 endpoint2 endpoint3 endpoint4 -> do+      withBinding4 transport (endpoint1,name1) (endpoint2,name2) (endpoint3,name3) (endpoint4,name4) $+        withConnection3 transport endpoint1 name2 name3 name4 $ do+          h2 <- handle endpoint2 name2 "foo" $ \bytes -> let Right msg = decode bytes in+                                                            return $ encode $ if msg == "hello" then "foo" else ""+          h3 <- handle endpoint3 name3 "foo" $ \bytes -> let Right msg = decode bytes in+                                                             return $ encode $ if msg == "hello" then "foo" else ""+          h4 <- handle endpoint4 name4 "foo" $ \bytes -> let Right msg = decode bytes in+                                                             return $ encode $ if msg == "hello" then "foo" else ""+          let cs = newCallSite endpoint1 name1+          (result,responder) <- (anyCall cs [name2,name3,name4] "foo" $ encode "hello")+          assertEqual "Response should have been 'foo'" (encode "foo") result+          assertBool "Responder was not in original list of names" $ elem responder [name2,name3,name4]+          hangup h2+          hangup h3+          hangup h4