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

Commit c12e0b0

Browse files
authored
DaedalusIPC clean ups (#213)
* DaedalusIPC: Clean it up a bit * Add a test program for DaedalusIPC
1 parent ff54336 commit c12e0b0

File tree

5 files changed

+251
-167
lines changed

5 files changed

+251
-167
lines changed

app/daedalus-ipc.hs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{-# LANGUAGE LambdaCase #-}
2+
3+
module Main where
4+
5+
import Cardano.Prelude
6+
7+
import Cardano.BM.Configuration.Static (defaultConfigStdout)
8+
import Cardano.BM.Setup (setupTrace_)
9+
import Cardano.Shell.DaedalusIPC
10+
11+
main :: IO ()
12+
main = fmap readEither <$> getArgs >>= \case
13+
[Right port] -> do
14+
c <- defaultConfigStdout
15+
(tr, _sb) <- setupTrace_ c "daedalus-ipc"
16+
daedalusIPC tr port
17+
_ -> do
18+
putStrLn ("Usage: daedalus-ipc PORT" :: Text)
19+
exitFailure

cardano-shell.cabal

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ library
2828
, Cardano.Shell.Configuration.Lib
2929
-- NodeIPC
3030
, Cardano.Shell.NodeIPC
31+
, Cardano.Shell.NodeIPC.General
3132
, Cardano.Shell.DaedalusIPC
3233
-- Update system
3334
, CardanoShellSpec
@@ -136,6 +137,28 @@ executable node-ipc
136137
-Wredundant-constraints
137138
-Wpartial-fields
138139

140+
executable daedalus-ipc
141+
main-is: app/daedalus-ipc.hs
142+
ghc-options: -threaded -rtsopts -with-rtsopts=-N
143+
build-depends:
144+
base >=4.7 && <5
145+
, cardano-shell
146+
, cardano-prelude
147+
, optparse-applicative
148+
, safe-exceptions
149+
, iohk-monitoring
150+
default-language: Haskell2010
151+
default-extensions: NoImplicitPrelude
152+
OverloadedStrings
153+
154+
ghc-options: -Wall
155+
-Werror
156+
-Wcompat
157+
-Wincomplete-record-updates
158+
-Wincomplete-uni-patterns
159+
-Wredundant-constraints
160+
-Wpartial-fields
161+
139162
executable cardano-launcher
140163
main-is: Main.hs
141164
other-modules:

nix/.stack.nix/cardano-shell.nix

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cardano/Shell/DaedalusIPC.hs

Lines changed: 20 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
-- |
77
-- Copyright: © 2018-2019 IOHK
88
--
9+
-- Daedalus <-> Wallet child process port discovery protocol.
910
-- Provides a mechanism for Daedalus to discover what port the cardano-wallet
1011
-- server is listening on.
1112
--
@@ -19,40 +20,21 @@ module Cardano.Shell.DaedalusIPC
1920
import Cardano.Prelude
2021

2122
import Cardano.BM.Trace (Trace, logError, logInfo, logNotice)
23+
import Cardano.Shell.NodeIPC.General (NodeChannelError (..),
24+
NodeChannelFinished (..),
25+
runNodeChannel,
26+
setupNodeChannel)
2227
import Control.Concurrent (threadDelay)
23-
import Control.Concurrent.Async (concurrently_, race)
24-
import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
25-
import Control.Exception (IOException, catch, tryJust)
2628
import Control.Monad (forever)
27-
import Data.Aeson (FromJSON (..), ToJSON (..), Value (..),
28-
eitherDecode, encode, object, withObject, (.:),
29-
(.=))
30-
import Data.Bifunctor (first)
31-
import Data.Binary.Get (getWord32le, getWord64le, runGet)
32-
import Data.Binary.Put (putLazyByteString, putWord32le, putWord64le,
33-
runPut)
34-
import Data.Functor (($>))
35-
import Data.Maybe (fromMaybe)
29+
import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), object,
30+
withObject, (.:), (.=))
3631
import Data.Text (Text)
37-
import Data.Word (Word32, Word64)
38-
39-
import GHC.IO.Handle.FD (fdToHandle)
40-
import System.Environment (lookupEnv)
41-
import System.Info (arch)
42-
import System.IO (Handle, hFlush, hGetLine, hSetNewlineMode,
43-
noNewlineTranslation)
44-
import System.IO.Error (IOError, userError)
45-
import Text.Read (readEither)
46-
47-
import qualified Data.ByteString.Lazy as BL
48-
import qualified Data.ByteString.Lazy.Char8 as L8
49-
import qualified Data.Text as T
50-
51-
----------------------------------------------------------------------------
52-
-- Daedalus <-> Wallet child process port discovery protocol
5332

