-- | GossipSub core types and parameters (docs/11-pubsub.md).
--
-- Protocol ID: /meshsub/1.1.0
--
-- GossipSub constructs two overlapping overlay networks per topic:
-- a full-message mesh (eager push, degree D=6) and a metadata-only
-- gossip layer (lazy push via IHAVE/IWANT). This module defines
-- all wire types, configuration parameters, per-peer state, and
-- the router state record.
module Network.LibP2P.Protocol.GossipSub.Types
  ( -- * Protocol constants
    gossipSubProtocolId
  , maxRPCSize
    -- * Topic and message identity
  , Topic
  , MessageId
    -- * Wire types
  , PubSubMessage (..)
  , SubOpts (..)
  , ControlMessage (..)
  , IHave (..)
  , IWant (..)
  , Graft (..)
  , Prune (..)
  , PeerExchangeInfo (..)
  , RPC (..)
    -- * Signature policy
  , SignaturePolicy (..)
    -- * Configuration
  , GossipSubParams (..)
  , defaultGossipSubParams
    -- * Peer tracking
  , PeerProtocol (..)
  , PeerState (..)
    -- * Scoring types
  , TopicScoreParams (..)
  , defaultTopicScoreParams
  , PeerScoreParams (..)
  , defaultPeerScoreParams
  , TopicPeerState (..)
  , defaultTopicPeerState
  , ScoreThresholds (..)
  , defaultScoreThresholds
    -- * Message cache types
  , CacheEntry (..)
  , MessageCache (..)
    -- * Router state
  , GossipSubRouter (..)
    -- * Defaults
  , emptyRPC
  , emptyControlMessage
  , defaultMessageId
  ) where

import Control.Concurrent.STM (TVar)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Sequence (Seq)
import Data.Set (Set)
import Data.Text (Text)
import Data.Time (NominalDiffTime, UTCTime)
import Data.Word (Word64)
import Network.LibP2P.Crypto.PeerId (PeerId)

-- | GossipSub v1.1 protocol identifier.
gossipSubProtocolId :: Text
gossipSubProtocolId :: Text
gossipSubProtocolId = Text
"/meshsub/1.1.0"

-- | Maximum RPC message size: 1 MiB.
maxRPCSize :: Int
maxRPCSize :: Int
maxRPCSize = Int
1048576

-- | Topic identifier (opaque string).
type Topic = Text

-- | Unique message identifier (typically from || seqno).
type MessageId = ByteString

-- Wire types

-- | A published message.
data PubSubMessage = PubSubMessage
  { PubSubMessage -> Maybe MessageId
msgFrom      :: !(Maybe ByteString)   -- ^ Original author's Peer ID bytes
  , PubSubMessage -> MessageId
msgData      :: !ByteString           -- ^ Application payload
  , PubSubMessage -> Maybe MessageId
msgSeqNo     :: !(Maybe ByteString)   -- ^ 8-byte big-endian sequence number
  , PubSubMessage -> Text
msgTopic     :: !Topic                -- ^ Topic this message belongs to
  , PubSubMessage -> Maybe MessageId
msgSignature :: !(Maybe ByteString)   -- ^ Signature over marshalled message
  , PubSubMessage -> Maybe MessageId
msgKey       :: !(Maybe ByteString)   -- ^ Author's public key (protobuf-encoded)
  } deriving (Int -> PubSubMessage -> ShowS
[PubSubMessage] -> ShowS
PubSubMessage -> String
(Int -> PubSubMessage -> ShowS)
-> (PubSubMessage -> String)
-> ([PubSubMessage] -> ShowS)
-> Show PubSubMessage
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PubSubMessage -> ShowS
showsPrec :: Int -> PubSubMessage -> ShowS
$cshow :: PubSubMessage -> String
show :: PubSubMessage -> String
$cshowList :: [PubSubMessage] -> ShowS
showList :: [PubSubMessage] -> ShowS
Show, PubSubMessage -> PubSubMessage -> Bool
(PubSubMessage -> PubSubMessage -> Bool)
-> (PubSubMessage -> PubSubMessage -> Bool) -> Eq PubSubMessage
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PubSubMessage -> PubSubMessage -> Bool
== :: PubSubMessage -> PubSubMessage -> Bool
$c/= :: PubSubMessage -> PubSubMessage -> Bool
/= :: PubSubMessage -> PubSubMessage -> Bool
Eq)

-- | Subscription change announcement.
data SubOpts = SubOpts
  { SubOpts -> Bool
subSubscribe :: !Bool    -- ^ True = subscribe, False = unsubscribe
  , SubOpts -> Text
subTopicId   :: !Topic   -- ^ Topic identifier
  } deriving (Int -> SubOpts -> ShowS
[SubOpts] -> ShowS
SubOpts -> String
(Int -> SubOpts -> ShowS)
-> (SubOpts -> String) -> ([SubOpts] -> ShowS) -> Show SubOpts
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SubOpts -> ShowS
showsPrec :: Int -> SubOpts -> ShowS
$cshow :: SubOpts -> String
show :: SubOpts -> String
$cshowList :: [SubOpts] -> ShowS
showList :: [SubOpts] -> ShowS
Show, SubOpts -> SubOpts -> Bool
(SubOpts -> SubOpts -> Bool)
-> (SubOpts -> SubOpts -> Bool) -> Eq SubOpts
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SubOpts -> SubOpts -> Bool
== :: SubOpts -> SubOpts -> Bool
$c/= :: SubOpts -> SubOpts -> Bool
/= :: SubOpts -> SubOpts -> Bool
Eq)

