Skip to content

Commit ea14d58

Browse files
committed
dmq-node: added cddl for LocalMsgNotification protocol
We only check the decoder by generating terms with `cddl`.
1 parent d8e83ce commit ea14d58

File tree

6 files changed

+149
-11
lines changed

6 files changed

+149
-11
lines changed

dmq-node/cddl/Main.hs

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
{-# LANGUAGE QuantifiedConstraints #-}
1010
{-# LANGUAGE RankNTypes #-}
1111
{-# LANGUAGE ScopedTypeVariables #-}
12+
{-# LANGUAGE StandaloneDeriving #-}
1213
{-# LANGUAGE TupleSections #-}
1314
{-# LANGUAGE TypeApplications #-}
1415
{-# LANGUAGE TypeFamilies #-}
@@ -20,33 +21,38 @@
2021

2122
module Main (main) where
2223

23-
import Control.Monad (forM_)
24+
import Control.Monad (forM, forM_, unless)
2425
import Control.Monad.Except (ExceptT (..), runExceptT)
2526

2627
import Codec.CBOR.Read qualified as CBOR
27-
-- import Codec.CBOR.Term (Term (..))
28+
import Codec.CBOR.Term (Term (..))
2829
import Codec.CBOR.Term qualified as CBOR
29-
-- import Codec.CBOR.Write qualified as CBOR
30+
import Codec.CBOR.Write qualified as CBOR
3031
-- import Codec.Serialise.Class (Serialise)
3132
-- import Codec.Serialise.Class qualified as Serialise
3233
import Codec.Serialise.Decoding qualified as CBOR
3334
-- import Codec.Serialise.Encoding qualified as CBOR
3435

3536
import Data.Bool (bool)
36-
import Data.ByteString.Base16.Lazy as BL.Base16
37+
import Data.ByteString.Base16.Lazy qualified as BL.Base16
3738
import Data.ByteString.Lazy qualified as BL
3839
import Data.ByteString.Lazy.Char8 qualified as BL.Char8
40+
import Text.Printf
3941

4042
import System.Directory (doesDirectoryExist)
4143
import System.Environment (setEnv)
42-
import System.Exit (ExitCode (..))
44+
import System.Exit (ExitCode (..), die)
4345
import System.FilePath
4446
import System.IO (hClose)
4547
import System.IO.Temp (withTempFile)
4648
import System.Process.ByteString.Lazy
4749

50+
import Network.TypedProtocol.Codec
51+
4852
import Cardano.KESAgent.Protocols.StandardCrypto (StandardCrypto)
4953

54+
import DMQ.Protocol.LocalMsgNotification.Codec
55+
import DMQ.Protocol.LocalMsgNotification.Type as LocalMsgNotification
5056
import DMQ.Protocol.SigSubmission.Codec
5157
import DMQ.Protocol.SigSubmission.Type
5258

@@ -62,29 +68,58 @@ main = do
6268
defaultMain (tests cddlSpecs)
6369

6470
tests :: CDDLSpecs -> TestTree
65-
tests CDDLSpecs { cddlSig
71+
tests CDDLSpecs { cddlSig,
72+
cddlLocalMsgNotification
6673
} =
6774
adjustOption (const $ QuickCheckMaxSize 10) $
6875
testGroup "cddl"
6976
[ testGroup "decoding"
7077
-- validate decoder by generating messages from the specification
7178
[ testCase "Sig" (unit_decodeSig cddlSig)
79+
, testCase "LocalMsgNotification" (unit_decodeLocalMsgNotification cddlLocalMsgNotification)
7280
]
81+
-- TODO: validate `LocalMsgNotification` encoder
7382
]
7483

7584
newtype CDDLSpec ps = CDDLSpec BL.ByteString
7685

7786
type AnnSigRawWithSignedBytes = BL.ByteString -> SigRawWithSignedBytes StandardCrypto
7887

7988
data CDDLSpecs = CDDLSpecs {
80-
cddlSig :: CDDLSpec AnnSigRawWithSignedBytes
89+
cddlSig :: CDDLSpec AnnSigRawWithSignedBytes,
90+
cddlLocalMsgNotification :: CDDLSpec (LocalMsgNotification (Sig StandardCrypto))
8191
}
8292

8393

8494
unit_decodeSig :: CDDLSpec AnnSigRawWithSignedBytes
8595
-> Assertion
8696
unit_decodeSig spec = validateDecoder spec decodeSig 100
8797

98+
unit_decodeLocalMsgNotification :: CDDLSpec (LocalMsgNotification (Sig StandardCrypto))
99+
-> Assertion
100+
unit_decodeLocalMsgNotification spec =
101+
validateAnnotatedDecoder
102+
(Just fix)
103+
spec
104+
codecLocalMsgNotification
105+
[ SomeAgency LocalMsgNotification.SingIdle
106+
, SomeAgency $ LocalMsgNotification.SingBusy LocalMsgNotification.SingBlocking
107+
, SomeAgency $ LocalMsgNotification.SingBusy LocalMsgNotification.SingNonBlocking
108+
]
109+
100
110+
where
111+
-- | The cddl spec cannot differentiate between fix-length list encoding and
112+
-- infinite-length encoding. The cddl tool always generates fix-length
113+
-- encoding but tx-submission codec is accepting only indefinite-length
114+
-- encoding.
115+
--
116+
fix :: CBOR.Term -> CBOR.Term
117+
fix = \case
118+
TList (TInt tag : TList l : as)
119+
| tag == 1 || tag == 2
120+
-> TList (TInt tag : TListI l : as)
121+
term -> term
122+
88123

89124
--
90125
-- utils
@@ -98,7 +133,9 @@ unit_decodeSig spec = validateDecoder spec decodeSig 100
98133
-- The `CDDL_INCLUDE_PATH` environment variable must be set.
99134
cddlc :: FilePath -> IO BL.ByteString
100135
cddlc path = do
101-
(_, cddl, _) <- readProcessWithExitCode "cddlc" ["-u", "-2", "-t", "cddl", path] mempty
136+
(exitCode, cddl, _) <- readProcessWithExitCode "cddlc" ["-u", "-2", "-t", "cddl", path] mempty
137+
unless (exitCode == ExitSuccess) $
138+
die $ printf "cddlc failed on \"%s\" with %s " path (show exitCode)
102139
return cddl
103140

104141

@@ -110,9 +147,11 @@ readCDDLSpecs = do
110147
setEnv "CDDL_INCLUDE_PATH" (dir <> ":")
111148

112149
sigSpec <- cddlc (dir </> "sig.cddl")
150+
localMessageNotificationSpec <- cddlc (dir </> "local-msg-notification.cddl")
113151

114152
return CDDLSpecs {
115-
cddlSig = CDDLSpec sigSpec
153+
cddlSig = CDDLSpec sigSpec,
154+
cddlLocalMsgNotification = CDDLSpec localMessageNotificationSpec
116155
}
117156

118157

@@ -130,7 +169,7 @@ validateDecoder (CDDLSpec spec) decoder rounds = do
130169
res = CBOR.deserialiseFromBytes decoder encoded_term
131170
case res of
132171
Left err -> assertFailure $ concat
133-
[ "decoding failure:\n"
172+
[ printf "decoding failure:\n"
134173
, show err
135174
, "\nwhile decoding:\n"
136175
, show decoded_term
@@ -142,6 +181,76 @@ validateDecoder (CDDLSpec spec) decoder rounds = do
142181
Right _ -> return ()
143182

144183

184+
data SomeAgency ps where
185+
SomeAgency :: ( ActiveState st
186+
, Show (StateToken st)
187+
)
188+
=> StateToken (st :: ps)
189+
-> SomeAgency ps
190+
191+
deriving instance Show (SomeAgency ps)
192+
193+
194+
-- | Generate valid encoded messages from a specification using `cddl generate`
195+
-- (and encoded with `diag2cbor.rb`) and check that we can decode it at one of
196+
-- the given agencies.
197+
--
198+
validateAnnotatedDecoder
199+
:: forall ps.
200+
Maybe (CBOR.Term -> CBOR.Term)
201+
-- ^ transform a generated term
202+
-> CDDLSpec ps
203+
-> AnnotatedCodec ps CBOR.DeserialiseFailure IO BL.ByteString
204+
-> [SomeAgency ps]
205+
-> Int
206+
-> Assertion
207+
validateAnnotatedDecoder transform (CDDLSpec spec) codec stoks rounds = do
208+
eterms <- runExceptT $ generateCBORFromSpec spec rounds
209+
case eterms of
210+
Left err -> assertFailure err
211+
Right terms ->
212+
forM_ terms $ \(generated_term, encoded_term) -> do
213+
let encoded_term' = case transform of
214+
Nothing -> encoded_term
215+
Just tr -> case CBOR.deserialiseFromBytes CBOR.decodeTerm encoded_term of
216+
Right (rest, term) | BL.null rest
217+
-> CBOR.toLazyByteString (CBOR.encodeTerm (tr term))
218+
Right _ -> error "validateDecoder: trailing bytes"
219+
Left err -> error $ "validateDecoder: decoding error: "
220+
++ show err
221+
222+
Right (_, decoded_term) =
223+
CBOR.deserialiseFromBytes CBOR.decodeTerm encoded_term'
224+
res <- decodeMsg encoded_term'
225+
case res of
226+
Just errs -> assertFailure $ concat
227+
[ printf "decoding failures:\n"
228+
, unlines ((\(SomeAgency a, e) -> printf "%-25s: %s" (show a) (show e)) <$>errs)
229+
, "while decoding:\n"
230+
, show decoded_term
231+
, "\ngenerated term (not rewritten):\n"
232+
, BL.Char8.unpack generated_term
233+
, "\nencoded term:\n"
234+
, BL.Char8.unpack (BL.Base16.encode encoded_term')
235+
]
236+
Nothing -> return ()
237+
where
238+
-- | Try decode at all given agencies. If one succeeds return
239+
-- 'Nothing' otherwise return all 'DeserialiseFailure's.
240+
--
241+
decodeMsg :: BL.ByteString
242+
-> IO (Maybe [(SomeAgency ps, CBOR.DeserialiseFailure)])
243+
decodeMsg bs =
244+
-- sequence [Nothing, ...] = Nothing
245+
fmap sequence $
246+
forM stoks $ \(a@(SomeAgency (stok :: StateToken st))) -> do
247+
decoder <- decode codec stok
248+
res <- runDecoder [bs] decoder
249+
return $ case res of
250+
Left err -> Just (a, err)
251+
Right {} -> Nothing
252+
253+
145254
generateCBORFromSpec :: BL.ByteString
146255
-> Int
147256
-> ExceptT String IO [(BL.ByteString, BL.ByteString)]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
localMessageNotificationMessage
2+
=
3+
; corresponds to either MsgRequestMessagesBlocking or
4+
; MsgRequestMessagesNonBlocking in the spec
5+
msgRequestMessages
6+
/ msgReplyMessagesNonBlocking
7+
/ msgReplyMessagesBlocking
8+
/ msgClientDone
9+
10+
isBlocking = bool
11+
hasMore = bool
12+
msgRequestMessages = [0, isBlocking]
13+
; the codec only accepts indefinite lists of messages
14+
msgReplyMessagesNonBlocking = [1, [*sig.message], hasMore]
15+
; the codec only accepts indefinite lists of messages
16+
msgReplyMessagesBlocking = [2, [+sig.message], hasMore]
17+
msgClientDone = [3]
18+
19+
;# import sig as sig

dmq-node/dmq-node.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ test-suite dmq-cddl
225225
tasty-hunit,
226226
tasty-quickcheck,
227227
temporary,
228+
typed-protocols,
228229

229230
ghc-options:
230231
-threaded

dmq-node/src/DMQ/NodeToClient/LocalMsgNotification.hs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ instance Exception LocalMsgNotificationProtocolError where
2626

2727
-- | Local Message Notification server application
2828
--
29+
-- enforced protocol invariants:
30+
--
31+
-- * non-blocking requests are only accepted when the server has no more messages,
32+
-- client is aware of this from previous replies
33+
-- * dually, blocking requests are only accepted when the server has more
34+
-- messages.
35+
-- * the first request must be blocking
36+
--
2937
localMsgNotificationServer
3038
:: forall m msg msgid idx a. (MonadSTM m, MonadThrow m)
3139
=> Tracer m (TraceMessageNotificationServer msg)

ouroboros-network-protocols/cddl/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ data SomeAgency ps where
644644
-- (and encoded with `diag2cbor.rb`) and check that we can decode it at one of
645645
-- the given agencies.
646646
--
647+
-- TODO: move to a common library shared with `dmq-node:cddl-test`
647648
validateDecoder :: Maybe (CBOR.Term -> CBOR.Term)
648649
-- ^ transform a generated term
649650
-> CDDLSpec ps

ouroboros-network-protocols/cddl/specs/tx-submission2.cddl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ txCount = base.word16
2929
txIdList = [ *base.txId ]
3030
txList = [ *base.tx ]
3131
txIdAndSize = [base.txId, txSizeInBytes]
32-
; The codec only accepts definite-length lists.
32+
; The codec only accepts indefinite-length lists.
3333
txIdsAndSizes = [ *txIdAndSize ]
3434
txSizeInBytes = base.word32
3535

0 commit comments

Comments
 (0)