Skip to content

Commit

Permalink
Merge pull request #1229 from input-output-hk/KtorZ/1228/safe-eitherR…
Browse files Browse the repository at this point in the history
…unGet

Define and use safe 'eitherRunGet' to avoid throwing on binary decoding
  • Loading branch information
KtorZ authored Dec 24, 2019
2 parents 398565c + 26dd771 commit 03c4972
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 18 deletions.
1 change: 1 addition & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ library
Cardano.Wallet.Unsafe
Cardano.Wallet.Version
Cardano.Wallet.Version.TH
Data.Binary.Get.Safe
Data.Function.Utils
Data.Time.Text
Data.Time.Utils
Expand Down
37 changes: 23 additions & 14 deletions lib/core/src/Cardano/Wallet/DaedalusIPC.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ import Control.Exception
( IOException, catch, tryJust )
import Control.Monad
( forever )
import Control.Monad.Trans.Class
( lift )
import Control.Monad.Trans.Except
( ExceptT, except, runExceptT )
import Data.Aeson
( FromJSON (..)
, ToJSON (..)
Expand All @@ -48,7 +52,9 @@ import Data.Aeson
import Data.Bifunctor
( first )
import Data.Binary.Get
( getWord32le, getWord64le, runGet )
( getWord32le, getWord64le )
import Data.Binary.Get.Safe
( eitherRunGet )
import Data.Binary.Put
( putLazyByteString, putWord32le, putWord64le, runPut )
import Data.Maybe
Expand Down Expand Up @@ -197,36 +203,39 @@ ipcListener handle hello onMsg = do
replyLoop = forever (recvMsg >>= onMsg >>= maybeSend)

recvMsg :: IO (Either Text msgin)
recvMsg = first T.pack . eitherDecode <$> readMessage handle
recvMsg = fmap (first T.pack) $ (>>= eitherDecode) <$> readMessage handle

sendMsg :: msgout -> IO ()
sendMsg = sendMessage handle . encode

maybeSend :: Maybe msgout -> IO ()
maybeSend = maybe (pure ()) sendMsg

readMessage :: Handle -> IO BL.ByteString
readMessage = if isWindows then windowsReadMessage else posixReadMessage
readMessage :: Handle -> IO (Either String BL.ByteString)
readMessage =
if isWindows
then windowsReadMessage
else fmap Right . posixReadMessage

isWindows :: Bool
isWindows = os == "mingw32"

windowsReadMessage :: Handle -> IO BL.ByteString
windowsReadMessage handle = do
windowsReadMessage :: Handle -> IO (Either String BL.ByteString)
windowsReadMessage handle = runExceptT $ do
_int1 <- readInt32 handle
_int2 <- readInt32 handle
size <- readInt64 handle
BL.hGet handle $ fromIntegral size
size <- readInt64 handle
lift $ BL.hGet handle $ fromIntegral size
where
readInt64 :: Handle -> IO Word64
readInt64 :: Handle -> ExceptT String IO Word64
readInt64 hnd = do
bs <- BL.hGet hnd 8
pure $ runGet getWord64le bs
bs <- lift $ BL.hGet hnd 8
except $ eitherRunGet getWord64le bs

readInt32 :: Handle -> IO Word32
readInt32 :: Handle -> ExceptT String IO Word32
readInt32 hnd = do
bs <- BL.hGet hnd 4
pure $ runGet getWord32le bs
bs <- lift $ BL.hGet hnd 4
except $ eitherRunGet getWord32le bs

posixReadMessage :: Handle -> IO BL.ByteString
posixReadMessage = fmap L8.pack . hGetLine
Expand Down
26 changes: 26 additions & 0 deletions lib/core/src/Data/Binary/Get/Safe.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- |
-- Copyright: © 2018-2019 IOHK
-- License: Apache-2.0
--
-- Extra helpers for safe decoding of binary data.

module Data.Binary.Get.Safe
( eitherRunGet
) where

import Prelude

import Data.Binary.Get
( Get, runGetOrFail )

import qualified Data.ByteString.Lazy as BL


-- | A safe version of 'runGet' which doesn't throw on error.
eitherRunGet
:: Get a
-> BL.ByteString
-> Either String a
eitherRunGet decoder bytes = case runGetOrFail decoder bytes of
Right (_, _, a) -> Right a
Left (_, _, e) -> Left e
1 change: 1 addition & 0 deletions lib/jormungandr/cardano-wallet-jormungandr.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ test-suite unit
, monad-loops
, QuickCheck
, safe
, servant
, text
, text-class
, time
Expand Down
6 changes: 3 additions & 3 deletions lib/jormungandr/src/Cardano/Wallet/Jormungandr/Api/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module Cardano.Wallet.Jormungandr.Api.Types
import Prelude

import Cardano.Wallet.Jormungandr.Binary
( Block, getBlock, runGet )
( Block, eitherRunGet, getBlock )
import Cardano.Wallet.Primitive.Types
( EpochNo (..)
, Hash (..)
Expand Down Expand Up @@ -109,7 +109,7 @@ instance ToHttpApiData AccountId where

instance MimeUnrender JormungandrBinary [BlockId] where
mimeUnrender _ =
pure . fmap (BlockId . Hash) . runGet (many $ getByteString 32)
fmap (map (BlockId . Hash)) . eitherRunGet (many $ getByteString 32)

instance MimeUnrender Hex BlockId where
mimeUnrender _ bs =
Expand All @@ -134,7 +134,7 @@ instance Accept JormungandrBinary where
contentType _ = contentType $ Proxy @Servant.OctetStream

instance MimeUnrender JormungandrBinary Block where
mimeUnrender _ = pure . runGet getBlock
mimeUnrender _ = eitherRunGet getBlock

instance MimeRender JormungandrBinary SealedTx where
mimeRender _ (SealedTx bytes) = BL.fromStrict bytes
Expand Down
3 changes: 3 additions & 0 deletions lib/jormungandr/src/Cardano/Wallet/Jormungandr/Binary.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ module Cardano.Wallet.Jormungandr.Binary
-- * Re-export
, Get
, runGet
, eitherRunGet
, runGetOrFail
, Put
, runPut
Expand Down Expand Up @@ -134,6 +135,8 @@ import Data.Binary.Get
, runGetOrFail
, skip
)
import Data.Binary.Get.Safe
( eitherRunGet )
import Data.Binary.Put
( Put, PutM, putByteString, putWord16be, putWord64be, putWord8, runPut )
import Data.Bits
Expand Down
15 changes: 14 additions & 1 deletion lib/jormungandr/test/unit/Cardano/Wallet/Jormungandr/ApiSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import Cardano.Wallet.Jormungandr.Api.Types
( AccountState (..)
, ApiStakeDistribution (..)
, ApiT (..)
, JormungandrBinary
, StakeApiResponse (..)
)
import Cardano.Wallet.Jormungandr.Binary
( Block )
import Cardano.Wallet.Primitive.Types
( PoolId (..) )
import Cardano.Wallet.Unsafe
Expand All @@ -28,6 +31,8 @@ import Data.Aeson
( eitherDecode )
import Data.Aeson.QQ
( aesonQQ )
import Data.Either
( isLeft )
import Data.Proxy
( Proxy (..) )
import Data.Quantity
Expand All @@ -36,10 +41,12 @@ import Data.Text.Class
( ToText (..) )
import Data.Word
( Word64 )
import Servant.API
( MimeUnrender (..) )
import Test.Aeson.Internal.RoundtripSpecs
( roundtripSpecs )
import Test.Hspec
( Spec, describe, it, shouldBe )
( Spec, describe, it, shouldBe, shouldSatisfy )
import Test.QuickCheck
( Arbitrary (..) )

Expand Down Expand Up @@ -192,6 +199,12 @@ spec = do
Left "Error in $.stake.unassigned: Word64 is either floating or \
\will cause over or underflow: 1.001000023e7"
return ()

describe "MimeUnrender decoding" $ do
it "returns 'Left' when encountering an invalid binary block" $ do
mimeUnrender (Proxy @JormungandrBinary) ""
`shouldSatisfy` (isLeft @_ @Block)

where
decodeJSON = eitherDecode :: BL.ByteString -> Either String StakeApiResponse

Expand Down
1 change: 1 addition & 0 deletions nix/.stack.nix/cardano-wallet-jormungandr.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 03c4972

Please sign in to comment.