module Network.Haskoin.Node.Types
( Addr(..)
, NetworkAddressTime
, Alert(..)
, GetData(..)
, Inv(..)
, InvVector(..)
, InvType(..)
, NetworkAddress(..)
, NotFound(..)
, Ping(..)
, Pong(..)
, Reject(..)
, RejectCode(..)
, reject
, VarInt(..)
, VarString(..)
, Version(..)
, MessageCommand(..)
) where
import Control.DeepSeq (NFData, rnf)
import Control.Monad (replicateM, liftM2, forM_, unless)
import Data.Word (Word32, Word64)
import Data.Binary (Binary, get, put)
import Data.Binary.Get
( Get
, getWord8
, getWord16le
, getWord16be
, getWord32be
, getWord32host
, getWord32le
, getWord64le
, getByteString
, isEmpty
)
import Data.Binary.Put
( Put
, putWord8
, putWord16le
, putWord16be
, putWord32be
, putWord32host
, putWord32le
, putWord64le
, putByteString
)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
( length
, takeWhile
, empty
, null
, take
)
import Data.ByteString.Char8 as C (replicate)
import Data.String.Conversions (cs)
import Network.Socket (SockAddr (SockAddrInet, SockAddrInet6))
import Network.Haskoin.Crypto.Hash
type NetworkAddressTime = (Word32, NetworkAddress)
data Addr =
Addr {
addrList :: ![NetworkAddressTime]
}
deriving (Eq, Show)
instance Binary Addr where
get = Addr <$> (repList =<< get)
where
repList (VarInt c) = replicateM (fromIntegral c) action
action = liftM2 (,) getWord32le get
put (Addr xs) = do
put $ VarInt $ fromIntegral $ length xs
forM_ xs $ \(a,b) -> putWord32le a >> put b
data Alert =
Alert {
alertPayload :: !VarString
, alertSignature :: !VarString
} deriving (Eq, Show, Read)
instance NFData Alert where
rnf (Alert p s) = rnf p `seq` rnf s
instance Binary Alert where
get = Alert <$> get <*> get
put (Alert p s) = put p >> put s
data GetData =
GetData {
getDataList :: ![InvVector]
} deriving (Eq, Show, Read)
instance NFData GetData where
rnf (GetData l) = rnf l
instance Binary GetData where
get = GetData <$> (repList =<< get)
where
repList (VarInt c) = replicateM (fromIntegral c) get
put (GetData xs) = do
put $ VarInt $ fromIntegral $ length xs
forM_ xs put
data Inv =
Inv {
invList :: ![InvVector]
} deriving (Eq, Show, Read)
instance NFData Inv where
rnf (Inv l) = rnf l
instance Binary Inv where
get = Inv <$> (repList =<< get)
where
repList (VarInt c) = replicateM (fromIntegral c) get
put (Inv xs) = do
put $ VarInt $ fromIntegral $ length xs
forM_ xs put
data InvType
= InvError
| InvTx
| InvBlock
| InvMerkleBlock
deriving (Eq, Show, Read)
instance NFData InvType where rnf x = seq x ()
instance Binary InvType where
get = go =<< getWord32le
where
go x = case x of
0 -> return InvError
1 -> return InvTx
2 -> return InvBlock
3 -> return InvMerkleBlock
_ -> fail "bitcoinGet InvType: Invalid Type"
put x = putWord32le $ case x of
InvError -> 0
InvTx -> 1
InvBlock -> 2
InvMerkleBlock -> 3
data InvVector =
InvVector {
invType :: !InvType
, invHash :: !Hash256
} deriving (Eq, Show, Read)
instance NFData InvVector where
rnf (InvVector t h) = rnf t `seq` rnf h
instance Binary InvVector where
get = InvVector <$> get <*> get
put (InvVector t h) = put t >> put h
data NetworkAddress =
NetworkAddress {
naServices :: !Word64
, naAddress :: !SockAddr
} deriving (Eq, Show)
instance NFData NetworkAddress where
rnf NetworkAddress{..} = rnf naServices `seq` naAddress `seq` ()
instance Binary NetworkAddress where
get = NetworkAddress <$> getWord64le
<*> getAddrPort
where
getAddrPort = do
a <- getWord32be
b <- getWord32be
c <- getWord32be
if a == 0x00000000 && b == 0x00000000 && c == 0x0000ffff
then do
d <- getWord32host
p <- getWord16be
return $ SockAddrInet (fromIntegral p) d
else do
d <- getWord32be
p <- getWord16be
return $ SockAddrInet6 (fromIntegral p) 0 (a,b,c,d) 0
put (NetworkAddress s (SockAddrInet6 p _ (a,b,c,d) _)) = do
putWord64le s
putWord32be a
putWord32be b
putWord32be c
putWord32be d
putWord16be (fromIntegral p)
put (NetworkAddress s (SockAddrInet p a)) = do
putWord64le s
putWord32be 0x00000000
putWord32be 0x00000000
putWord32be 0x0000ffff
putWord32host a
putWord16be (fromIntegral p)
put _ = error "NetworkAddress can onle be IPv4 or IPv6"
data NotFound =
NotFound {
notFoundList :: ![InvVector]
} deriving (Eq, Show, Read)
instance NFData NotFound where
rnf (NotFound l) = rnf l
instance Binary NotFound where
get = NotFound <$> (repList =<< get)
where
repList (VarInt c) = replicateM (fromIntegral c) get
put (NotFound xs) = do
put $ VarInt $ fromIntegral $ length xs
forM_ xs put
newtype Ping =
Ping {
pingNonce :: Word64
} deriving (Eq, Show, Read)
instance NFData Ping where
rnf (Ping n) = rnf n
newtype Pong =
Pong {
pongNonce :: Word64
} deriving (Eq, Show, Read)
instance NFData Pong where
rnf (Pong n) = rnf n
instance Binary Ping where
get = Ping <$> getWord64le
put (Ping n) = putWord64le n
instance Binary Pong where
get = Pong <$> getWord64le
put (Pong n) = putWord64le n
data Reject =
Reject {
rejectMessage :: !MessageCommand
, rejectCode :: !RejectCode
, rejectReason :: !VarString
, rejectData :: !ByteString
} deriving (Eq, Show, Read)
data RejectCode
= RejectMalformed
| RejectInvalid
| RejectObsolete
| RejectDuplicate
| RejectNonStandard
| RejectDust
| RejectInsufficientFee
| RejectCheckpoint
deriving (Eq, Show, Read)
instance Binary RejectCode where
get = getWord8 >>= \code -> case code of
0x01 -> return RejectMalformed
0x10 -> return RejectInvalid
0x11 -> return RejectObsolete
0x12 -> return RejectDuplicate
0x40 -> return RejectNonStandard
0x41 -> return RejectDust
0x42 -> return RejectInsufficientFee
0x43 -> return RejectCheckpoint
_ -> fail $ unwords
[ "Reject get: Invalid code"
, show code
]
put code = putWord8 $ case code of
RejectMalformed -> 0x01
RejectInvalid -> 0x10
RejectObsolete -> 0x11
RejectDuplicate -> 0x12
RejectNonStandard -> 0x40
RejectDust -> 0x41
RejectInsufficientFee -> 0x42
RejectCheckpoint -> 0x43
reject :: MessageCommand -> RejectCode -> ByteString -> Reject
reject cmd code reason =
Reject cmd code (VarString reason) BS.empty
instance Binary Reject where
get = get >>= \(VarString bs) -> case stringToCommand bs of
Just cmd -> Reject cmd <$> get <*> get <*> maybeData
_ -> fail $ unwords
["Reason get: Invalid message command" ,cs bs]
where
maybeData = isEmpty >>= \done ->
if done then return BS.empty else getByteString 32
put (Reject cmd code reason dat) = do
put $ VarString $ commandToString cmd
put code
put reason
unless (BS.null dat) $ putByteString dat
newtype VarInt = VarInt { getVarInt :: Word64 }
deriving (Eq, Show, Read)
instance NFData VarInt where
rnf (VarInt w) = rnf w
instance Binary VarInt where
get = VarInt <$> ( getWord8 >>= go )
where
go 0xff = getWord64le
go 0xfe = fromIntegral <$> getWord32le
go 0xfd = fromIntegral <$> getWord16le
go x = fromIntegral <$> return x
put (VarInt x)
| x < 0xfd =
putWord8 $ fromIntegral x
| x <= 0xffff = do
putWord8 0xfd
putWord16le $ fromIntegral x
| x <= 0xffffffff = do
putWord8 0xfe
putWord32le $ fromIntegral x
| otherwise = do
putWord8 0xff
putWord64le x
newtype VarString = VarString { getVarString :: ByteString }
deriving (Eq, Show, Read)
instance NFData VarString where
rnf (VarString s) = rnf s
instance Binary VarString where
get = VarString <$> (readBS =<< get)
where
readBS (VarInt len) = getByteString (fromIntegral len)
put (VarString bs) = do
put $ VarInt $ fromIntegral $ BS.length bs
putByteString bs
data Version =
Version {
version :: !Word32
, services :: !Word64
, timestamp :: !Word64
, addrRecv :: !NetworkAddress
, addrSend :: !NetworkAddress
, verNonce :: !Word64
, userAgent :: !VarString
, startHeight :: !Word32
, relay :: !Bool
} deriving (Eq, Show)
instance NFData Version where
rnf Version{..} =
rnf version `seq`
rnf services `seq`
rnf timestamp `seq`
rnf addrRecv `seq`
rnf addrSend `seq`
rnf verNonce `seq`
rnf userAgent `seq`
rnf startHeight `seq`
rnf relay
instance Binary Version where
get = Version <$> getWord32le
<*> getWord64le
<*> getWord64le
<*> get
<*> get
<*> getWord64le
<*> get
<*> getWord32le
<*> (go =<< isEmpty)
where
go True = return True
go False = getBool
put (Version v s t ar as n ua sh r) = do
putWord32le v
putWord64le s
putWord64le t
put ar
put as
putWord64le n
put ua
putWord32le sh
putBool r
getBool :: Get Bool
getBool = go =<< getWord8
where
go 0 = return False
go _ = return True
putBool :: Bool -> Put
putBool True = putWord8 1
putBool False = putWord8 0
data MessageCommand
= MCVersion
| MCVerAck
| MCAddr
| MCInv
| MCGetData
| MCNotFound
| MCGetBlocks
| MCGetHeaders
| MCTx
| MCBlock
| MCMerkleBlock
| MCHeaders
| MCGetAddr
| MCFilterLoad
| MCFilterAdd
| MCFilterClear
| MCPing
| MCPong
| MCAlert
| MCMempool
| MCReject
deriving (Eq, Show, Read)
instance NFData MessageCommand where rnf x = seq x ()
instance Binary MessageCommand where
get = go =<< getByteString 12
where
go bs = case stringToCommand $ unpackCommand bs of
Just cmd -> return cmd
Nothing -> fail "get MessageCommand : Invalid command"
put mc = putByteString $ packCommand $ commandToString mc
stringToCommand :: ByteString -> Maybe MessageCommand
stringToCommand str = case str of
"version" -> Just MCVersion
"verack" -> Just MCVerAck
"addr" -> Just MCAddr
"inv" -> Just MCInv
"getdata" -> Just MCGetData
"notfound" -> Just MCNotFound
"getblocks" -> Just MCGetBlocks
"getheaders" -> Just MCGetHeaders
"tx" -> Just MCTx
"block" -> Just MCBlock
"merkleblock" -> Just MCMerkleBlock
"headers" -> Just MCHeaders
"getaddr" -> Just MCGetAddr
"filterload" -> Just MCFilterLoad
"filteradd" -> Just MCFilterAdd
"filterclear" -> Just MCFilterClear
"ping" -> Just MCPing
"pong" -> Just MCPong
"alert" -> Just MCAlert
"mempool" -> Just MCMempool
"reject" -> Just MCReject
_ -> Nothing
commandToString :: MessageCommand -> ByteString
commandToString mc = case mc of
MCVersion -> "version"
MCVerAck -> "verack"
MCAddr -> "addr"
MCInv -> "inv"
MCGetData -> "getdata"
MCNotFound -> "notfound"
MCGetBlocks -> "getblocks"
MCGetHeaders -> "getheaders"
MCTx -> "tx"
MCBlock -> "block"
MCMerkleBlock -> "merkleblock"
MCHeaders -> "headers"
MCGetAddr -> "getaddr"
MCFilterLoad -> "filterload"
MCFilterAdd -> "filteradd"
MCFilterClear -> "filterclear"
MCPing -> "ping"
MCPong -> "pong"
MCAlert -> "alert"
MCMempool -> "mempool"
MCReject -> "reject"
packCommand :: ByteString -> ByteString
packCommand s = BS.take 12 $
s `mappend` C.replicate 12 '\NUL'
unpackCommand :: ByteString -> ByteString
unpackCommand = BS.takeWhile (/= 0)