{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
module Lightning.Protocol.BOLT1.TLV (
TlvRecord(..)
, TlvStream
, unTlvStream
, tlvStream
, unsafeTlvStream
, TlvError(..)
, encodeTlvRecord
, encodeTlvStream
, decodeTlvStream
, decodeTlvStreamWith
, decodeTlvStreamRaw
, InitTlv(..)
, parseInitTlvs
, encodeInitTlvs
, ChainHash
, chainHash
, unChainHash
) where
import Control.DeepSeq (NFData)
import Control.Monad (when)
import qualified Data.ByteString as BS
import Data.Word (Word64)
import GHC.Generics (Generic)
import Lightning.Protocol.BOLT1.Prim
data TlvRecord = TlvRecord
{ TlvRecord -> Word64
tlvType :: {-# UNPACK #-} !Word64
, TlvRecord -> ByteString
tlvValue :: !BS.ByteString
} deriving stock (TlvRecord -> TlvRecord -> Bool
(TlvRecord -> TlvRecord -> Bool)
-> (TlvRecord -> TlvRecord -> Bool) -> Eq TlvRecord
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TlvRecord -> TlvRecord -> Bool
== :: TlvRecord -> TlvRecord -> Bool
$c/= :: TlvRecord -> TlvRecord -> Bool
/= :: TlvRecord -> TlvRecord -> Bool
Eq, Int -> TlvRecord -> ShowS
[TlvRecord] -> ShowS
TlvRecord -> String
(Int -> TlvRecord -> ShowS)
-> (TlvRecord -> String)
-> ([TlvRecord] -> ShowS)
-> Show TlvRecord
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TlvRecord -> ShowS
showsPrec :: Int -> TlvRecord -> ShowS
$cshow :: TlvRecord -> String
show :: TlvRecord -> String
$cshowList :: [TlvRecord] -> ShowS
showList :: [TlvRecord] -> ShowS
Show, (forall x. TlvRecord -> Rep TlvRecord x)
-> (forall x. Rep TlvRecord x -> TlvRecord) -> Generic TlvRecord
forall x. Rep TlvRecord x -> TlvRecord
forall x. TlvRecord -> Rep TlvRecord x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. TlvRecord -> Rep TlvRecord x
from :: forall x. TlvRecord -> Rep TlvRecord x
$cto :: forall x. Rep TlvRecord x -> TlvRecord
to :: forall x. Rep TlvRecord x -> TlvRecord
Generic)
instance NFData TlvRecord
newtype TlvStream = TlvStream { TlvStream -> [TlvRecord]
unTlvStream :: [TlvRecord] }
deriving stock (TlvStream -> TlvStream -> Bool
(TlvStream -> TlvStream -> Bool)
-> (TlvStream -> TlvStream -> Bool) -> Eq TlvStream
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TlvStream -> TlvStream -> Bool
== :: TlvStream -> TlvStream -> Bool
$c/= :: TlvStream -> TlvStream -> Bool
/= :: TlvStream -> TlvStream -> Bool
Eq, Int -> TlvStream -> ShowS
[TlvStream] -> ShowS
TlvStream -> String
(Int -> TlvStream -> ShowS)
-> (TlvStream -> String)
-> ([TlvStream] -> ShowS)
-> Show TlvStream
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TlvStream -> ShowS
showsPrec :: Int -> TlvStream -> ShowS
$cshow :: TlvStream -> String
show :: TlvStream -> String
$cshowList :: [TlvStream] -> ShowS
showList :: [TlvStream] -> ShowS
Show, (forall x. TlvStream -> Rep TlvStream x)
-> (forall x. Rep TlvStream x -> TlvStream) -> Generic TlvStream
forall x. Rep TlvStream x -> TlvStream
forall x. TlvStream -> Rep TlvStream x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. TlvStream -> Rep TlvStream x
from :: forall x. TlvStream -> Rep TlvStream x
$cto :: forall x. Rep TlvStream x -> TlvStream
to :: forall x. Rep TlvStream x -> TlvStream
Generic)
instance NFData TlvStream
tlvStream :: [TlvRecord] -> Maybe TlvStream
tlvStream :: [TlvRecord] -> Maybe TlvStream
tlvStream [TlvRecord]
recs
| [Word64] -> Bool
isStrictlyIncreasing ((TlvRecord -> Word64) -> [TlvRecord] -> [Word64]
forall a b. (a -> b) -> [a] -> [b]
map TlvRecord -> Word64
tlvType [TlvRecord]
recs) = TlvStream -> Maybe TlvStream
forall a. a -> Maybe a
Just ([TlvRecord] -> TlvStream
TlvStream [TlvRecord]
recs)
| Bool
otherwise = Maybe TlvStream
forall a. Maybe a
Nothing
where
isStrictlyIncreasing :: [Word64] -> Bool
isStrictlyIncreasing :: [Word64] -> Bool
isStrictlyIncreasing [] = Bool
True
isStrictlyIncreasing [Word64
_] = Bool
True
isStrictlyIncreasing (Word64
x:Word64
y:[Word64]
rest) = Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
y Bool -> Bool -> Bool
&& [Word64] -> Bool
isStrictlyIncreasing (Word64
yWord64 -> [Word64] -> [Word64]
forall a. a -> [a] -> [a]
:[Word64]
rest)
unsafeTlvStream :: [TlvRecord] -> TlvStream
unsafeTlvStream :: [TlvRecord] -> TlvStream
unsafeTlvStream = [TlvRecord] -> TlvStream
TlvStream
data TlvError
= TlvNonMinimalEncoding
| TlvNotStrictlyIncreasing
| TlvLengthExceedsBounds
| TlvUnknownEvenType !Word64
| TlvInvalidKnownType !Word64
deriving stock (TlvError -> TlvError -> Bool
(TlvError -> TlvError -> Bool)
-> (TlvError -> TlvError -> Bool) -> Eq TlvError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TlvError -> TlvError -> Bool
== :: TlvError -> TlvError -> Bool
$c/= :: TlvError -> TlvError -> Bool
/= :: TlvError -> TlvError -> Bool
Eq, Int -> TlvError -> ShowS
[TlvError] -> ShowS
TlvError -> String
(Int -> TlvError -> ShowS)
-> (TlvError -> String) -> ([TlvError] -> ShowS) -> Show TlvError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TlvError -> ShowS
showsPrec :: Int -> TlvError -> ShowS
$cshow :: TlvError -> String
show :: TlvError -> String
$cshowList :: [TlvError] -> ShowS
showList :: [TlvError] -> ShowS
Show, (forall x. TlvError -> Rep TlvError x)
-> (forall x. Rep TlvError x -> TlvError) -> Generic TlvError
forall x. Rep TlvError x -> TlvError
forall x. TlvError -> Rep TlvError x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. TlvError -> Rep TlvError x
from :: forall x. TlvError -> Rep TlvError x
$cto :: forall x. Rep TlvError x -> TlvError
to :: forall x. Rep TlvError x -> TlvError
Generic)
instance NFData TlvError
encodeTlvRecord :: TlvRecord -> BS.ByteString
encodeTlvRecord :: TlvRecord -> ByteString
encodeTlvRecord (TlvRecord Word64
typ ByteString
val) = [ByteString] -> ByteString
forall a. Monoid a => [a] -> a
mconcat
[ Word64 -> ByteString
encodeBigSize Word64
typ
, Word64 -> ByteString
encodeBigSize (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
BS.length ByteString
val))
, ByteString
val
]
encodeTlvStream :: TlvStream -> BS.ByteString
encodeTlvStream :: TlvStream -> ByteString
encodeTlvStream (TlvStream [TlvRecord]
recs) = [ByteString] -> ByteString
forall a. Monoid a => [a] -> a
mconcat ((TlvRecord -> ByteString) -> [TlvRecord] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map TlvRecord -> ByteString
encodeTlvRecord [TlvRecord]
recs)
decodeTlvStreamRaw :: BS.ByteString -> Either TlvError TlvStream
decodeTlvStreamRaw :: ByteString -> Either TlvError TlvStream
decodeTlvStreamRaw = Maybe Word64
-> [TlvRecord] -> ByteString -> Either TlvError TlvStream
go Maybe Word64
forall a. Maybe a
Nothing []
where
go :: Maybe Word64 -> [TlvRecord] -> BS.ByteString
-> Either TlvError TlvStream
go :: Maybe Word64
-> [TlvRecord] -> ByteString -> Either TlvError TlvStream
go !Maybe Word64
_ ![TlvRecord]
acc !ByteString
bs
| ByteString -> Bool
BS.null ByteString
bs = TlvStream -> Either TlvError TlvStream
forall a b. b -> Either a b
Right ([TlvRecord] -> TlvStream
unsafeTlvStream ([TlvRecord] -> [TlvRecord]
forall a. [a] -> [a]
reverse [TlvRecord]
acc))
go !Maybe Word64
mPrevType ![TlvRecord]
acc !ByteString
bs = do
(typ, rest1) <- Either TlvError (Word64, ByteString)
-> ((Word64, ByteString) -> Either TlvError (Word64, ByteString))
-> Maybe (Word64, ByteString)
-> Either TlvError (Word64, ByteString)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (TlvError -> Either TlvError (Word64, ByteString)
forall a b. a -> Either a b
Left TlvError
TlvNonMinimalEncoding) (Word64, ByteString) -> Either TlvError (Word64, ByteString)
forall a b. b -> Either a b
Right
(ByteString -> Maybe (Word64, ByteString)
decodeBigSize ByteString
bs)
case mPrevType of
Just Word64
prevType -> Bool -> Either TlvError () -> Either TlvError ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Word64
typ Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
prevType) (Either TlvError () -> Either TlvError ())
-> Either TlvError () -> Either TlvError ()
forall a b. (a -> b) -> a -> b
$
TlvError -> Either TlvError ()
forall a b. a -> Either a b
Left TlvError
TlvNotStrictlyIncreasing
Maybe Word64
Nothing -> () -> Either TlvError ()
forall a. a -> Either TlvError a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
(len, rest2) <- maybe (Left TlvNonMinimalEncoding) Right
(decodeBigSize rest1)
when (fromIntegral len > BS.length rest2) $
Left TlvLengthExceedsBounds
let !val = Int -> ByteString -> ByteString
BS.take (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
len) ByteString
rest2
!rest3 = Int -> ByteString -> ByteString
BS.drop (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
len) ByteString
rest2
!rec = Word64 -> ByteString -> TlvRecord
TlvRecord Word64
typ ByteString
val
go (Just typ) (rec : acc) rest3
decodeTlvStreamWith
:: (Word64 -> Bool)
-> BS.ByteString
-> Either TlvError TlvStream
decodeTlvStreamWith :: (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
decodeTlvStreamWith Word64 -> Bool
isKnown = Maybe Word64
-> [TlvRecord] -> ByteString -> Either TlvError TlvStream
go Maybe Word64
forall a. Maybe a
Nothing []
where
go :: Maybe Word64 -> [TlvRecord] -> BS.ByteString
-> Either TlvError TlvStream
go :: Maybe Word64
-> [TlvRecord] -> ByteString -> Either TlvError TlvStream
go !Maybe Word64
_ ![TlvRecord]
acc !ByteString
bs
| ByteString -> Bool
BS.null ByteString
bs = TlvStream -> Either TlvError TlvStream
forall a b. b -> Either a b
Right ([TlvRecord] -> TlvStream
unsafeTlvStream ([TlvRecord] -> [TlvRecord]
forall a. [a] -> [a]
reverse [TlvRecord]
acc))
go !Maybe Word64
mPrevType ![TlvRecord]
acc !ByteString
bs = do
(typ, rest1) <- Either TlvError (Word64, ByteString)
-> ((Word64, ByteString) -> Either TlvError (Word64, ByteString))
-> Maybe (Word64, ByteString)
-> Either TlvError (Word64, ByteString)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (TlvError -> Either TlvError (Word64, ByteString)
forall a b. a -> Either a b
Left TlvError
TlvNonMinimalEncoding) (Word64, ByteString) -> Either TlvError (Word64, ByteString)
forall a b. b -> Either a b
Right
(ByteString -> Maybe (Word64, ByteString)
decodeBigSize ByteString
bs)
case mPrevType of
Just Word64
prevType -> Bool -> Either TlvError () -> Either TlvError ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Word64
typ Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
prevType) (Either TlvError () -> Either TlvError ())
-> Either TlvError () -> Either TlvError ()
forall a b. (a -> b) -> a -> b
$
TlvError -> Either TlvError ()
forall a b. a -> Either a b
Left TlvError
TlvNotStrictlyIncreasing
Maybe Word64
Nothing -> () -> Either TlvError ()
forall a. a -> Either TlvError a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
(len, rest2) <- maybe (Left TlvNonMinimalEncoding) Right
(decodeBigSize rest1)
when (fromIntegral len > BS.length rest2) $
Left TlvLengthExceedsBounds
let !val = Int -> ByteString -> ByteString
BS.take (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
len) ByteString
rest2
!rest3 = Int -> ByteString -> ByteString
BS.drop (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
len) ByteString
rest2
!rec = Word64 -> ByteString -> TlvRecord
TlvRecord Word64
typ ByteString
val
if isKnown typ
then go (Just typ) (rec : acc) rest3
else if even typ
then Left (TlvUnknownEvenType typ)
else go (Just typ) acc rest3
decodeTlvStream :: BS.ByteString -> Either TlvError TlvStream
decodeTlvStream :: ByteString -> Either TlvError TlvStream
decodeTlvStream = (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
decodeTlvStreamWith Word64 -> Bool
isInitTlvType
where
isInitTlvType :: Word64 -> Bool
isInitTlvType :: Word64 -> Bool
isInitTlvType Word64
1 = Bool
True
isInitTlvType Word64
3 = Bool
True
isInitTlvType Word64
_ = Bool
False
data InitTlv
= InitNetworks ![ChainHash]
| InitRemoteAddr !BS.ByteString
deriving stock (InitTlv -> InitTlv -> Bool
(InitTlv -> InitTlv -> Bool)
-> (InitTlv -> InitTlv -> Bool) -> Eq InitTlv
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: InitTlv -> InitTlv -> Bool
== :: InitTlv -> InitTlv -> Bool
$c/= :: InitTlv -> InitTlv -> Bool
/= :: InitTlv -> InitTlv -> Bool
Eq, Int -> InitTlv -> ShowS
[InitTlv] -> ShowS
InitTlv -> String
(Int -> InitTlv -> ShowS)
-> (InitTlv -> String) -> ([InitTlv] -> ShowS) -> Show InitTlv
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> InitTlv -> ShowS
showsPrec :: Int -> InitTlv -> ShowS
$cshow :: InitTlv -> String
show :: InitTlv -> String
$cshowList :: [InitTlv] -> ShowS
showList :: [InitTlv] -> ShowS
Show, (forall x. InitTlv -> Rep InitTlv x)
-> (forall x. Rep InitTlv x -> InitTlv) -> Generic InitTlv
forall x. Rep InitTlv x -> InitTlv
forall x. InitTlv -> Rep InitTlv x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. InitTlv -> Rep InitTlv x
from :: forall x. InitTlv -> Rep InitTlv x
$cto :: forall x. Rep InitTlv x -> InitTlv
to :: forall x. Rep InitTlv x -> InitTlv
Generic)
instance NFData InitTlv
parseInitTlvs :: TlvStream -> Either TlvError [InitTlv]
parseInitTlvs :: TlvStream -> Either TlvError [InitTlv]
parseInitTlvs (TlvStream [TlvRecord]
recs) = (TlvRecord -> Either TlvError InitTlv)
-> [TlvRecord] -> Either TlvError [InitTlv]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> [a] -> f [b]
traverse TlvRecord -> Either TlvError InitTlv
parseOne [TlvRecord]
recs
where
parseOne :: TlvRecord -> Either TlvError InitTlv
parseOne (TlvRecord Word64
1 ByteString
val)
| ByteString -> Int
BS.length ByteString
val Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
32 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 =
InitTlv -> Either TlvError InitTlv
forall a b. b -> Either a b
Right ([ChainHash] -> InitTlv
InitNetworks ((ByteString -> ChainHash) -> [ByteString] -> [ChainHash]
forall a b. (a -> b) -> [a] -> [b]
map ByteString -> ChainHash
mkChainHash (Int -> ByteString -> [ByteString]
chunksOf Int
32 ByteString
val)))
| Bool
otherwise = TlvError -> Either TlvError InitTlv
forall a b. a -> Either a b
Left (Word64 -> TlvError
TlvInvalidKnownType Word64
1)
parseOne (TlvRecord Word64
3 ByteString
val) = InitTlv -> Either TlvError InitTlv
forall a b. b -> Either a b
Right (ByteString -> InitTlv
InitRemoteAddr ByteString
val)
parseOne (TlvRecord Word64
t ByteString
_) = TlvError -> Either TlvError InitTlv
forall a b. a -> Either a b
Left (Word64 -> TlvError
TlvUnknownEvenType Word64
t)
mkChainHash :: ByteString -> ChainHash
mkChainHash ByteString
bs = case ByteString -> Maybe ChainHash
chainHash ByteString
bs of
Just ChainHash
ch -> ChainHash
ch
Maybe ChainHash
Nothing -> String -> ChainHash
forall a. HasCallStack => String -> a
error String
"parseInitTlvs: impossible - chunk is not 32 bytes"
chunksOf :: Int -> BS.ByteString -> [BS.ByteString]
chunksOf :: Int -> ByteString -> [ByteString]
chunksOf !Int
n !ByteString
bs
| ByteString -> Bool
BS.null ByteString
bs = []
| Bool
otherwise =
let (!ByteString
chunk, !ByteString
rest) = Int -> ByteString -> (ByteString, ByteString)
BS.splitAt Int
n ByteString
bs
in ByteString
chunk ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
: Int -> ByteString -> [ByteString]
chunksOf Int
n ByteString
rest
encodeInitTlvs :: [InitTlv] -> TlvStream
encodeInitTlvs :: [InitTlv] -> TlvStream
encodeInitTlvs = [TlvRecord] -> TlvStream
unsafeTlvStream ([TlvRecord] -> TlvStream)
-> ([InitTlv] -> [TlvRecord]) -> [InitTlv] -> TlvStream
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (InitTlv -> TlvRecord) -> [InitTlv] -> [TlvRecord]
forall a b. (a -> b) -> [a] -> [b]
map InitTlv -> TlvRecord
toRecord
where
toRecord :: InitTlv -> TlvRecord
toRecord (InitNetworks [ChainHash]
chains) =
Word64 -> ByteString -> TlvRecord
TlvRecord Word64
1 ([ByteString] -> ByteString
forall a. Monoid a => [a] -> a
mconcat ((ChainHash -> ByteString) -> [ChainHash] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map ChainHash -> ByteString
unChainHash [ChainHash]
chains))
toRecord (InitRemoteAddr ByteString
addr) =
Word64 -> ByteString -> TlvRecord
TlvRecord Word64
3 ByteString
addr