-- | IHAVE: announce message IDs for a topic.
data IHave = IHave
  { IHave -> Text
ihaveTopic      :: !Topic        -- ^ Topic ID
  , IHave -> [MessageId]
ihaveMessageIds :: ![MessageId]  -- ^ Message IDs available
  } deriving (Int -> IHave -> ShowS
[IHave] -> ShowS
IHave -> String
(Int -> IHave -> ShowS)
-> (IHave -> String) -> ([IHave] -> ShowS) -> Show IHave
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> IHave -> ShowS
showsPrec :: Int -> IHave -> ShowS
$cshow :: IHave -> String
show :: IHave -> String
$cshowList :: [IHave] -> ShowS
showList :: [IHave] -> ShowS
Show, IHave -> IHave -> Bool
(IHave -> IHave -> Bool) -> (IHave -> IHave -> Bool) -> Eq IHave
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: IHave -> IHave -> Bool
== :: IHave -> IHave -> Bool
$c/= :: IHave -> IHave -> Bool
/= :: IHave -> IHave -> Bool
Eq)

-- | IWANT: request full messages by ID.
data IWant = IWant
  { IWant -> [MessageId]
iwantMessageIds :: ![MessageId]  -- ^ Requested message IDs
  } deriving (Int -> IWant -> ShowS
[IWant] -> ShowS
IWant -> String
(Int -> IWant -> ShowS)
-> (IWant -> String) -> ([IWant] -> ShowS) -> Show IWant
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> IWant -> ShowS
showsPrec :: Int -> IWant -> ShowS
$cshow :: IWant -> String
show :: IWant -> String
$cshowList :: [IWant] -> ShowS
showList :: [IWant] -> ShowS
Show, IWant -> IWant -> Bool
(IWant -> IWant -> Bool) -> (IWant -> IWant -> Bool) -> Eq IWant
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: IWant -> IWant -> Bool
== :: IWant -> IWant -> Bool
$c/= :: IWant -> IWant -> Bool
/= :: IWant -> IWant -> Bool
Eq)

-- | GRAFT: request mesh membership.
data Graft = Graft
  { Graft -> Text
graftTopic :: !Topic  -- ^ Topic to graft into
  } deriving (Int -> Graft -> ShowS
[Graft] -> ShowS
Graft -> String
(Int -> Graft -> ShowS)
-> (Graft -> String) -> ([Graft] -> ShowS) -> Show Graft
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Graft -> ShowS
showsPrec :: Int -> Graft -> ShowS
$cshow :: Graft -> String
show :: Graft -> String
$cshowList :: [Graft] -> ShowS
showList :: [Graft] -> ShowS
Show, Graft -> Graft -> Bool
(Graft -> Graft -> Bool) -> (Graft -> Graft -> Bool) -> Eq Graft
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Graft -> Graft -> Bool
== :: Graft -> Graft -> Bool
$c/= :: Graft -> Graft -> Bool
/= :: Graft -> Graft -> Bool
Eq)

-- | PRUNE: remove from mesh with optional peer exchange and backoff.
data Prune = Prune
  { Prune -> Text
pruneTopic   :: !Topic                -- ^ Topic to prune from
  , Prune -> [PeerExchangeInfo]
prunePeers   :: ![PeerExchangeInfo]   -- ^ Peer exchange (v1.1)
  , Prune -> Maybe Word64
pruneBackoff :: !(Maybe Word64)       -- ^ Backoff duration in seconds (v1.1)
  } deriving (Int -> Prune -> ShowS
[Prune] -> ShowS
Prune -> String
(Int -> Prune -> ShowS)
-> (Prune -> String) -> ([Prune] -> ShowS) -> Show Prune
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Prune -> ShowS
showsPrec :: Int -> Prune -> ShowS
$cshow :: Prune -> String
show :: Prune -> String
$cshowList :: [Prune] -> ShowS
showList :: [Prune] -> ShowS
Show, Prune -> Prune -> Bool
(Prune -> Prune -> Bool) -> (Prune -> Prune -> Bool) -> Eq Prune
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Prune -> Prune -> Bool
== :: Prune -> Prune -> Bool
$c/= :: Prune -> Prune -> Bool
/= :: Prune -> Prune -> Bool
Eq)

-- | Peer exchange info in PRUNE messages (v1.1).
data PeerExchangeInfo = PeerExchangeInfo
  { PeerExchangeInfo -> MessageId
pxPeerId            :: !ByteString          -- ^ Peer ID bytes
  , PeerExchangeInfo -> Maybe MessageId
pxSignedPeerRecord  :: !(Maybe ByteString)  -- ^ Optional signed peer record
  } deriving (Int -> PeerExchangeInfo -> ShowS
[PeerExchangeInfo] -> ShowS
PeerExchangeInfo -> String
(Int -> PeerExchangeInfo -> ShowS)
-> (PeerExchangeInfo -> String)
-> ([PeerExchangeInfo] -> ShowS)
-> Show PeerExchangeInfo
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PeerExchangeInfo -> ShowS
showsPrec :: Int -> PeerExchangeInfo -> ShowS
$cshow :: PeerExchangeInfo -> String
show :: PeerExchangeInfo -> String
$cshowList :: [PeerExchangeInfo] -> ShowS
showList :: [PeerExchangeInfo] -> ShowS
Show, PeerExchangeInfo -> PeerExchangeInfo -> Bool
(PeerExchangeInfo -> PeerExchangeInfo -> Bool)
-> (PeerExchangeInfo -> PeerExchangeInfo -> Bool)
-> Eq PeerExchangeInfo
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PeerExchangeInfo -> PeerExchangeInfo -> Bool
== :: PeerExchangeInfo -> PeerExchangeInfo -> Bool
$c/= :: PeerExchangeInfo -> PeerExchangeInfo -> Bool
/= :: PeerExchangeInfo -> PeerExchangeInfo -> Bool
Eq)