33+
-- | Messages sent from Daedalus -> cardano-wallet
5434
data MsgIn = QueryPort
5535
deriving (Show, Eq)
36+
37+
-- | Messages sent from cardano-wallet -> Daedalus
5638
data MsgOut = Started | ReplyPort Int | ParseError Text
5739
deriving (Show, Eq)
5840

@@ -80,154 +62,26 @@ daedalusIPC
8062
-> Int
8163
-- ^ Port number to send to Daedalus
8264
-> IO ()
83-
daedalusIPC trace port = withNodeChannel (pure . msg) action >>= \case
84-
Right runServer -> do
85-
logInfo trace "Daedalus IPC server starting"
86-
runServer >>= \case
65+
daedalusIPC tr port = setupNodeChannel >>= \case
66+
Right chan -> do
67+
logInfo tr "Daedalus IPC server starting"
68+
runNodeChannel (pure . msg) action chan >>= \case
8769
Left (NodeChannelFinished err) ->
88-
logNotice trace $ "Daedalus IPC finished for this reason: " <> show err
89-
Right () -> logError trace "Unreachable code"
70+
logNotice tr $ "Daedalus IPC finished for this reason: " <> show err
71+
Right () -> logError tr "Unreachable code"
9072
Left NodeChannelDisabled -> do
91-
logInfo trace "Daedalus IPC is not enabled."
73+
logInfo tr "Daedalus IPC is not enabled."
9274
sleep
9375
Left (NodeChannelBadFD err) ->
94-
logError trace $ "Problem starting Daedalus IPC: " <> show err
76+
logError tr $ "Problem starting Daedalus IPC: " <> show err
9577
where
9678
-- How to respond to an incoming message, or when there is an incoming
9779
-- message that couldn't be parsed.
9880
msg (Right QueryPort) = Just (ReplyPort port)
9981
msg (Left e) = Just (ParseError e)
10082

101-
-- What to do in context of withNodeChannel
83+
-- What to do in context of runNodeChannel
10284
action :: (MsgOut -> IO ()) -> IO ()
10385
action send = send Started >> sleep
10486

