packages feed

haskoin-node-0.11.3: src/Haskoin/Node/Peer.hs

{-# LANGUAGE ConstraintKinds       #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeFamilies          #-}
{-|
Module      : Network.Haskoin.Node.Peer
Copyright   : No rights reserved
License     : UNLICENSE
Maintainer  : jprupp@protonmail.ch
Stability   : experimental
Portability : POSIX

Network peer process. Represents a network peer connection locally.
-}
module Haskoin.Node.Peer
    ( peer
    ) where

import           Conduit                 (ConduitT, awaitForever, foldC, mapM_C,
                                          runConduit, takeCE, yield, (.|))
import           Control.Monad           (forever, when)
import           Control.Monad.Logger    (MonadLoggerIO, logErrorS)
import           Control.Monad.Reader    (runReaderT)
import           Data.ByteString         (ByteString)
import qualified Data.ByteString         as B
import           Data.Conduit.Network    (appSink, appSource)
import           Data.Serialize          (decode, runGet, runPut)
import           Data.String.Conversions (cs)
import           Haskoin                 (Message, MessageHeader (..), Network,
                                          getMessage, putMessage)
import           Haskoin.Node.Common     (PeerConfig (..), PeerException (..),
                                          PeerMessage (..), peerString,
                                          withConnection)
import           Network.Socket          (SockAddr)
import           NQE                     (Inbox, PublisherMessage (..), receive,
                                          send)
import           UnliftIO                (MonadUnliftIO, atomically, link,
                                          throwIO, withAsync)

-- | Run peer process in current thread.
peer ::
       (MonadUnliftIO m, MonadLoggerIO m)
    => PeerConfig
    -> Inbox PeerMessage
    -> m ()
peer pc inbox = withConnection a $ \ad -> runReaderT (peer_session ad) pc
  where
    a = peerConfAddress pc
    go = forever $ do
      receive inbox >>= dispatchMessage pc
    net = peerConfNetwork pc
    peer_session ad =
        let ins = appSource ad
            ons = appSink ad
            src = runConduit $ ins .| inPeerConduit net a .| mapM_C send_msg
            snk = outPeerConduit net .| ons
         in withAsync src $ \as -> do
                link as
                runConduit (go .| snk)
    send_msg = (`send` peerConfListen pc) . Event

-- | Internal function to dispatch peer messages.
dispatchMessage ::
       MonadLoggerIO m => PeerConfig -> PeerMessage -> ConduitT i Message m ()
dispatchMessage _ (SendMessage msg) = yield msg
dispatchMessage cfg (GetPublisher reply) =
    atomically (reply (peerConfListen cfg))
dispatchMessage _ (KillPeer e) = throwIO e

-- | Internal conduit to parse messages coming from peer.
inPeerConduit ::
       MonadLoggerIO m
    => Network
    -> SockAddr
    -> ConduitT ByteString Message m ()
inPeerConduit net a =
    forever $ do
        x <- takeCE 24 .| foldC
        case decode x of
            Left _ -> do
                $(logErrorS)
                    (peerString a)
                    "Could not decode incoming message header"
                throwIO DecodeHeaderError
            Right (MessageHeader _ _ len _) -> do
                when (len > 32 * 2 ^ (20 :: Int)) $ do
                    $(logErrorS) (peerString a) "Payload too large"
                    throwIO $ PayloadTooLarge len
                y <- takeCE (fromIntegral len) .| foldC
                case runGet (getMessage net) $ x `B.append` y of
                    Left e -> do
                        $(logErrorS) (peerString a) $
                            "Cannot decode payload: " <> cs (show e)
                        throwIO CannotDecodePayload
                    Right msg -> yield msg

-- | Outgoing peer conduit to serialize and send messages.
outPeerConduit :: Monad m => Network -> ConduitT Message ByteString m ()
outPeerConduit net = awaitForever $ yield . runPut . putMessage net