-- | GossipSub control message (IHAVE, IWANT, GRAFT, PRUNE).
data ControlMessage = ControlMessage
  { ControlMessage -> [IHave]
ctrlIHave :: ![IHave]
  , ControlMessage -> [IWant]
ctrlIWant :: ![IWant]
  , ControlMessage -> [Graft]
ctrlGraft :: ![Graft]
  , ControlMessage -> [Prune]
ctrlPrune :: ![Prune]
  } deriving (Int -> ControlMessage -> ShowS
[ControlMessage] -> ShowS
ControlMessage -> String
(Int -> ControlMessage -> ShowS)
-> (ControlMessage -> String)
-> ([ControlMessage] -> ShowS)
-> Show ControlMessage
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ControlMessage -> ShowS
showsPrec :: Int -> ControlMessage -> ShowS
$cshow :: ControlMessage -> String
show :: ControlMessage -> String
$cshowList :: [ControlMessage] -> ShowS
showList :: [ControlMessage] -> ShowS
Show, ControlMessage -> ControlMessage -> Bool
(ControlMessage -> ControlMessage -> Bool)
-> (ControlMessage -> ControlMessage -> Bool) -> Eq ControlMessage
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ControlMessage -> ControlMessage -> Bool
== :: ControlMessage -> ControlMessage -> Bool
$c/= :: ControlMessage -> ControlMessage -> Bool
/= :: ControlMessage -> ControlMessage -> Bool
Eq)

-- | RPC wrapper containing subscriptions, published messages, and control.
data RPC = RPC
  { RPC -> [SubOpts]
rpcSubscriptions :: ![SubOpts]
  , RPC -> [PubSubMessage]
rpcPublish       :: ![PubSubMessage]
  , RPC -> Maybe ControlMessage
rpcControl       :: !(Maybe ControlMessage)
  } deriving (Int -> RPC -> ShowS
[RPC] -> ShowS
RPC -> String
(Int -> RPC -> ShowS)
-> (RPC -> String) -> ([RPC] -> ShowS) -> Show RPC
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RPC -> ShowS
showsPrec :: Int -> RPC -> ShowS
$cshow :: RPC -> String
show :: RPC -> String
$cshowList :: [RPC] -> ShowS
showList :: [RPC] -> ShowS
Show, RPC -> RPC -> Bool
(RPC -> RPC -> Bool) -> (RPC -> RPC -> Bool) -> Eq RPC
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RPC -> RPC -> Bool
== :: RPC -> RPC -> Bool
$c/= :: RPC -> RPC -> Bool
/= :: RPC -> RPC -> Bool
Eq)

-- Signature policy

-- | Signature policy for message signing/validation.
data SignaturePolicy
  = StrictSign       -- ^ All messages must be signed
  | StrictNoSign     -- ^ No signatures allowed
  deriving (Int -> SignaturePolicy -> ShowS
[SignaturePolicy] -> ShowS
SignaturePolicy -> String
(Int -> SignaturePolicy -> ShowS)
-> (SignaturePolicy -> String)
-> ([SignaturePolicy] -> ShowS)
-> Show SignaturePolicy
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SignaturePolicy -> ShowS
showsPrec :: Int -> SignaturePolicy -> ShowS
$cshow :: SignaturePolicy -> String
show :: SignaturePolicy -> String
$cshowList :: [SignaturePolicy] -> ShowS
showList :: [SignaturePolicy] -> ShowS
Show, SignaturePolicy -> SignaturePolicy -> Bool
(SignaturePolicy -> SignaturePolicy -> Bool)
-> (SignaturePolicy -> SignaturePolicy -> Bool)
-> Eq SignaturePolicy
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SignaturePolicy -> SignaturePolicy -> Bool
== :: SignaturePolicy -> SignaturePolicy -> Bool
$c/= :: SignaturePolicy -> SignaturePolicy -> Bool
/= :: SignaturePolicy -> SignaturePolicy -> Bool
Eq)

-- Configuration

