packages feed

haskoin-core-0.21.0: src/Haskoin/Network/Message.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}

{- |
Module      : Haskoin.Network.Message
Copyright   : No rights reserved
License     : MIT
Maintainer  : jprupp@protonmail.ch
Stability   : experimental
Portability : POSIX

Peer-to-peer network message serialization.
-}
module Haskoin.Network.Message (
    -- * Network Message
    Message (..),
    MessageHeader (..),
    msgType,
    putMessage,
    getMessage,
) where

import Control.DeepSeq
import Control.Monad (unless)
import Data.Binary (Binary (..))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Serialize (Serialize (..))
import Data.Word (Word32)
import GHC.Generics (Generic)
import Haskoin.Block.Common
import Haskoin.Block.Merkle
import Haskoin.Crypto.Hash
import Haskoin.Data
import Haskoin.Network.Bloom
import Haskoin.Network.Common
import Haskoin.Transaction.Common

{- | Data type representing the header of a 'Message'. All messages sent between
 nodes contain a message header.
-}
data MessageHeader = MessageHeader
    { -- | magic bytes identify network
      headMagic :: !Word32
    , -- | message type
      headCmd :: !MessageCommand
    , -- | length of payload
      headPayloadSize :: !Word32
    , -- | checksum of payload
      headChecksum :: !CheckSum32
    }
    deriving (Eq, Show, Generic, NFData)

instance Serial MessageHeader where
    deserialize =
        MessageHeader
            <$> getWord32be
            <*> deserialize
            <*> getWord32le
            <*> deserialize

    serialize (MessageHeader m c l chk) = do
        putWord32be m
        serialize c
        putWord32le l
        serialize chk

instance Binary MessageHeader where
    put = serialize
    get = deserialize

instance Serialize MessageHeader where
    put = serialize
    get = deserialize

{- | The 'Message' type is used to identify all the valid messages that can be
 sent between bitcoin peers. Only values of type 'Message' will be accepted
 by other bitcoin peers as bitcoin protocol messages need to be correctly
 serialized with message headers. Serializing a 'Message' value will
 include the 'MessageHeader' with the correct checksum value automatically.
 No need to add the 'MessageHeader' separately.
-}
data Message
    = MVersion !Version
    | MVerAck
    | MAddr !Addr
    | MInv !Inv
    | MGetData !GetData
    | MNotFound !NotFound
    | MGetBlocks !GetBlocks
    | MGetHeaders !GetHeaders
    | MTx !Tx
    | MBlock !Block
    | MMerkleBlock !MerkleBlock
    | MHeaders !Headers
    | MGetAddr
    | MFilterLoad !FilterLoad
    | MFilterAdd !FilterAdd
    | MFilterClear
    | MPing !Ping
    | MPong !Pong
    | MAlert !Alert
    | MMempool
    | MReject !Reject
    | MSendHeaders
    | MOther !ByteString !ByteString
    deriving (Eq, Show, Generic, NFData)

-- | Get 'MessageCommand' assocated with a message.
msgType :: Message -> MessageCommand
msgType (MVersion _) = MCVersion
msgType MVerAck = MCVerAck
msgType (MAddr _) = MCAddr
msgType (MInv _) = MCInv
msgType (MGetData _) = MCGetData
msgType (MNotFound _) = MCNotFound
msgType (MGetBlocks _) = MCGetBlocks
msgType (MGetHeaders _) = MCGetHeaders
msgType (MTx _) = MCTx
msgType (MBlock _) = MCBlock
msgType (MMerkleBlock _) = MCMerkleBlock
msgType (MHeaders _) = MCHeaders
msgType (MFilterLoad _) = MCFilterLoad
msgType (MFilterAdd _) = MCFilterAdd
msgType MFilterClear = MCFilterClear
msgType (MPing _) = MCPing
msgType (MPong _) = MCPong
msgType (MAlert _) = MCAlert
msgType MMempool = MCMempool
msgType (MReject _) = MCReject
msgType MSendHeaders = MCSendHeaders
msgType MGetAddr = MCGetAddr
msgType (MOther c _) = MCOther c

