-- | Implementation of the Enea LINX Gateway protocol in Haskell. More
-- information about LINX and the LINX Gateway can be found on its
-- project page on Sourceforge:
--
-- [LINX Gateway Documentation]
--
-- User's guide:
--
-- LINX protcols:
--
-- [Example application]
--
-- Bundled with this software package is an example application
-- consisting of one ping server program and one ping client
-- program. The example programs are demonstrating several aspects of
-- the gateway API.
--
-- The code can be browsed in the examples directory at the project's
-- GitHub:
--
-- In order to run the examples a LINX Gateway server must be setup
-- and be available in your IP network. For the samples below the
-- gateway server is running at 192.168.122.8 port 21768.
--
-- > cabal configure
-- > cabal build
-- > cabal run Ping client 192.168.122.8 21768
-- > cabal run Ping 192.168.122.8 21768
--
-- The order in which the server and the client is started is not
-- important. The client is also supervising the server, so if the
-- server is terminated the client is trying to reconnect to the
-- server again once it's restarted.
--
-- Several clients can be started.
module Network.Linx.Gateway
( Gateway (..)
, HostName
, PortID (..)
, Signal (..)
, SigNo (..)
, SignalSelector (..)
, Pid
, Timeout (..)
, create
, destroy
, hunt
, receiveWithTimeout
, receive
, sendWithSender
, sendWithSelf
, attach
, detach
, askName
, withGateway
) where
import Control.Applicative ((<$>))
import Control.Exception (bracket)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Lazy.Char8 as LBSC
import Data.Maybe (fromJust)
import Network (HostName, PortID (..), connectTo)
import Network.Socket (withSocketsDo)
import Network.Linx.Gateway.Message
( Message (..)
, Header (..)
, ProtocolPayload (..)
, PayloadType (..)
, encode
, mkInterfaceRequest
, mkCreateRequest
, mkDestroyRequest
, mkHuntRequest
, mkReceiveRequest
, mkSendRequest
, mkAttachRequest
, mkDetachRequest
, mkNameRequest
, headerSize
, decodeHeader
, decodeProtocolPayload
)
import Network.Linx.Gateway.Signal
( Signal (..)
, SignalSelector (..)
)
import Network.Linx.Gateway.Types
( Version (..)
, Status (..)
, Flags (..)
, Length (..)
, Pid (..)
, Timeout (..)
, SigNo (..)
, Attref (..)
, CString (..)
, mkCString
)
import System.IO (Handle)
-- | Gateway instance.
data Gateway =
Gateway {
-- | The socket handle towards the gateway server.
handle :: !Handle
-- | The LINX 'Pid' of the gateway instance.
, self :: !Pid
-- | The max length of a 'Signal' payload the gateway
-- server is accepting.
, maxSignal :: !Length
-- | The type of operations in the gateway protocol that
-- the gateway server is accepting.
, accept :: ![PayloadType]}
deriving (Show, Eq)
-- | Create a new client instance in the gateway. The gateway is
-- addressed by a hostname and a port id.
create :: String -> HostName -> PortID -> IO Gateway
create name' hostname port =
withSocketsDo $ do
gw <- connectTo hostname port
createReply <- expectPayload gw =<< talkGateway gw (mkCreateRequest name')
ifReply <- expectPayload gw
=<< talkGateway gw (mkInterfaceRequest V100 BigEndian)
return $ Gateway gw (pid createReply)
(maxSigSize createReply)
(payloadTypes ifReply)
-- | Destroy a client.
destroy :: Gateway -> IO ()
destroy gw = do
_ <- expectPayload (handle gw)
=<< talkGateway (handle gw) (mkDestroyRequest (self gw))
return ()
-- | Ask the gateway server to execute a hunt call. If the hunted
-- process is available at the moment of the hunt its pid is returned
-- immediately.
hunt :: Gateway -> String -> Signal -> IO (Maybe Pid)
hunt gw client signal' = do
reply <- expectPayload (handle gw)
=<< talkGateway (handle gw) (mkHuntRequest signal' (mkCString client))
let pid' = pid reply
return $
case pid' of
Pid 0 -> Nothing
_ -> Just pid'
-- | Ask the gateway server to execute a receive call with the
-- specified timeout value. If no signal is received within the time
-- the value of 'Nothing' is returned.
receiveWithTimeout :: Gateway -> Timeout -> SignalSelector
-> IO (Maybe (Pid, Signal))
receiveWithTimeout gw tmo sigSel' = do
reply <- expectPayload (handle gw)
=<< talkGateway (handle gw) (mkReceiveRequest tmo sigSel')
return $
case reply of
ReceiveReply Success (Pid 0) (Pid 0) NoSignal -> Nothing
ReceiveReply Success senderPid' _ signal' ->
Just (senderPid', signal')
_ -> Nothing
-- | Ask the gateway server to execute a receive call with infinitely
-- long waiting time.
receive :: Gateway -> SignalSelector -> IO (Pid, Signal)
receive gw sigSel' = fromJust <$> receiveWithTimeout gw Infinity sigSel'
-- | Ask the gateway server to execute a send_w_s call.
sendWithSender :: Gateway -> Pid -> Pid -> Signal -> IO ()
sendWithSender gw fromPid' destPid' signal' = do
_ <- expectPayload (handle gw)
=<< talkGateway (handle gw) (mkSendRequest fromPid' destPid' signal')
return ()
-- | Ask the gateway server to execute a send call_w_s call where the
-- sender is the pid stored in the gateway record.
sendWithSelf :: Gateway -> Pid -> Signal -> IO ()
sendWithSelf gw = sendWithSender gw (self gw)
-- | Ask the gateway server to execute an attach call.
attach :: Gateway -> Pid -> Signal -> IO Attref
attach gw pid' signal' = do
reply <- expectPayload (handle gw)
=<< talkGateway (handle gw) (mkAttachRequest pid' signal')
return $ attref reply
-- | Ask the gateway server to execute a detach call.
detach :: Gateway -> Attref -> IO ()
detach gw attref' = do
_ <- expectPayload (handle gw)
=<< talkGateway (handle gw) (mkDetachRequest attref')
return ()
-- | Ask the gateway server about its name.
askName :: Gateway -> IO String
askName gw = do
reply <- expectPayload (handle gw)
=<< talkGateway (handle gw) mkNameRequest
let CString lbs = name reply
return $ LBSC.unpack lbs
-- | Convenience function to handle acquire and release semantics for
-- creating and destroying a gateway instance.
withGateway :: String -> HostName -> PortID -> (Gateway -> IO a) -> IO a
withGateway service host port handler =
bracket (create service host port) destroy handler
talkGateway :: Handle -> Message -> IO Header
talkGateway hGw message = do
LBS.hPut hGw $ encode message
decodeHeader <$> readGateway hGw headerSize
expectPayload :: Handle -> Header -> IO ProtocolPayload
expectPayload hGw header =
decodeProtocolPayload (payloadType header)
<$> readGateway hGw (payloadLength header)
readGateway :: Handle -> Length -> IO LBS.ByteString
readGateway hGw (Length len) = LBS.hGet hGw (fromIntegral len)