-- | GossipSub router parameters.
data GossipSubParams = GossipSubParams
  { GossipSubParams -> Int
paramD                  :: !Int              -- ^ Desired mesh degree (default 6)
  , GossipSubParams -> Int
paramDlo                :: !Int              -- ^ Lower bound (default 4)
  , GossipSubParams -> Int
paramDhi                :: !Int              -- ^ Upper bound (default 12)
  , GossipSubParams -> Int
paramDlazy              :: !Int              -- ^ Gossip emission peers (default 6)
  , GossipSubParams -> Int
paramDscore             :: !Int              -- ^ Score-retained peers (default 4)
  , GossipSubParams -> Int
paramDout               :: !Int              -- ^ Minimum outbound (default 2)
  , GossipSubParams -> NominalDiffTime
paramHeartbeatInterval  :: !NominalDiffTime  -- ^ Heartbeat period (default 1s)
  , GossipSubParams -> NominalDiffTime
paramFanoutTTL          :: !NominalDiffTime  -- ^ Fanout expiry (default 60s)
  , GossipSubParams -> NominalDiffTime
paramSeenTTL            :: !NominalDiffTime  -- ^ Seen cache TTL (default 120s)
  , GossipSubParams -> NominalDiffTime
paramPruneBackoff       :: !NominalDiffTime  -- ^ Prune backoff (default 60s)
  , GossipSubParams -> NominalDiffTime
paramUnsubBackoff       :: !NominalDiffTime  -- ^ Unsubscribe backoff (default 10s)
  , GossipSubParams -> Double
paramGossipFactor       :: !Double           -- ^ Gossip target fraction (default 0.25)
  , GossipSubParams -> Bool
paramFloodPublish       :: !Bool             -- ^ Flood own messages (default True)
  , GossipSubParams -> PubSubMessage -> MessageId
paramMessageIdFn        :: !(PubSubMessage -> MessageId)
    -- ^ Message ID function (default: from || seqno)
  , GossipSubParams -> SignaturePolicy
paramSignaturePolicy    :: !SignaturePolicy  -- ^ Signing policy (default StrictSign)
  , GossipSubParams -> Int
paramMcacheLen          :: !Int              -- ^ Message cache windows (default 5)
  , GossipSubParams -> Int
paramMcacheGossip       :: !Int              -- ^ Gossip windows (default 3)
  }

-- | Default message ID: concatenation of from and seqno fields.
defaultMessageId :: PubSubMessage -> MessageId
defaultMessageId :: PubSubMessage -> MessageId
defaultMessageId PubSubMessage
msg =
  let from :: MessageId
from = MessageId
-> (MessageId -> MessageId) -> Maybe MessageId -> MessageId
forall b a. b -> (a -> b) -> Maybe a -> b
maybe MessageId
BS.empty MessageId -> MessageId
forall a. a -> a
id (PubSubMessage -> Maybe MessageId
msgFrom PubSubMessage
msg)
      seqno :: MessageId
seqno = MessageId
-> (MessageId -> MessageId) -> Maybe MessageId -> MessageId
forall b a. b -> (a -> b) -> Maybe a -> b
maybe MessageId
BS.empty MessageId -> MessageId
forall a. a -> a
id (PubSubMessage -> Maybe MessageId
msgSeqNo PubSubMessage
msg)
  in MessageId
from MessageId -> MessageId -> MessageId
forall a. Semigroup a => a -> a -> a
<> MessageId
seqno

-- | Default GossipSub parameters per spec.
defaultGossipSubParams :: GossipSubParams
defaultGossipSubParams :: GossipSubParams
defaultGossipSubParams = GossipSubParams
  { paramD :: Int
paramD                 = Int
6
  , paramDlo :: Int
paramDlo               = Int
4
  , paramDhi :: Int
paramDhi               = Int
12
  , paramDlazy :: Int
paramDlazy             = Int
6
  , paramDscore :: Int
paramDscore            = Int
4
  , paramDout :: Int
paramDout              = Int
2
  , paramHeartbeatInterval :: NominalDiffTime
paramHeartbeatInterval = NominalDiffTime
1
  , paramFanoutTTL :: NominalDiffTime
paramFanoutTTL         = NominalDiffTime
60
  , paramSeenTTL :: NominalDiffTime
paramSeenTTL           = NominalDiffTime
120
  , paramPruneBackoff :: NominalDiffTime
paramPruneBackoff      = NominalDiffTime
60
  , paramUnsubBackoff :: NominalDiffTime
paramUnsubBackoff      = NominalDiffTime
10
  , paramGossipFactor :: Double
paramGossipFactor      = Double
0.25
  , paramFloodPublish :: Bool
paramFloodPublish      = Bool
True
  , paramMessageIdFn :: PubSubMessage -> MessageId
paramMessageIdFn       = PubSubMessage -> MessageId
defaultMessageId
  , paramSignaturePolicy :: SignaturePolicy
paramSignaturePolicy   = SignaturePolicy
StrictSign
  , paramMcacheLen :: Int
paramMcacheLen         = Int
5
  , paramMcacheGossip :: Int
paramMcacheGossip      = Int
3
  }

-- Peer tracking

-- | Peer's protocol capability.
data PeerProtocol
  = GossipSubPeer   -- ^ Supports GossipSub (/meshsub/1.1.0)
  | FloodSubPeer    -- ^ Supports FloodSub only
  deriving (Int -> PeerProtocol -> ShowS
[PeerProtocol] -> ShowS
PeerProtocol -> String
(Int -> PeerProtocol -> ShowS)
-> (PeerProtocol -> String)
-> ([PeerProtocol] -> ShowS)
-> Show PeerProtocol
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PeerProtocol -> ShowS
showsPrec :: Int -> PeerProtocol -> ShowS
$cshow :: PeerProtocol -> String
show :: PeerProtocol -> String
$cshowList :: [PeerProtocol] -> ShowS
showList :: [PeerProtocol] -> ShowS
Show, PeerProtocol -> PeerProtocol -> Bool
(PeerProtocol -> PeerProtocol -> Bool)
-> (PeerProtocol -> PeerProtocol -> Bool) -> Eq PeerProtocol
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PeerProtocol -> PeerProtocol -> Bool
== :: PeerProtocol -> PeerProtocol -> Bool
$c/= :: PeerProtocol -> PeerProtocol -> Bool
/= :: PeerProtocol -> PeerProtocol -> Bool
Eq)