-- | Deserializer for network messages.
getMessage :: MonadGet m => Network -> m Message
getMessage net = do
    (MessageHeader mgc cmd len chk) <- deserialize
    bs <- lookAhead $ getByteString $ fromIntegral len
    unless
        (mgc == getNetworkMagic net)
        (fail $ "get: Invalid network magic bytes: " ++ show mgc)
    unless
        (checkSum32 bs == chk)
        (fail $ "get: Invalid message checksum: " ++ show chk)
    if len > 0
        then do
            bs <- ensure (fromIntegral len)
            let f = case cmd of
                    MCVersion -> MVersion <$> deserialize
                    MCAddr -> MAddr <$> deserialize
                    MCInv -> MInv <$> deserialize
                    MCGetData -> MGetData <$> deserialize
                    MCNotFound -> MNotFound <$> deserialize
                    MCGetBlocks -> MGetBlocks <$> deserialize
                    MCGetHeaders -> MGetHeaders <$> deserialize
                    MCTx -> MTx <$> deserialize
                    MCBlock -> MBlock <$> deserialize
                    MCMerkleBlock -> MMerkleBlock <$> deserialize
                    MCHeaders -> MHeaders <$> deserialize
                    MCFilterLoad -> MFilterLoad <$> deserialize
                    MCFilterAdd -> MFilterAdd <$> deserialize
                    MCPing -> MPing <$> deserialize
                    MCPong -> MPong <$> deserialize
                    MCAlert -> MAlert <$> deserialize
                    MCReject -> MReject <$> deserialize
                    MCOther c -> MOther c <$> getByteString (fromIntegral len)
                    _ ->
                        fail $
                            "get: command " ++ show cmd
                                ++ " should not carry a payload"
            either fail return (runGetS f bs)
        else case cmd of
            MCGetAddr -> return MGetAddr
            MCVerAck -> return MVerAck
            MCFilterClear -> return MFilterClear
            MCMempool -> return MMempool
            MCSendHeaders -> return MSendHeaders
            MCOther c -> return (MOther c BS.empty)
            _ ->
                fail $
                    "get: command " ++ show cmd
                        ++ " is expected to carry a payload"

-- | Serializer for network messages.
putMessage :: MonadPut m => Network -> Message -> m ()
putMessage net msg = do
    let (cmd, payload) =
            case msg of
                MVersion m -> (MCVersion, runPutS $ serialize m)
                MVerAck -> (MCVerAck, BS.empty)
                MAddr m -> (MCAddr, runPutS $ serialize m)
                MInv m -> (MCInv, runPutS $ serialize m)
                MGetData m -> (MCGetData, runPutS $ serialize m)
                MNotFound m -> (MCNotFound, runPutS $ serialize m)
                MGetBlocks m -> (MCGetBlocks, runPutS $ serialize m)
                MGetHeaders m -> (MCGetHeaders, runPutS $ serialize m)
                MTx m -> (MCTx, runPutS $ serialize m)
                MBlock m -> (MCBlock, runPutS $ serialize m)
                MMerkleBlock m -> (MCMerkleBlock, runPutS $ serialize m)
                MHeaders m -> (MCHeaders, runPutS $ serialize m)
                MGetAddr -> (MCGetAddr, BS.empty)
                MFilterLoad m -> (MCFilterLoad, runPutS $ serialize m)
                MFilterAdd m -> (MCFilterAdd, runPutS $ serialize m)
                MFilterClear -> (MCFilterClear, BS.empty)
                MPing m -> (MCPing, runPutS $ serialize m)
                MPong m -> (MCPong, runPutS $ serialize m)
                MAlert m -> (MCAlert, runPutS $ serialize m)
                MMempool -> (MCMempool, BS.empty)
                MReject m -> (MCReject, runPutS $ serialize m)
                MSendHeaders -> (MCSendHeaders, BS.empty)
                MOther c p -> (MCOther c, p)
        chk = checkSum32 payload
        len = fromIntegral $ BS.length payload
        header = MessageHeader (getNetworkMagic net) cmd len chk
    serialize header
    putByteString payload