Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

Commit d8a8163

Browse files
authored
[GH-78] Generalize node IPC so it can be extended. (#89)
1 parent 28dba80 commit d8a8163

File tree

6 files changed

+89
-46
lines changed

6 files changed

+89
-46
lines changed

app/NodeIPC/Main.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ module Main
33
) where
44

55
import Cardano.Prelude
6-
import Cardano.Shell.NodeIPC (Port (..), startNodeJsIPC)
6+
import Cardano.Shell.NodeIPC (Port (..), ProtocolDuration (..),
7+
startNodeJsIPC)
78

89
main :: IO ()
910
main = do
1011
let port = Port 8090
11-
startNodeJsIPC port
12+
startNodeJsIPC SingleMessage port

src/Cardano/Shell/NodeIPC.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Cardano.Shell.NodeIPC
1111
, ReadHandle(..)
1212
, WriteHandle(..)
1313
-- * IPC protocol
14+
, ProtocolDuration (..)
1415
, startNodeJsIPC
1516
, startIPC
1617
-- ** Exceptions
@@ -36,8 +37,8 @@ import Cardano.Shell.NodeIPC.Example (exampleWithFD,
3637
getReadWriteHandles)
3738
import Cardano.Shell.NodeIPC.Lib (MessageSendFailure (..), MsgIn (..),
3839
MsgOut (..), NodeIPCException (..),
39-
Port (..), isHandleClosed,
40-
isIPCException,
40+
Port (..), ProtocolDuration (..),
41+
isHandleClosed, isIPCException,
4142
isNodeChannelCannotBeFound,
4243
isUnreadableHandle,
4344
isUnwritableHandle, startIPC,

src/Cardano/Shell/NodeIPC/Example.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import System.Posix.Process (exitImmediately, forkProcess)
3030
import System.Process (createPipe)
3131

3232
import Cardano.Shell.NodeIPC.Lib (MsgIn (..), MsgOut (..), Port (..),
33-
startIPC)
33+
ProtocolDuration (..), startIPC)
3434
import Cardano.Shell.NodeIPC.Message (ReadHandle (..),
3535
WriteHandle (..), readMessage,
3636
sendMessage)
@@ -87,7 +87,7 @@ ipcServer clientWriteHandle msgin = do
8787
(serverReadHandle, serverWriteHandle) <- getReadWriteHandles
8888
-- Send message to server
8989
sendMessage serverWriteHandle msgin
90-
startIPC serverReadHandle clientWriteHandle nodePort
90+
startIPC SingleMessage serverReadHandle clientWriteHandle nodePort
9191

9292
-- | Read message wigh given 'ReadHandle'
9393
receieveMessages :: ReadHandle -> IO (MsgOut, MsgOut)

src/Cardano/Shell/NodeIPC/Lib.hs

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
<https://github.com/input-output-hk/cardano-shell/blob/develop/specs/CardanoShellSpec.pdf>
44
-}
55

