6
6
-- |
7
7
-- Copyright: © 2018-2019 IOHK
8
8
--
9
+ -- Daedalus <-> Wallet child process port discovery protocol.
9
10
-- Provides a mechanism for Daedalus to discover what port the cardano-wallet
10
11
-- server is listening on.
11
12
--
@@ -19,40 +20,21 @@ module Cardano.Shell.DaedalusIPC
19
20
import Cardano.Prelude
20
21
21
22
import Cardano.BM.Trace (Trace , logError , logInfo , logNotice )
23
+ import Cardano.Shell.NodeIPC.General (NodeChannelError (.. ),
24
+ NodeChannelFinished (.. ),
25
+ runNodeChannel ,
26
+ setupNodeChannel )
22
27
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 )
26
28
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 , (.:) , (.=) )
36
31
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
53
32
33
+ -- | Messages sent from Daedalus -> cardano-wallet
54
34
data MsgIn = QueryPort
55
35
deriving (Show , Eq )
36
+
37
+ -- | Messages sent from cardano-wallet -> Daedalus
56
38
data MsgOut = Started | ReplyPort Int | ParseError Text
57
39
deriving (Show , Eq )
58
40
@@ -80,154 +62,26 @@ daedalusIPC
80
62
-> Int
81
63
-- ^ Port number to send to Daedalus
82
64
-> 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
87
69
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"
90
72
Left NodeChannelDisabled -> do
91
- logInfo trace " Daedalus IPC is not enabled."
73
+ logInfo tr " Daedalus IPC is not enabled."
92
74
sleep
93
75
Left (NodeChannelBadFD err) ->
94
- logError trace $ " Problem starting Daedalus IPC: " <> show err
76
+ logError tr $ " Problem starting Daedalus IPC: " <> show err
95
77
where
96
78
-- How to respond to an incoming message, or when there is an incoming
97
79
-- message that couldn't be parsed.
98
80
msg (Right QueryPort ) = Just (ReplyPort port)
99
81
msg (Left e) = Just (ParseError e)
100
82
101
- -- What to do in context of withNodeChannel
83
+ -- What to do in context of runNodeChannel
102
84
action :: (MsgOut -> IO () ) -> IO ()
103
85
action send = send Started >> sleep
104
86
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