Skip to content

Commit

Permalink
Peer Sharing - MiniProtocol
Browse files Browse the repository at this point in the history
Create PeerSharing Typed Protocol and tests

Add Peer Sharing CDDL spec and tests
  • Loading branch information
bolt12 committed Feb 8, 2023
1 parent 2a860bf commit 74e2e80
Show file tree
Hide file tree
Showing 16 changed files with 735 additions and 16 deletions.
@@ -1,13 +1,23 @@
{-# LANGUAGE DeriveGeneric #-}

{-# OPTIONS_GHC -Wno-orphans #-}

module Ouroboros.Network.PeerSelection.PeerSharing.Type
( PeerSharing (..)
, combinePeerInformation
, encodePortNumber
, decodePortNumber
, encodeRemoteAddress
, decodeRemoteAddress
) where

import Data.Aeson
import qualified Codec.CBOR.Decoding as CBOR
import qualified Codec.CBOR.Encoding as CBOR
import Data.Aeson.Types (FromJSON (..), ToJSON (..), Value (..),
withText)
import qualified Data.Text as Text
import GHC.Generics (Generic)
import Network.Socket (PortNumber, SockAddr (..))
import Ouroboros.Network.PeerSelection.PeerAdvertise.Type
(PeerAdvertise (..))
import Text.Read (readMaybe)
Expand Down Expand Up @@ -50,4 +60,57 @@ combinePeerInformation :: PeerSharing -> PeerAdvertise -> PeerSharing
combinePeerInformation NoPeerSharing _ = NoPeerSharing
combinePeerInformation PeerSharingPrivate _ = PeerSharingPrivate
combinePeerInformation PeerSharingPublic DoNotAdvertisePeer = PeerSharingPrivate
combinePeerInformation _ _ = PeerSharingPublic
combinePeerInformation _ _ = PeerSharingPublic

encodePortNumber :: PortNumber -> CBOR.Encoding
encodePortNumber = CBOR.encodeWord16 . fromIntegral

decodePortNumber :: CBOR.Decoder s PortNumber
decodePortNumber = fromIntegral <$> CBOR.decodeWord16


-- | This encoder should be faithful to the PeerSharing
-- CDDL Specification.
--
-- See the network design document for more details
--
encodeRemoteAddress :: SockAddr -> CBOR.Encoding
encodeRemoteAddress (SockAddrInet pn w) = CBOR.encodeListLen 3
<> CBOR.encodeWord 0
<> CBOR.encodeWord32 w
<> encodePortNumber pn
encodeRemoteAddress (SockAddrInet6 pn fi (w1, w2, w3, w4) si) = CBOR.encodeListLen 8
<> CBOR.encodeWord 1
<> CBOR.encodeWord32 w1
<> CBOR.encodeWord32 w2
<> CBOR.encodeWord32 w3
<> CBOR.encodeWord32 w4
<> CBOR.encodeWord32 fi
<> CBOR.encodeWord32 si
<> encodePortNumber pn
encodeRemoteAddress (SockAddrUnix _) = error "Should never be encoding a SockAddrUnix!"

-- | This decoder should be faithful to the PeerSharing
-- CDDL Specification.
--
-- See the network design document for more details
--
decodeRemoteAddress :: CBOR.Decoder s SockAddr
decodeRemoteAddress = do
_ <- CBOR.decodeListLen
tok <- CBOR.decodeWord
case tok of
0 -> do
w <- CBOR.decodeWord32
pn <- decodePortNumber
return (SockAddrInet pn w)
1 -> do
w1 <- CBOR.decodeWord32
w2 <- CBOR.decodeWord32
w3 <- CBOR.decodeWord32
w4 <- CBOR.decodeWord32
fi <- CBOR.decodeWord32
si <- CBOR.decodeWord32
pn <- decodePortNumber
return (SockAddrInet6 pn fi (w1, w2, w3, w4) si)
_ -> fail ("Serialise.decode.SockAddr unexpected tok " ++ show tok)
18 changes: 13 additions & 5 deletions ouroboros-network-protocols/ouroboros-network-protocols.cabal
Expand Up @@ -65,6 +65,10 @@ library
Ouroboros.Network.Protocol.KeepAlive.Client
Ouroboros.Network.Protocol.KeepAlive.Server
Ouroboros.Network.Protocol.KeepAlive.Codec
Ouroboros.Network.Protocol.PeerSharing.Type
Ouroboros.Network.Protocol.PeerSharing.Client
Ouroboros.Network.Protocol.PeerSharing.Server
Ouroboros.Network.Protocol.PeerSharing.Codec

default-language: Haskell2010
other-extensions: BangPatterns,
Expand Down Expand Up @@ -94,10 +98,10 @@ library
bytestring >=0.10 && <0.12,
cborg >=0.2.1 && <0.3,

io-classes,
ouroboros-network-api
^>=0.1,
typed-protocols,
io-classes ^>=0.3,
ouroboros-network-api,
serialise,
typed-protocols >=0.1 && <1.0,
typed-protocols-cborg
>=0.1 && <1.0

Expand Down Expand Up @@ -143,6 +147,9 @@ library testlib
Ouroboros.Network.Protocol.KeepAlive.Direct
Ouroboros.Network.Protocol.KeepAlive.Examples
Ouroboros.Network.Protocol.KeepAlive.Test
Ouroboros.Network.Protocol.PeerSharing.Direct
Ouroboros.Network.Protocol.PeerSharing.Examples
Ouroboros.Network.Protocol.PeerSharing.Test

Test.ChainGenerators
Test.ChainProducerState
Expand Down Expand Up @@ -196,7 +203,7 @@ test-suite test

ghc-options: -Wall
-Wunused-packages

test-suite cddl
type: exitcode-stdio-1.0
hs-source-dirs: test-cddl
Expand All @@ -217,6 +224,7 @@ test-suite cddl
serialise,
text,
temporary,
network,

QuickCheck,
quickcheck-instances,
Expand Down
@@ -0,0 +1,38 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}

module Ouroboros.Network.Protocol.PeerSharing.Client where

import Network.TypedProtocol.Core (Peer (..), PeerHasAgency (..),
PeerRole (..))
import Ouroboros.Network.Protocol.PeerSharing.Type
(ClientHasAgency (..), Message (..), NobodyHasAgency (..),
PeerSharing (..), PeerSharingAmount, ServerHasAgency (..))

data PeerSharingClient peerAddress m a where
SendMsgShareRequest
:: PeerSharingAmount
-> ([peerAddress] -> m (PeerSharingClient peerAddress m a))
-> PeerSharingClient peerAddress m a

SendMsgDone
:: m a -> PeerSharingClient peerAddress m a

-- | Interpret a particular client action sequence into the client side of the
-- 'PeerSharing' protocol.
--
peerSharingClientPeer :: Monad m
=> PeerSharingClient peerAddress m a
-> Peer (PeerSharing peerAddress) AsClient StIdle m a
peerSharingClientPeer (SendMsgShareRequest amount k) =
-- Send MsgShareRequest message
Yield (ClientAgency TokIdle) (MsgShareRequest amount) $
-- Wait for the reply (notice the agency proofs)
Await (ServerAgency TokBusy) $ \(MsgSharePeers resp) ->
-- We have our reply. We might want to perform some action with it so we
-- run the continuation to handle t he response.
Effect $ peerSharingClientPeer <$> k resp
peerSharingClientPeer (SendMsgDone result) =
-- Perform some finishing action
-- Perform a transition to the 'StDone' state
Effect $ (Yield (ClientAgency TokIdle) MsgDone . Done TokDone) <$> result
@@ -0,0 +1,135 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Ouroboros.Network.Protocol.PeerSharing.Codec where

import Control.Monad.Class.MonadST

import Data.ByteString.Lazy (ByteString)

import qualified Codec.CBOR.Read as CBOR

import Network.TypedProtocol.Codec.CBOR

import qualified Codec.CBOR.Decoding as CBOR
import qualified Codec.CBOR.Encoding as CBOR
import qualified Codec.Serialise.Class as CBOR
import Ouroboros.Network.Protocol.PeerSharing.Type
(ClientHasAgency (..), Message (..), PeerSharing,
ServerHasAgency (..))

import Control.Monad.Class.MonadTime (DiffTime)
import Ouroboros.Network.Protocol.Limits

codecPeerSharing :: forall m peerAddress.
MonadST m
=> (peerAddress -> CBOR.Encoding)
-> (forall s . CBOR.Decoder s peerAddress)
-> Codec (PeerSharing peerAddress)
CBOR.DeserialiseFailure
m
ByteString
codecPeerSharing encodeAddress decodeAddress = mkCodecCborLazyBS encodeMsg decodeMsg
where
encodeMsg :: PeerHasAgency pr st
-> Message (PeerSharing peerAddress) st st'
-> CBOR.Encoding
encodeMsg (ClientAgency TokIdle) (MsgShareRequest amount) =
CBOR.encodeListLen 2
<> CBOR.encodeWord 0
<> CBOR.encode amount
encodeMsg (ServerAgency TokBusy) (MsgSharePeers peers) =
CBOR.encodeListLen 2
<> CBOR.encodeWord 1
<> encodeListWith encodeAddress peers
encodeMsg (ClientAgency TokIdle) MsgDone =
CBOR.encodeListLen 1
<> CBOR.encodeWord 2

decodeMsg :: PeerHasAgency pr (st :: PeerSharing peerAddress)
-> CBOR.Decoder s (SomeMessage st)
decodeMsg stok = do
_ <- CBOR.decodeListLen
key <- CBOR.decodeWord
case (stok, key) of
(ClientAgency TokIdle, 0) -> SomeMessage . MsgShareRequest
<$> CBOR.decode
(ServerAgency TokBusy, 1) -> SomeMessage . MsgSharePeers
<$> decodeListWith decodeAddress
(ClientAgency TokIdle, 2) -> return
$ SomeMessage MsgDone

(ClientAgency TokIdle, _) ->
fail ("codecPeerSharing.StIdle: unexpected key: " ++ show key)
(ServerAgency TokBusy, _) ->
fail ("codecPeerSharing.StBusy: unexpected key: " ++ show key)

-- Definition as in CBOR.encodeList but indexed by an external encoder
encodeListWith :: (a -> CBOR.Encoding) -> [a] -> CBOR.Encoding
encodeListWith _ [] = CBOR.encodeListLen 0
encodeListWith enc xs = CBOR.encodeListLenIndef
<> foldr (\x r -> enc x <> r) CBOR.encodeBreak xs
-- Definition as in Cardano.Binary.FromCBOR.encodeList to avoid Serialise
-- dependency
decodeListWith :: CBOR.Decoder s a -> CBOR.Decoder s [a]
decodeListWith d = do
CBOR.decodeListLenIndef
CBOR.decodeSequenceLenIndef (flip (:)) [] reverse d


codecPeerSharingId
:: forall peerAddress m.
Monad m
=> Codec (PeerSharing peerAddress) CodecFailure m (AnyMessage (PeerSharing peerAddress))
codecPeerSharingId = Codec encodeMsg decodeMsg
where
encodeMsg :: forall (pr :: PeerRole) st st'.
PeerHasAgency pr st
-> Message (PeerSharing peerAddress) st st'
-> AnyMessage (PeerSharing peerAddress)
encodeMsg _ = AnyMessage

decodeMsg :: forall (pr :: PeerRole) (st :: (PeerSharing peerAddress)).
PeerHasAgency pr st
-> m (DecodeStep (AnyMessage (PeerSharing peerAddress))
CodecFailure m (SomeMessage st))
decodeMsg stok = return $ DecodePartial $ \bytes -> return $
case (stok, bytes) of
(ClientAgency TokIdle, Just (AnyMessage msg@(MsgShareRequest {})))
-> DecodeDone (SomeMessage msg) Nothing
(ServerAgency TokBusy, Just (AnyMessage msg@(MsgSharePeers {})))
-> DecodeDone (SomeMessage msg) Nothing
(ClientAgency TokIdle, Just (AnyMessage msg@(MsgDone)))
-> DecodeDone (SomeMessage msg) Nothing
(_, _) -> DecodeFail (CodecFailure "codecPeerSharingId: no matching message")

-- | We assume that a TCP segment size of 1440 bytes with initial window of size
-- 4. This sets upper limit of 5760 bytes on each message of peer sharing
-- protocol, which means request and response should be done in a single RTT
--
maxTransmissionUnit :: Word
maxTransmissionUnit = 4 * 1440

byteLimitsPeerSharing :: (bytes -> Word)
-> ProtocolSizeLimits (PeerSharing peerAddress) bytes
byteLimitsPeerSharing = ProtocolSizeLimits sizeLimitForState
where
sizeLimitForState :: PeerHasAgency (pr :: PeerRole)
(st :: PeerSharing peerAddress)
-> Word
sizeLimitForState (ClientAgency TokIdle) = maxTransmissionUnit
sizeLimitForState (ServerAgency TokBusy) = maxTransmissionUnit


timeLimitsPeerSharing :: ProtocolTimeLimits (PeerSharing peerAddress)
timeLimitsPeerSharing = ProtocolTimeLimits { timeLimitForState }
where
timeLimitForState :: PeerHasAgency (pr :: PeerRole)
(st :: PeerSharing peerAddress)
-> Maybe DiffTime
timeLimitForState (ClientAgency TokIdle) = waitForever
timeLimitForState (ServerAgency TokBusy) = longWait
@@ -0,0 +1,39 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RecordWildCards #-}

module Ouroboros.Network.Protocol.PeerSharing.Server where

import Network.TypedProtocol.Core (Peer (..), PeerHasAgency (..),
PeerRole (..))
import Ouroboros.Network.Protocol.PeerSharing.Type
(ClientHasAgency (..), Message (..), NobodyHasAgency (..),
PeerSharing (..), PeerSharingAmount, ServerHasAgency (..))

data PeerSharingServer peerAddress m = PeerSharingServer {
-- | The client sent us a 'MsgShareRequest'. We have need to compute the
-- response.
--
recvMsgShareRequest :: PeerSharingAmount
-> m ( [peerAddress]
, PeerSharingServer peerAddress m
)
}

peerSharingServerPeer :: Monad m
=> PeerSharingServer peerAddress m
-> Peer (PeerSharing peerAddress) AsServer StIdle m ()
peerSharingServerPeer PeerSharingServer{..} =
-- Await receival of a message from the client
Await (ClientAgency TokIdle) $ \msg ->
-- Can be either 'MsgShareRequest' or 'MsgDone'
case msg of
-- Compute the response and send 'MsgSharePeers' message
MsgShareRequest amount -> Effect $ do
(resp, server) <- recvMsgShareRequest amount
return $
Yield (ServerAgency TokBusy)
(MsgSharePeers resp)
(peerSharingServerPeer server)
-- Nothing to do.
MsgDone -> Done TokDone ()

0 comments on commit 74e2e80

Please sign in to comment.