-- | Per-peer state tracked by the router.
data PeerState = PeerState
  { PeerState -> PeerProtocol
psProtocol         :: !PeerProtocol              -- ^ Protocol support
  , PeerState -> Set Text
psTopics           :: !(Set Topic)               -- ^ Subscribed topics
  , PeerState -> Bool
psIsOutbound       :: !Bool                      -- ^ True if we dialed this peer
  , PeerState -> UTCTime
psConnectedAt      :: !UTCTime                   -- ^ Connection establishment time
  , PeerState -> Map Text TopicPeerState
psTopicState       :: !(Map Topic TopicPeerState) -- ^ Per-topic scoring state
  , PeerState -> Double
psBehaviorPenalty  :: !Double                     -- ^ P7 behavioral penalty counter
  , PeerState -> Maybe MessageId
psIPAddress        :: !(Maybe ByteString)         -- ^ IP address for P6
  , PeerState -> Double
psCachedScore      :: !Double                     -- ^ Cached computed score
  } deriving (Int -> PeerState -> ShowS
[PeerState] -> ShowS
PeerState -> String
(Int -> PeerState -> ShowS)
-> (PeerState -> String)
-> ([PeerState] -> ShowS)
-> Show PeerState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PeerState -> ShowS
showsPrec :: Int -> PeerState -> ShowS
$cshow :: PeerState -> String
show :: PeerState -> String
$cshowList :: [PeerState] -> ShowS
showList :: [PeerState] -> ShowS
Show, PeerState -> PeerState -> Bool
(PeerState -> PeerState -> Bool)
-> (PeerState -> PeerState -> Bool) -> Eq PeerState
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PeerState -> PeerState -> Bool
== :: PeerState -> PeerState -> Bool
$c/= :: PeerState -> PeerState -> Bool
/= :: PeerState -> PeerState -> Bool
Eq)

-- Scoring types

-- | Per-topic scoring parameters.
data TopicScoreParams = TopicScoreParams
  { TopicScoreParams -> Double
tspTopicWeight                    :: !Double          -- ^ How much this topic contributes
  , TopicScoreParams -> Double
tspTimeInMeshWeight               :: !Double          -- ^ P1 weight (small positive)
  , TopicScoreParams -> NominalDiffTime
tspTimeInMeshQuantum              :: !NominalDiffTime -- ^ P1 time unit
  , TopicScoreParams -> Double
tspTimeInMeshCap                  :: !Double          -- ^ P1 maximum value
  , TopicScoreParams -> Double
tspFirstMessageDeliveriesWeight   :: !Double          -- ^ P2 weight (positive)
  , TopicScoreParams -> Double
tspFirstMessageDeliveriesDecay    :: !Double          -- ^ P2 decay factor
  , TopicScoreParams -> Double
tspFirstMessageDeliveriesCap      :: !Double          -- ^ P2 maximum counter
  , TopicScoreParams -> Double
tspMeshMessageDeliveriesWeight    :: !Double          -- ^ P3 weight (negative)
  , TopicScoreParams -> Double
tspMeshMessageDeliveriesDecay     :: !Double          -- ^ P3 decay factor
  , TopicScoreParams -> Double
tspMeshMessageDeliveriesThreshold :: !Double          -- ^ P3 expected threshold
  , TopicScoreParams -> Double
tspMeshMessageDeliveriesCap       :: !Double          -- ^ P3 maximum counter
  , TopicScoreParams -> NominalDiffTime
tspMeshMessageDeliveriesActivation :: !NominalDiffTime -- ^ P3 grace period
  , TopicScoreParams -> NominalDiffTime
tspMeshMessageDeliveryWindow      :: !NominalDiffTime  -- ^ P3 near-first window
  , TopicScoreParams -> Double
tspMeshFailurePenaltyWeight       :: !Double          -- ^ P3b weight (negative)
  , TopicScoreParams -> Double
tspMeshFailurePenaltyDecay        :: !Double          -- ^ P3b decay factor
  , TopicScoreParams -> Double
tspInvalidMessageDeliveriesWeight :: !Double          -- ^ P4 weight (negative)
  , TopicScoreParams -> Double
tspInvalidMessageDeliveriesDecay  :: !Double          -- ^ P4 decay factor
  } deriving (Int -> TopicScoreParams -> ShowS
[TopicScoreParams] -> ShowS
TopicScoreParams -> String
(Int -> TopicScoreParams -> ShowS)
-> (TopicScoreParams -> String)
-> ([TopicScoreParams] -> ShowS)
-> Show TopicScoreParams
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TopicScoreParams -> ShowS
showsPrec :: Int -> TopicScoreParams -> ShowS
$cshow :: TopicScoreParams -> String
show :: TopicScoreParams -> String
$cshowList :: [TopicScoreParams] -> ShowS
showList :: [TopicScoreParams] -> ShowS
Show, TopicScoreParams -> TopicScoreParams -> Bool
(TopicScoreParams -> TopicScoreParams -> Bool)
-> (TopicScoreParams -> TopicScoreParams -> Bool)
-> Eq TopicScoreParams
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TopicScoreParams -> TopicScoreParams -> Bool
== :: TopicScoreParams -> TopicScoreParams -> Bool
$c/= :: TopicScoreParams -> TopicScoreParams -> Bool
/= :: TopicScoreParams -> TopicScoreParams -> Bool
Eq)