6-
{-# LANGUAGE DeriveGeneric #-}
7-
{-# LANGUAGE LambdaCase #-}
8-
{-# LANGUAGE OverloadedStrings #-}
9-
{-# LANGUAGE RankNTypes #-}
10-
{-# LANGUAGE ScopedTypeVariables #-}
6+
{-# LANGUAGE DeriveGeneric #-}
7+
{-# LANGUAGE LambdaCase #-}
8+
{-# LANGUAGE RankNTypes #-}
9+
{-# LANGUAGE ScopedTypeVariables #-}
1110

1211
module Cardano.Shell.NodeIPC.Lib
1312
( startNodeJsIPC
1413
, startIPC
1514
, Port (..)
15+
, ProtocolDuration (..)
1616
-- * Testing
1717
, getIPCHandle
1818
, MsgIn(..)
@@ -52,6 +52,14 @@ import Cardano.Shell.NodeIPC.Message (MessageException,
5252

5353
import qualified Prelude as P (Show (..))
5454

55+
-- | The way the IPC protocol works.
56+
data ProtocolDuration
57+
= SingleMessage
58+
-- ^ Responds to a single message and exits
59+
| MultiMessage
60+
-- ^ Runs forever responding to messages
61+
deriving (Eq, Show)
62+
5563
-- | Message expecting from Daedalus
5664
data MsgIn
5765
= QueryPort
@@ -120,7 +128,13 @@ instance ToJSON MessageSendFailure where
120128
-- (e.g @8090@)
121129
newtype Port = Port
122130
{ getPort :: Word16
123-
} deriving Show
131+
} deriving (Eq, Show, Generic)
132+
133+
instance FromJSON Port where
134+
parseJSON = genericParseJSON opts
135+
136+
instance ToJSON Port where
137+
toEncoding = genericToEncoding opts
124138

125139
instance Arbitrary Port where
126140
arbitrary = Port <$> arbitrary
@@ -162,44 +176,58 @@ getIPCHandle = do
162176
Right fd -> liftIO $ fdToHandle fd
163177

164178
-- | Start IPC with given 'ReadHandle', 'WriteHandle' and 'Port'
165-
startIPC :: forall m. (MonadIO m) => ReadHandle -> WriteHandle -> Port -> m ()
166-
startIPC readHandle writeHandle port = liftIO $ void $ ipcListener readHandle writeHandle port
179+
startIPC :: forall m. (MonadIO m) => ProtocolDuration -> ReadHandle -> WriteHandle -> Port -> m ()
180+
startIPC protocolDuration readHandle writeHandle port = liftIO $ void $ ipcListener protocolDuration readHandle writeHandle port
167181

168182
-- | Start IPC with NodeJS
169183
--
170184
-- This only works if NodeJS spawns the Haskell executable as child process
171185
-- (See @server.js@ as an example)
172-
startNodeJsIPC :: forall m. (MonadIO m) => Port -> m ()
173-
startNodeJsIPC port = do
174-
handle <- liftIO $ getIPCHandle
175-
let readHandle = ReadHandle handle
186+
startNodeJsIPC :: forall m. (MonadIO m) => ProtocolDuration -> Port -> m ()
187+
startNodeJsIPC protocolDuration port = do
188+
handle <- liftIO $ getIPCHandle
189+
let readHandle = ReadHandle handle
176190
let writeHandle = WriteHandle handle
177-
liftIO $ void $ ipcListener readHandle writeHandle port
191+
liftIO $ void $ ipcListener protocolDuration readHandle writeHandle port
192+
193+
-- | Function for handling the protocol
194+
handleIPCProtocol :: forall m. (MonadIO m) => Port -> MsgIn -> m MsgOut
195+
handleIPCProtocol (Port port) = \case
196+
QueryPort -> pure (ReplyPort port)
197+
Ping -> pure Pong
198+
MessageInFailure f -> pure $ MessageOutFailure f
178199

179200
-- | Start IPC listener with given Handles and Port
180201
--
181202
-- When the listener recieves 'Ping' it will return 'Pong'.
182203
--
183204
-- If it recieves 'QueryPort', then the listener
184205
-- responds with 'ReplyPort' with 'Port',
185-
ipcListener :: forall m . (MonadIO m, MonadCatch m, MonadMask m) => ReadHandle -> WriteHandle -> Port -> m ()
186-
ipcListener readHandle@(ReadHandle rHndl) writeHandle@(WriteHandle wHndl) (Port port) =
187-
do
188-
checkHandles readHandle writeHandle
189-
catches handleMsgIn [Handler handler, Handler handleMsgError]
190-
`finally`
191-
shutdown
206+
ipcListener
207+
:: forall m. (MonadIO m, MonadCatch m, MonadMask m)
208+
=> ProtocolDuration
209+
-> ReadHandle
210+
-> WriteHandle
211+
-> Port
212+
-> m ()
213+
ipcListener protocolDuration readHandle@(ReadHandle rHndl) writeHandle@(WriteHandle wHndl) port = do
214+
checkHandles readHandle writeHandle
215+
handleMsgIn `catches` [Handler handler, Handler handleMsgError] `finally` shutdown
192216
where
193217
handleMsgIn :: m ()
194218
handleMsgIn = do
195219
liftIO $ hSetNewlineMode rHndl noNewlineTranslation
196-
send Started
197-
msgIn <- readMessage readHandle
198-
case msgIn of
199-
QueryPort -> send (ReplyPort port)
200-
Ping -> send Pong
201-
-- TODO:Handle them nicely
202-
MessageInFailure _ -> return ()
220+
send Started -- Send the message first time the IPC is up!
221+
222+
let frequencyFunction = case protocolDuration of
223+
SingleMessage -> void
224+
MultiMessage -> forever
225+
226+
-- Fetch message and respond to it
227+
frequencyFunction $ do
228+
msgIn <- readMessage readHandle -- Read message
229+
messageByteString <- handleIPCProtocol port msgIn -- Respond
230+
sendMessage writeHandle messageByteString -- Write to client/server
203231

204232
send :: MsgOut -> m ()
205233
send = sendMessage writeHandle

src/Cardano/Shell/NodeIPC/Message.hs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ between Daedalus and Cardano-node
66

77
module Cardano.Shell.NodeIPC.Message
88
( sendMessage
9+
, sendMessageByteString
910
, readMessage
1011
, MessageException(..)
1112
, ReadHandle(..)
@@ -47,8 +48,11 @@ newtype WriteHandle = WriteHandle
4748

4849
-- | Send JSON message with given 'WriteHandle'
4950
sendMessage :: (MonadIO m, ToJSON msg) => WriteHandle -> msg -> m ()
50-
sendMessage (WriteHandle hndl) cmd = liftIO $ do
51-
send buildOS $ encode cmd
51+
sendMessage writeHandle cmd = sendMessageByteString writeHandle (encode cmd)
52+
53+
sendMessageByteString :: (MonadIO m) => WriteHandle -> BSL.ByteString -> m ()
54+
sendMessageByteString (WriteHandle hndl) byteString = liftIO $ do
55+
send buildOS $ byteString
5256
hFlush hndl
5357
where
5458
send :: OS -> BSL.ByteString -> IO ()
@@ -66,6 +70,10 @@ sendMessage (WriteHandle hndl) cmd = liftIO $ do
6670
sendLinuxMessage :: BSL.ByteString -> IO ()
6771
sendLinuxMessage = BSLC.hPutStrLn hndl
6872

73+
74+
75+
76+
6977
-- | Read JSON message with given 'ReadHandle'
7078
readMessage :: (MonadIO m, MonadThrow m, FromJSON msg) => ReadHandle -> m msg
7179
readMessage (ReadHandle hndl) = do

test/NodeIPCSpec.hs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import Test.QuickCheck.Monadic (assert, monadicIO, run)
2121
import Cardano.Shell.NodeIPC (MessageException,
2222
MessageSendFailure (..), MsgIn (..),
2323
MsgOut (..), NodeIPCException (..),
24-
Port (..), ReadHandle (..),
25-
WriteHandle (..), exampleWithFD,
26-
exampleWithProcess, getReadWriteHandles,
27-
isHandleClosed, isIPCException,
24+
Port (..), ProtocolDuration (..),
25+
ReadHandle (..), WriteHandle (..),
26+
exampleWithFD, exampleWithProcess,
27+
getReadWriteHandles, isHandleClosed,
28+
isIPCException,
2829
isNodeChannelCannotBeFound,
2930
isUnreadableHandle, isUnwritableHandle,
3031
readMessage, sendMessage, startIPC,
@@ -77,23 +78,23 @@ nodeIPCSpec = do
7778
eResult <- run $ try $ do
7879
(readHandle, writeHandle) <- getReadWriteHandles
7980
closedReadHandle <- (\(ReadHandle hndl) -> hClose hndl >> return (ReadHandle hndl)) readHandle
80-
startIPC closedReadHandle writeHandle port
81+
startIPC SingleMessage closedReadHandle writeHandle port
8182
assert $ isLeft (eResult :: Either NodeIPCException ())
8283
whenLeft eResult $ \exception -> assert $ isHandleClosed exception
8384

8485
it "should throw NodeIPCException when unreadable handle is given" $ monadicIO $ do
8586
eResult <- run $ try $ do
8687
(readHandle, writeHandle) <- getReadWriteHandles
8788
let (unReadableHandle, _) = swapHandles readHandle writeHandle
88-
startIPC unReadableHandle writeHandle port
89+
startIPC SingleMessage unReadableHandle writeHandle port
8990
assert $ isLeft (eResult :: Either NodeIPCException ())
9091
whenLeft eResult $ \exception -> assert $ isUnreadableHandle exception
9192

9293
it "should throw NodeIPCException when unwritable handle is given" $ monadicIO $ do
9394
eResult <- run $ try $ do
9495
(readHandle, writeHandle) <- getReadWriteHandles
9596
let (_, unWritableHandle) = swapHandles readHandle writeHandle
96-
startIPC readHandle unWritableHandle port
97+
startIPC SingleMessage readHandle unWritableHandle port
9798
assert $ isLeft (eResult :: Either NodeIPCException ())
9899
whenLeft eResult $ \exception -> assert $ isUnwritableHandle exception
99100

@@ -120,7 +121,7 @@ nodeIPCSpec = do
120121
handlesClosed <- run $ do
121122
(clientReadHandle, clientWriteHandle) <- getReadWriteHandles
122123
(serverReadHandle, serverWriteHandle) <- getReadWriteHandles
123-
as <- async $ startIPC serverReadHandle clientWriteHandle port
124+
as <- async $ startIPC SingleMessage serverReadHandle clientWriteHandle port
124125
let readClientMessage = readMessage clientReadHandle
125126
sendServer = sendMessage serverWriteHandle
126127
_ <- readClientMessage
@@ -143,7 +144,7 @@ nodeIPCSpec = do
143144

144145
describe "startNodeJsIPC" $
145146
it "should throw NodeIPCException when it is not spawned by NodeJS process" $ monadicIO $ do
146-
eResult <- run $ try $ startNodeJsIPC port
147+
eResult <- run $ try $ startNodeJsIPC SingleMessage port
147148
assert $ isLeft (eResult :: Either NodeIPCException ())
148149
whenLeft eResult $ \exception -> assert $ isNodeChannelCannotBeFound exception
149150
where
@@ -165,7 +166,7 @@ nodeIPCSpec = do
165166
(clientReadHandle, clientWriteHandle) <- getReadWriteHandles
166167
(serverReadHandle, _) <- getReadWriteHandles
167168

168-
as <- async $ startIPC serverReadHandle clientWriteHandle port
169+
as <- async $ startIPC SingleMessage serverReadHandle clientWriteHandle port
169170
(_ :: MsgOut) <- readMessage clientReadHandle
170171
return (as, serverReadHandle, clientWriteHandle)
171172

@@ -187,7 +188,11 @@ testStartNodeIPC port msg = do
187188

188189
-- Start the server
189190
(_, responses) <-
190-
startIPC serverReadHandle clientWriteHandle port
191+
startIPC
192+
SingleMessage
193+
serverReadHandle
194+
clientWriteHandle
195+
port
191196
`concurrently`
192197
do
193198
-- Use these functions so you don't pass the wrong handle by mistake

0 commit comments

Comments
 (0)