105-
sleep = threadDelay maxBound
106-
107-
----------------------------------------------------------------------------
108-
-- NodeJS child_process IPC protocol
109-
-- https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
110-
111-
-- | Possible reasons why the node channel can't be set up.
112-
data NodeChannelError
113-
= NodeChannelDisabled
114-
-- ^ This process has not been started as a nodejs @'ipc'@ child_process.
115-
| NodeChannelBadFD Text
116-
-- ^ The @NODE_CHANNEL_FD@ environment variable has an incorrect value.
117-
deriving (Show, Eq)
118-
119-
-- | The only way a node channel finishes on its own is if there is some error
120-
-- reading or writing to its file descriptor.
121-
newtype NodeChannelFinished = NodeChannelFinished IOError
122-
123-
-- | Communicate with a parent process using a NodeJS-specific protocol. This
124-
-- process must have been spawned with one of @stdio@ array entries set to
125-
-- @'ipc'@.
126-
--
127-
-- If the channel could be set up, then it returns a function for communicating
128-
-- with the parent process.
129-
withNodeChannel
130-
:: (FromJSON msgin, ToJSON msgout)
131-
=> (Either Text msgin -> IO (Maybe msgout))
132-
-- ^ Handler for messages coming from the parent process. Left values are
133-
-- for JSON parse errors. The handler can optionally return a reply
134-
-- message.
135-
-> ((msgout -> IO ()) -> IO a)
136-
-- ^ Action to run with the channel. It is passed a function for sending
137-
-- messages to the parent process.
138-
-> IO (Either NodeChannelError (IO (Either NodeChannelFinished a)))
139-
withNodeChannel onMsg handleMsg = fmap setup <$> lookupNodeChannel
140-
where
141-
setup handle = do
142-
chan <- newEmptyMVar
143-
let ipc = ipcListener handle onMsg chan
144-
action' = handleMsg (putMVar chan)
145-
race ipc action'
146-
147-
-- | Parse the NODE_CHANNEL_FD variable, if it's set, and convert to a
148-
-- 'System.IO.Handle'.
149-
lookupNodeChannel :: IO (Either NodeChannelError Handle)
150-
lookupNodeChannel = (fromMaybe "" <$> lookupEnv "NODE_CHANNEL_FD") >>= \case
151-
"" -> pure (Left NodeChannelDisabled)
152-
var -> case readEither var of
153-
Left err -> pure . Left . NodeChannelBadFD $
154-
"unable to parse NODE_CHANNEL_FD: " <> T.pack err
155-
Right fd -> tryJust handleBadFd (fdToHandle fd)
156-
where
157-
handleBadFd :: IOException -> Maybe NodeChannelError
158-
handleBadFd = Just . NodeChannelBadFD . T.pack . show
159-
160-
ipcListener
161-
:: forall msgin msgout. (FromJSON msgin, ToJSON msgout)
162-
=> Handle
163-
-> (Either Text msgin -> IO (Maybe msgout))
164-
-> MVar msgout
165-
-> IO NodeChannelFinished
166-
ipcListener handle onMsg chan = NodeChannelFinished <$> do
167-
hSetNewlineMode handle noNewlineTranslation
168-
(concurrently_ replyLoop sendLoop $> unexpected) `catch` pure
169-
where
170-
sendLoop, replyLoop :: IO ()
171-
replyLoop = forever (recvMsg >>= onMsg >>= maybeSend)
172-
sendLoop = forever (takeMVar chan >>= sendMsg)
173-
174-
recvMsg :: IO (Either Text msgin)
175-
recvMsg = first T.pack . eitherDecode <$> readMessage handle
176-
177-
sendMsg :: msgout -> IO ()
178-
sendMsg = sendMessage handle . encode
179-
180-
maybeSend :: Maybe msgout -> IO ()
181-
maybeSend = maybe (pure ()) (putMVar chan)
182-
183-
unexpected = userError "ipcListener: unreachable code"
184-
185-
readMessage :: Handle -> IO BL.ByteString
186-
readMessage = if isWindows then windowsReadMessage else posixReadMessage
187-
188-
isWindows :: Bool
189-
isWindows = arch == "windows"
190-
191-
windowsReadMessage :: Handle -> IO BL.ByteString
192-
windowsReadMessage handle = do
193-
_int1 <- readInt32 handle
194-
_int2 <- readInt32 handle
195-
size <- readInt64 handle
196-
-- logInfo $ "int is: " <> (show [_int1, _int2]) <> " and blob is: " <> (show blob)
197-
BL.hGet handle $ fromIntegral size
198-
where
199-
readInt64 :: Handle -> IO Word64
200-
readInt64 hnd = do
201-
bs <- BL.hGet hnd 8
202-
pure $ runGet getWord64le bs
203-
204-
readInt32 :: Handle -> IO Word32
205-
readInt32 hnd = do
206-
bs <- BL.hGet hnd 4
207-
pure $ runGet getWord32le bs
208-
209-
posixReadMessage :: Handle -> IO BL.ByteString
210-
posixReadMessage = fmap L8.pack . hGetLine
211-
212-
sendMessage :: Handle -> BL.ByteString -> IO ()
213-
sendMessage handle msg = send handle msg >> hFlush handle
214-
where
215-
send = if isWindows then sendMessageWindows else sendMessagePosix
216-
217-
sendMessageWindows :: Handle -> BL.ByteString -> IO ()
218-
sendMessageWindows = sendWindowsMessage' 1 0
219-
220-
sendWindowsMessage' :: Word32 -> Word32 -> Handle -> BL.ByteString -> IO ()
221-
sendWindowsMessage' int1 int2 handle blob =
222-
L8.hPut handle $ runPut $ mconcat parts
223-
where
224-
blob' = blob <> "\n"
225-
parts =
226-
[ putWord32le int1
227-
, putWord32le int2
228-
, putWord64le $ fromIntegral $ BL.length blob'
229-
, putLazyByteString blob'
230-
]
231-
232-
sendMessagePosix :: Handle -> BL.ByteString -> IO ()
233-
sendMessagePosix = L8.hPutStrLn
87+
sleep = forever $ threadDelay maxBound

0 commit comments

Comments
 (0)