-- | Default topic scoring parameters (conservative values).
defaultTopicScoreParams :: TopicScoreParams
defaultTopicScoreParams :: TopicScoreParams
defaultTopicScoreParams = TopicScoreParams
  { tspTopicWeight :: Double
tspTopicWeight                     = Double
1.0
  , tspTimeInMeshWeight :: Double
tspTimeInMeshWeight                = Double
0.01
  , tspTimeInMeshQuantum :: NominalDiffTime
tspTimeInMeshQuantum               = NominalDiffTime
1     -- 1 second
  , tspTimeInMeshCap :: Double
tspTimeInMeshCap                   = Double
100
  , tspFirstMessageDeliveriesWeight :: Double
tspFirstMessageDeliveriesWeight    = Double
1.0
  , tspFirstMessageDeliveriesDecay :: Double
tspFirstMessageDeliveriesDecay     = Double
0.5
  , tspFirstMessageDeliveriesCap :: Double
tspFirstMessageDeliveriesCap       = Double
100
  , tspMeshMessageDeliveriesWeight :: Double
tspMeshMessageDeliveriesWeight     = -Double
1.0
  , tspMeshMessageDeliveriesDecay :: Double
tspMeshMessageDeliveriesDecay      = Double
0.5
  , tspMeshMessageDeliveriesThreshold :: Double
tspMeshMessageDeliveriesThreshold  = Double
1.0
  , tspMeshMessageDeliveriesCap :: Double
tspMeshMessageDeliveriesCap        = Double
100
  , tspMeshMessageDeliveriesActivation :: NominalDiffTime
tspMeshMessageDeliveriesActivation = NominalDiffTime
5    -- 5 seconds
  , tspMeshMessageDeliveryWindow :: NominalDiffTime
tspMeshMessageDeliveryWindow       = NominalDiffTime
0.01 -- 10 ms
  , tspMeshFailurePenaltyWeight :: Double
tspMeshFailurePenaltyWeight        = -Double
1.0
  , tspMeshFailurePenaltyDecay :: Double
tspMeshFailurePenaltyDecay         = Double
0.5
  , tspInvalidMessageDeliveriesWeight :: Double
tspInvalidMessageDeliveriesWeight  = -Double
100.0
  , tspInvalidMessageDeliveriesDecay :: Double
tspInvalidMessageDeliveriesDecay   = Double
0.5
  }

-- | Global peer scoring parameters.
data PeerScoreParams = PeerScoreParams
  { PeerScoreParams -> Map Text TopicScoreParams
pspTopicParams              :: !(Map Topic TopicScoreParams)
  , PeerScoreParams -> Double
pspAppSpecificWeight        :: !Double            -- ^ w5 weight
  , PeerScoreParams -> PeerId -> Double
pspAppSpecificScore         :: !(PeerId -> Double) -- ^ P5 callback
  , PeerScoreParams -> Double
pspIPColocationFactorWeight :: !Double            -- ^ w6 weight (negative)
  , PeerScoreParams -> Int
pspIPColocationFactorThreshold :: !Int            -- ^ P6 threshold
  , PeerScoreParams -> Double
pspBehaviorPenaltyWeight    :: !Double            -- ^ w7 weight (negative)
  , PeerScoreParams -> Double
pspBehaviorPenaltyDecay     :: !Double            -- ^ w7 decay factor
  , PeerScoreParams -> NominalDiffTime
pspDecayInterval            :: !NominalDiffTime   -- ^ How often to decay
  , PeerScoreParams -> Double
pspDecayToZero              :: !Double            -- ^ Zero-out threshold
  , PeerScoreParams -> NominalDiffTime
pspRetainScore              :: !NominalDiffTime   -- ^ Keep score after disconnect
  , PeerScoreParams -> Double
pspTopicScoreCap            :: !Double            -- ^ Cap for topic score sum
  }

-- | Default global scoring parameters.
defaultPeerScoreParams :: PeerScoreParams
defaultPeerScoreParams :: PeerScoreParams
defaultPeerScoreParams = PeerScoreParams
  { pspTopicParams :: Map Text TopicScoreParams
pspTopicParams              = Map Text TopicScoreParams
forall k a. Map k a
Map.empty
  , pspAppSpecificWeight :: Double
pspAppSpecificWeight        = Double
1.0
  , pspAppSpecificScore :: PeerId -> Double
pspAppSpecificScore         = Double -> PeerId -> Double
forall a b. a -> b -> a
const Double
0
  , pspIPColocationFactorWeight :: Double
pspIPColocationFactorWeight = -Double
10.0
  , pspIPColocationFactorThreshold :: Int
pspIPColocationFactorThreshold = Int
3
  , pspBehaviorPenaltyWeight :: Double
pspBehaviorPenaltyWeight    = -Double
1.0
  , pspBehaviorPenaltyDecay :: Double
pspBehaviorPenaltyDecay     = Double
0.99
  , pspDecayInterval :: NominalDiffTime
pspDecayInterval            = NominalDiffTime
1    -- 1 second
  , pspDecayToZero :: Double
pspDecayToZero              = Double
0.01
  , pspRetainScore :: NominalDiffTime
pspRetainScore              = NominalDiffTime
3600 -- 1 hour
  , pspTopicScoreCap :: Double
pspTopicScoreCap            = Double
100.0
  }

-- | Per-topic per-peer state for scoring counters.
data TopicPeerState = TopicPeerState
  { TopicPeerState -> NominalDiffTime
tpsMeshTime                :: !NominalDiffTime  -- ^ P1: time in mesh
  , TopicPeerState -> Double
tpsFirstMessageDeliveries  :: !Double           -- ^ P2 counter
  , TopicPeerState -> Double
tpsMeshMessageDeliveries   :: !Double           -- ^ P3 counter
  , TopicPeerState -> Double
tpsMeshFailurePenalty      :: !Double           -- ^ P3b counter
  , TopicPeerState -> Double
tpsInvalidMessages         :: !Double           -- ^ P4 counter
  , TopicPeerState -> Maybe UTCTime
tpsGraftTime               :: !(Maybe UTCTime)  -- ^ When grafted
  , TopicPeerState -> Bool
tpsInMesh                  :: !Bool             -- ^ Currently in mesh?
  } deriving (Int -> TopicPeerState -> ShowS
[TopicPeerState] -> ShowS
TopicPeerState -> String
(Int -> TopicPeerState -> ShowS)
-> (TopicPeerState -> String)
-> ([TopicPeerState] -> ShowS)
-> Show TopicPeerState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TopicPeerState -> ShowS
showsPrec :: Int -> TopicPeerState -> ShowS
$cshow :: TopicPeerState -> String
show :: TopicPeerState -> String
$cshowList :: [TopicPeerState] -> ShowS
showList :: [TopicPeerState] -> ShowS
Show, TopicPeerState -> TopicPeerState -> Bool
(TopicPeerState -> TopicPeerState -> Bool)
-> (TopicPeerState -> TopicPeerState -> Bool) -> Eq TopicPeerState
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TopicPeerState -> TopicPeerState -> Bool
== :: TopicPeerState -> TopicPeerState -> Bool
$c/= :: TopicPeerState -> TopicPeerState -> Bool
/= :: TopicPeerState -> TopicPeerState -> Bool
Eq)

-- | Default empty topic peer state.
defaultTopicPeerState :: TopicPeerState
defaultTopicPeerState :: TopicPeerState
defaultTopicPeerState = TopicPeerState
  { tpsMeshTime :: NominalDiffTime
tpsMeshTime               = NominalDiffTime
0
  , tpsFirstMessageDeliveries :: Double
tpsFirstMessageDeliveries = Double
0
  , tpsMeshMessageDeliveries :: Double
tpsMeshMessageDeliveries  = Double
0
  , tpsMeshFailurePenalty :: Double
tpsMeshFailurePenalty     = Double
0
  , tpsInvalidMessages :: Double
tpsInvalidMessages        = Double
0
  , tpsGraftTime :: Maybe UTCTime
tpsGraftTime              = Maybe UTCTime
forall a. Maybe a
Nothing
  , tpsInMesh :: Bool
tpsInMesh                 = Bool
False
  }

-- | Score thresholds controlling router behavior.
data ScoreThresholds = ScoreThresholds
  { ScoreThresholds -> Double
stGossipThreshold            :: !Double  -- ^ Below: no gossip
  , ScoreThresholds -> Double
stPublishThreshold           :: !Double  -- ^ Below: no flood publish
  , ScoreThresholds -> Double
stGraylistThreshold          :: !Double  -- ^ Below: ignore all RPCs
  , ScoreThresholds -> Double
stAcceptPXThreshold          :: !Double  -- ^ Above: accept PX from PRUNE
  , ScoreThresholds -> Double
stOpportunisticGraftThreshold :: !Double -- ^ Below: trigger opportunistic graft
  } deriving (Int -> ScoreThresholds -> ShowS
[ScoreThresholds] -> ShowS
ScoreThresholds -> String
(Int -> ScoreThresholds -> ShowS)
-> (ScoreThresholds -> String)
-> ([ScoreThresholds] -> ShowS)
-> Show ScoreThresholds
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ScoreThresholds -> ShowS
showsPrec :: Int -> ScoreThresholds -> ShowS
$cshow :: ScoreThresholds -> String
show :: ScoreThresholds -> String
$cshowList :: [ScoreThresholds] -> ShowS
showList :: [ScoreThresholds] -> ShowS
Show, ScoreThresholds -> ScoreThresholds -> Bool
(ScoreThresholds -> ScoreThresholds -> Bool)
-> (ScoreThresholds -> ScoreThresholds -> Bool)
-> Eq ScoreThresholds
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ScoreThresholds -> ScoreThresholds -> Bool
== :: ScoreThresholds -> ScoreThresholds -> Bool
$c/= :: ScoreThresholds -> ScoreThresholds -> Bool
/= :: ScoreThresholds -> ScoreThresholds -> Bool
Eq)

-- | Default score thresholds.
defaultScoreThresholds :: ScoreThresholds
defaultScoreThresholds :: ScoreThresholds
defaultScoreThresholds = ScoreThresholds
  { stGossipThreshold :: Double
stGossipThreshold             = -Double
100
  , stPublishThreshold :: Double
stPublishThreshold            = -Double
1000
  , stGraylistThreshold :: Double
stGraylistThreshold           = -Double
10000
  , stAcceptPXThreshold :: Double
stAcceptPXThreshold           = Double
100
  , stOpportunisticGraftThreshold :: Double
stOpportunisticGraftThreshold = Double
1
  }

-- Message cache types

-- | A cached message entry.
data CacheEntry = CacheEntry
  { CacheEntry -> MessageId
ceMessageId :: !MessageId
  , CacheEntry -> PubSubMessage
ceMessage   :: !PubSubMessage
  , CacheEntry -> Text
ceTopic     :: !Topic
  } deriving (Int -> CacheEntry -> ShowS
[CacheEntry] -> ShowS
CacheEntry -> String
(Int -> CacheEntry -> ShowS)
-> (CacheEntry -> String)
-> ([CacheEntry] -> ShowS)
-> Show CacheEntry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CacheEntry -> ShowS
showsPrec :: Int -> CacheEntry -> ShowS
$cshow :: CacheEntry -> String
show :: CacheEntry -> String
$cshowList :: [CacheEntry] -> ShowS
showList :: [CacheEntry] -> ShowS
Show, CacheEntry -> CacheEntry -> Bool
(CacheEntry -> CacheEntry -> Bool)
-> (CacheEntry -> CacheEntry -> Bool) -> Eq CacheEntry
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CacheEntry -> CacheEntry -> Bool
== :: CacheEntry -> CacheEntry -> Bool
$c/= :: CacheEntry -> CacheEntry -> Bool
/= :: CacheEntry -> CacheEntry -> Bool
Eq)

-- | Sliding-window message cache.
data MessageCache = MessageCache
  { MessageCache -> Seq [CacheEntry]
mcWindows :: !(Seq [CacheEntry])          -- ^ Circular windows, newest first (index 0)
  , MessageCache -> Map MessageId CacheEntry
mcIndex   :: !(Map MessageId CacheEntry)  -- ^ Fast lookup by message ID
  , MessageCache -> Int
mcLen     :: !Int                         -- ^ Total number of windows
  , MessageCache -> Int
mcGossip  :: !Int                         -- ^ Number of windows for gossip (IHAVE)
  } deriving (Int -> MessageCache -> ShowS
[MessageCache] -> ShowS
MessageCache -> String
(Int -> MessageCache -> ShowS)
-> (MessageCache -> String)
-> ([MessageCache] -> ShowS)
-> Show MessageCache
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> MessageCache -> ShowS
showsPrec :: Int -> MessageCache -> ShowS
$cshow :: MessageCache -> String
show :: MessageCache -> String
$cshowList :: [MessageCache] -> ShowS
showList :: [MessageCache] -> ShowS
Show, MessageCache -> MessageCache -> Bool
(MessageCache -> MessageCache -> Bool)
-> (MessageCache -> MessageCache -> Bool) -> Eq MessageCache
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: MessageCache -> MessageCache -> Bool
== :: MessageCache -> MessageCache -> Bool
$c/= :: MessageCache -> MessageCache -> Bool
/= :: MessageCache -> MessageCache -> Bool
Eq)

-- Router state

-- | GossipSub router state with STM-managed concurrent state.
data GossipSubRouter = GossipSubRouter
  { GossipSubRouter -> GossipSubParams
gsParams      :: !GossipSubParams
  , GossipSubRouter -> PeerId
gsLocalPeerId :: !PeerId
    -- Mesh and fanout state
  , GossipSubRouter -> TVar (Map Text (Set PeerId))
gsMesh        :: !(TVar (Map Topic (Set PeerId)))
  , GossipSubRouter -> TVar (Map Text (Set PeerId))
gsFanout      :: !(TVar (Map Topic (Set PeerId)))
  , GossipSubRouter -> TVar (Map Text UTCTime)
gsFanoutPub   :: !(TVar (Map Topic UTCTime))
    -- Peer tracking
  , GossipSubRouter -> TVar (Map PeerId PeerState)
gsPeers       :: !(TVar (Map PeerId PeerState))
    -- Message deduplication
  , GossipSubRouter -> TVar (Map MessageId UTCTime)
gsSeen        :: !(TVar (Map MessageId UTCTime))
    -- Backoff state
  , GossipSubRouter -> TVar (Map (PeerId, Text) UTCTime)
gsBackoff     :: !(TVar (Map (PeerId, Topic) UTCTime))
    -- Scoring (Phase 9b)
  , GossipSubRouter -> PeerScoreParams
gsScoreParams :: !PeerScoreParams
  , GossipSubRouter -> ScoreThresholds
gsThresholds  :: !ScoreThresholds
  , GossipSubRouter -> TVar (Map MessageId (Set PeerId))
gsIPPeerCount :: !(TVar (Map ByteString (Set PeerId)))
    -- ^ IP address → peers sharing that IP (for P6)
    -- Message cache (Phase 9c)
  , GossipSubRouter -> TVar MessageCache
gsMessageCache  :: !(TVar MessageCache)
    -- ^ Sliding-window message cache for IWANT and IHAVE
  , GossipSubRouter -> TVar Int
gsHeartbeatCount :: !(TVar Int)
    -- ^ Heartbeat counter (for opportunistic graft timing)
    -- Injectable functions for testability
  , GossipSubRouter -> PeerId -> RPC -> IO ()
gsSendRPC     :: !(PeerId -> RPC -> IO ())
    -- ^ Fire-and-forget RPC sender
  , GossipSubRouter -> IO UTCTime
gsGetTime     :: !(IO UTCTime)
    -- ^ Time source (injectable for deterministic tests)
  , GossipSubRouter -> TVar (Text -> PubSubMessage -> IO ())
gsOnMessage   :: !(TVar (Topic -> PubSubMessage -> IO ()))
    -- ^ Application message callback
  }

-- Defaults

-- | Empty RPC with no subscriptions, messages, or control.
emptyRPC :: RPC
emptyRPC :: RPC
emptyRPC = [SubOpts] -> [PubSubMessage] -> Maybe ControlMessage -> RPC
RPC [] [] Maybe ControlMessage
forall a. Maybe a
Nothing

-- | Empty control message with no IHAVE/IWANT/GRAFT/PRUNE.
emptyControlMessage :: ControlMessage
emptyControlMessage :: ControlMessage
emptyControlMessage = [IHave] -> [IWant] -> [Graft] -> [Prune] -> ControlMessage
ControlMessage [] [] [] []