ppad-bolt1 0.0.1 → 0.1.0
raw patch · 11 files changed
+2001/−3215 lines, 11 filesdep +ppad-bolt9dep ~deepseqPVP ok
version bump matches the API change (PVP)
Dependencies added: ppad-bolt9
Dependency ranges changed: deepseq
API changes (from Hackage documentation)
- Lightning.Protocol.BOLT1: DecodeInvalidChannelId :: DecodeError
- Lightning.Protocol.BOLT1: DecodeInvalidExtension :: !TlvError -> DecodeError
- Lightning.Protocol.BOLT1: DecodeInvalidLength :: DecodeError
- Lightning.Protocol.BOLT1: Envelope :: !MsgType -> !ByteString -> !Maybe TlvStream -> Envelope
- Lightning.Protocol.BOLT1: InitNetworks :: ![ChainHash] -> InitTlv
- Lightning.Protocol.BOLT1: InitRemoteAddr :: !ByteString -> InitTlv
- Lightning.Protocol.BOLT1: MsgErrorVal :: !Error -> Message
- Lightning.Protocol.BOLT1: MsgInitVal :: !Init -> Message
- Lightning.Protocol.BOLT1: MsgPeerStorageRet :: MsgType
- Lightning.Protocol.BOLT1: MsgPeerStorageRetrievalVal :: !PeerStorageRetrieval -> Message
- Lightning.Protocol.BOLT1: MsgPeerStorageVal :: !PeerStorage -> Message
- Lightning.Protocol.BOLT1: MsgPingVal :: !Ping -> Message
- Lightning.Protocol.BOLT1: MsgPongVal :: !Pong -> Message
- Lightning.Protocol.BOLT1: MsgUnknown :: !Word16 -> MsgType
- Lightning.Protocol.BOLT1: MsgWarningVal :: !Warning -> Message
- Lightning.Protocol.BOLT1: TlvInvalidKnownType :: !Word64 -> TlvError
- Lightning.Protocol.BOLT1: TlvLengthExceedsBounds :: TlvError
- Lightning.Protocol.BOLT1: TlvNonMinimalEncoding :: TlvError
- Lightning.Protocol.BOLT1: [envExtension] :: Envelope -> !Maybe TlvStream
- Lightning.Protocol.BOLT1: [envPayload] :: Envelope -> !ByteString
- Lightning.Protocol.BOLT1: [envType] :: Envelope -> !MsgType
- Lightning.Protocol.BOLT1: [errorChannelId] :: Error -> !ChannelId
- Lightning.Protocol.BOLT1: [errorData] :: Error -> !ByteString
- Lightning.Protocol.BOLT1: [initFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1: [initGlobalFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1: [initTlvs] :: Init -> ![InitTlv]
- Lightning.Protocol.BOLT1: [peerStorageBlob] :: PeerStorage -> !ByteString
- Lightning.Protocol.BOLT1: [peerStorageRetrievalBlob] :: PeerStorageRetrieval -> !ByteString
- Lightning.Protocol.BOLT1: [pingIgnored] :: Ping -> !ByteString
- Lightning.Protocol.BOLT1: [pingNumPongBytes] :: Ping -> {-# UNPACK #-} !Word16
- Lightning.Protocol.BOLT1: [pongIgnored] :: Pong -> !ByteString
- Lightning.Protocol.BOLT1: [tlvType] :: TlvRecord -> {-# UNPACK #-} !Word64
- Lightning.Protocol.BOLT1: [tlvValue] :: TlvRecord -> !ByteString
- Lightning.Protocol.BOLT1: [warningChannelId] :: Warning -> !ChannelId
- Lightning.Protocol.BOLT1: [warningData] :: Warning -> !ByteString
- Lightning.Protocol.BOLT1: allChannels :: ChannelId
- Lightning.Protocol.BOLT1: chainHash :: ByteString -> Maybe ChainHash
- Lightning.Protocol.BOLT1: channelId :: ByteString -> Maybe ChannelId
- Lightning.Protocol.BOLT1: data Envelope
- Lightning.Protocol.BOLT1: data InitTlv
- Lightning.Protocol.BOLT1: data MsgType
- Lightning.Protocol.BOLT1: decodeBigSize :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1: decodeEnvelope :: ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1: decodeEnvelopeWith :: (Word64 -> Bool) -> ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1: decodeMessage :: MsgType -> ByteString -> Either DecodeError (Message, ByteString)
- Lightning.Protocol.BOLT1: decodeMinSigned :: Int -> ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1: decodeS16 :: ByteString -> Maybe (Int16, ByteString)
- Lightning.Protocol.BOLT1: decodeS32 :: ByteString -> Maybe (Int32, ByteString)
- Lightning.Protocol.BOLT1: decodeS64 :: ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1: decodeS8 :: ByteString -> Maybe (Int8, ByteString)
- Lightning.Protocol.BOLT1: decodeTlvStream :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1: decodeTlvStreamRaw :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1: decodeTlvStreamWith :: (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1: decodeTu16 :: Int -> ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1: decodeTu32 :: Int -> ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1: decodeTu64 :: Int -> ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1: decodeU16 :: ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1: decodeU32 :: ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1: decodeU64 :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1: encodeBigSize :: Word64 -> ByteString
- Lightning.Protocol.BOLT1: encodeEnvelope :: Message -> Maybe TlvStream -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1: encodeMessage :: Message -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1: encodeMinSigned :: Int64 -> ByteString
- Lightning.Protocol.BOLT1: encodeS16 :: Int16 -> ByteString
- Lightning.Protocol.BOLT1: encodeS32 :: Int32 -> ByteString
- Lightning.Protocol.BOLT1: encodeS64 :: Int64 -> ByteString
- Lightning.Protocol.BOLT1: encodeS8 :: Int8 -> ByteString
- Lightning.Protocol.BOLT1: encodeTlvStream :: TlvStream -> ByteString
- Lightning.Protocol.BOLT1: encodeTu16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1: encodeTu32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1: encodeTu64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1: encodeU16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1: encodeU32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1: encodeU64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1: msgTypeWord :: MsgType -> Word16
- Lightning.Protocol.BOLT1: tlvStream :: [TlvRecord] -> Maybe TlvStream
- Lightning.Protocol.BOLT1: unChainHash :: ChainHash -> ByteString
- Lightning.Protocol.BOLT1: unsafeTlvStream :: [TlvRecord] -> TlvStream
- Lightning.Protocol.BOLT1.Codec: DecodeInsufficientBytes :: DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeInvalidChannelId :: DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeInvalidExtension :: !TlvError -> DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeInvalidLength :: DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeTlvError :: !TlvError -> DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeUnknownEvenType :: !Word16 -> DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeUnknownOddType :: !Word16 -> DecodeError
- Lightning.Protocol.BOLT1.Codec: EncodeLengthOverflow :: EncodeError
- Lightning.Protocol.BOLT1.Codec: EncodeMessageTooLarge :: EncodeError
- Lightning.Protocol.BOLT1.Codec: data DecodeError
- Lightning.Protocol.BOLT1.Codec: data EncodeError
- Lightning.Protocol.BOLT1.Codec: decodeEnvelope :: ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1.Codec: decodeEnvelopeWith :: (Word64 -> Bool) -> ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1.Codec: decodeError :: ByteString -> Either DecodeError (Error, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodeInit :: ByteString -> Either DecodeError (Init, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodeMessage :: MsgType -> ByteString -> Either DecodeError (Message, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePeerStorage :: ByteString -> Either DecodeError (PeerStorage, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePeerStorageRetrieval :: ByteString -> Either DecodeError (PeerStorageRetrieval, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePing :: ByteString -> Either DecodeError (Ping, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePong :: ByteString -> Either DecodeError (Pong, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodeWarning :: ByteString -> Either DecodeError (Warning, ByteString)
- Lightning.Protocol.BOLT1.Codec: encodeEnvelope :: Message -> Maybe TlvStream -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeError :: Error -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeInit :: Init -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeMessage :: Message -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePeerStorage :: PeerStorage -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePeerStorageRetrieval :: PeerStorageRetrieval -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePing :: Ping -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePong :: Pong -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeWarning :: Warning -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Show.Show Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Show.Show Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Message: Envelope :: !MsgType -> !ByteString -> !Maybe TlvStream -> Envelope
- Lightning.Protocol.BOLT1.Message: Error :: !ChannelId -> !ByteString -> Error
- Lightning.Protocol.BOLT1.Message: Init :: !ByteString -> !ByteString -> ![InitTlv] -> Init
- Lightning.Protocol.BOLT1.Message: MsgError :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgErrorVal :: !Error -> Message
- Lightning.Protocol.BOLT1.Message: MsgInit :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgInitVal :: !Init -> Message
- Lightning.Protocol.BOLT1.Message: MsgPeerStorage :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPeerStorageRet :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPeerStorageRetrievalVal :: !PeerStorageRetrieval -> Message
- Lightning.Protocol.BOLT1.Message: MsgPeerStorageVal :: !PeerStorage -> Message
- Lightning.Protocol.BOLT1.Message: MsgPing :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPingVal :: !Ping -> Message
- Lightning.Protocol.BOLT1.Message: MsgPong :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPongVal :: !Pong -> Message
- Lightning.Protocol.BOLT1.Message: MsgUnknown :: !Word16 -> MsgType
- Lightning.Protocol.BOLT1.Message: MsgWarning :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgWarningVal :: !Warning -> Message
- Lightning.Protocol.BOLT1.Message: PeerStorage :: !ByteString -> PeerStorage
- Lightning.Protocol.BOLT1.Message: PeerStorageRetrieval :: !ByteString -> PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: Ping :: {-# UNPACK #-} !Word16 -> !ByteString -> Ping
- Lightning.Protocol.BOLT1.Message: Pong :: !ByteString -> Pong
- Lightning.Protocol.BOLT1.Message: Warning :: !ChannelId -> !ByteString -> Warning
- Lightning.Protocol.BOLT1.Message: [envExtension] :: Envelope -> !Maybe TlvStream
- Lightning.Protocol.BOLT1.Message: [envPayload] :: Envelope -> !ByteString
- Lightning.Protocol.BOLT1.Message: [envType] :: Envelope -> !MsgType
- Lightning.Protocol.BOLT1.Message: [errorChannelId] :: Error -> !ChannelId
- Lightning.Protocol.BOLT1.Message: [errorData] :: Error -> !ByteString
- Lightning.Protocol.BOLT1.Message: [initFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1.Message: [initGlobalFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1.Message: [initTlvs] :: Init -> ![InitTlv]
- Lightning.Protocol.BOLT1.Message: [peerStorageBlob] :: PeerStorage -> !ByteString
- Lightning.Protocol.BOLT1.Message: [peerStorageRetrievalBlob] :: PeerStorageRetrieval -> !ByteString
- Lightning.Protocol.BOLT1.Message: [pingIgnored] :: Ping -> !ByteString
- Lightning.Protocol.BOLT1.Message: [pingNumPongBytes] :: Ping -> {-# UNPACK #-} !Word16
- Lightning.Protocol.BOLT1.Message: [pongIgnored] :: Pong -> !ByteString
- Lightning.Protocol.BOLT1.Message: [warningChannelId] :: Warning -> !ChannelId
- Lightning.Protocol.BOLT1.Message: [warningData] :: Warning -> !ByteString
- Lightning.Protocol.BOLT1.Message: allChannels :: ChannelId
- Lightning.Protocol.BOLT1.Message: channelId :: ByteString -> Maybe ChannelId
- Lightning.Protocol.BOLT1.Message: data ChannelId
- Lightning.Protocol.BOLT1.Message: data Envelope
- Lightning.Protocol.BOLT1.Message: data Error
- Lightning.Protocol.BOLT1.Message: data Init
- Lightning.Protocol.BOLT1.Message: data Message
- Lightning.Protocol.BOLT1.Message: data MsgType
- Lightning.Protocol.BOLT1.Message: data PeerStorage
- Lightning.Protocol.BOLT1.Message: data PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: data Ping
- Lightning.Protocol.BOLT1.Message: data Pong
- Lightning.Protocol.BOLT1.Message: data Warning
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: messageType :: Message -> MsgType
- Lightning.Protocol.BOLT1.Message: msgTypeWord :: MsgType -> Word16
- Lightning.Protocol.BOLT1.Message: parseMsgType :: Word16 -> MsgType
- Lightning.Protocol.BOLT1.Message: unChannelId :: ChannelId -> ByteString
- Lightning.Protocol.BOLT1.Prim: chainHash :: ByteString -> Maybe ChainHash
- Lightning.Protocol.BOLT1.Prim: data ChainHash
- Lightning.Protocol.BOLT1.Prim: decodeBigSize :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeMinSigned :: Int -> ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS16 :: ByteString -> Maybe (Int16, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS32 :: ByteString -> Maybe (Int32, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS64 :: ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS8 :: ByteString -> Maybe (Int8, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeTu16 :: Int -> ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeTu32 :: Int -> ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeTu64 :: Int -> ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeU16 :: ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeU32 :: ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeU64 :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1.Prim: encodeBigSize :: Word64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeLength :: ByteString -> Maybe ByteString
- Lightning.Protocol.BOLT1.Prim: encodeMinSigned :: Int64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS16 :: Int16 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS32 :: Int32 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS64 :: Int64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS8 :: Int8 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeTu16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeTu32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeTu64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeU16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeU32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeU64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: instance GHC.Show.Show Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: unChainHash :: ChainHash -> ByteString
- Lightning.Protocol.BOLT1.TLV: InitNetworks :: ![ChainHash] -> InitTlv
- Lightning.Protocol.BOLT1.TLV: InitRemoteAddr :: !ByteString -> InitTlv
- Lightning.Protocol.BOLT1.TLV: TlvInvalidKnownType :: !Word64 -> TlvError
- Lightning.Protocol.BOLT1.TLV: TlvLengthExceedsBounds :: TlvError
- Lightning.Protocol.BOLT1.TLV: TlvNonMinimalEncoding :: TlvError
- Lightning.Protocol.BOLT1.TLV: TlvNotStrictlyIncreasing :: TlvError
- Lightning.Protocol.BOLT1.TLV: TlvRecord :: {-# UNPACK #-} !Word64 -> !ByteString -> TlvRecord
- Lightning.Protocol.BOLT1.TLV: TlvUnknownEvenType :: !Word64 -> TlvError
- Lightning.Protocol.BOLT1.TLV: [tlvType] :: TlvRecord -> {-# UNPACK #-} !Word64
- Lightning.Protocol.BOLT1.TLV: [tlvValue] :: TlvRecord -> !ByteString
- Lightning.Protocol.BOLT1.TLV: chainHash :: ByteString -> Maybe ChainHash
- Lightning.Protocol.BOLT1.TLV: data ChainHash
- Lightning.Protocol.BOLT1.TLV: data InitTlv
- Lightning.Protocol.BOLT1.TLV: data TlvError
- Lightning.Protocol.BOLT1.TLV: data TlvRecord
- Lightning.Protocol.BOLT1.TLV: data TlvStream
- Lightning.Protocol.BOLT1.TLV: decodeTlvStream :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1.TLV: decodeTlvStreamRaw :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1.TLV: decodeTlvStreamWith :: (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1.TLV: encodeInitTlvs :: [InitTlv] -> TlvStream
- Lightning.Protocol.BOLT1.TLV: encodeTlvRecord :: TlvRecord -> ByteString
- Lightning.Protocol.BOLT1.TLV: encodeTlvStream :: TlvStream -> ByteString
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: parseInitTlvs :: TlvStream -> Either TlvError [InitTlv]
- Lightning.Protocol.BOLT1.TLV: tlvStream :: [TlvRecord] -> Maybe TlvStream
- Lightning.Protocol.BOLT1.TLV: unChainHash :: ChainHash -> ByteString
- Lightning.Protocol.BOLT1.TLV: unsafeTlvStream :: [TlvRecord] -> TlvStream
+ Lightning.Protocol.BOLT1: DecodeInvalidTlvValue :: !Word64 -> DecodeError
+ Lightning.Protocol.BOLT1: EncodeInvalidTlvs :: EncodeError
+ Lightning.Protocol.BOLT1: MsgPeerStorageRetrieval :: !PeerStorageRetrieval -> Message
+ Lightning.Protocol.BOLT1: ShortChannelId :: Word64 -> ShortChannelId
+ Lightning.Protocol.BOLT1: TlvNonMinimalBigSize :: TlvError
+ Lightning.Protocol.BOLT1: TlvTruncated :: TlvError
+ Lightning.Protocol.BOLT1: [error_channel_id] :: Error -> !ChannelId
+ Lightning.Protocol.BOLT1: [error_data] :: Error -> !ByteString
+ Lightning.Protocol.BOLT1: [error_tlvs] :: Error -> !TlvStream
+ Lightning.Protocol.BOLT1: [init_features] :: Init -> !FeatureVector
+ Lightning.Protocol.BOLT1: [init_global_features] :: Init -> !FeatureVector
+ Lightning.Protocol.BOLT1: [init_networks] :: Init -> !Maybe [ChainHash]
+ Lightning.Protocol.BOLT1: [init_remote_addr] :: Init -> !Maybe ByteString
+ Lightning.Protocol.BOLT1: [init_tlvs] :: Init -> !TlvStream
+ Lightning.Protocol.BOLT1: [peer_storage_blob] :: PeerStorage -> !ByteString
+ Lightning.Protocol.BOLT1: [peer_storage_retrieval_blob] :: PeerStorageRetrieval -> !ByteString
+ Lightning.Protocol.BOLT1: [peer_storage_retrieval_tlvs] :: PeerStorageRetrieval -> !TlvStream
+ Lightning.Protocol.BOLT1: [peer_storage_tlvs] :: PeerStorage -> !TlvStream
+ Lightning.Protocol.BOLT1: [ping_ignored] :: Ping -> !ByteString
+ Lightning.Protocol.BOLT1: [ping_num_pong_bytes] :: Ping -> {-# UNPACK #-} !Word16
+ Lightning.Protocol.BOLT1: [ping_tlvs] :: Ping -> !TlvStream
+ Lightning.Protocol.BOLT1: [pong_ignored] :: Pong -> !ByteString
+ Lightning.Protocol.BOLT1: [pong_tlvs] :: Pong -> !TlvStream
+ Lightning.Protocol.BOLT1: [tlv_type] :: TlvRecord -> {-# UNPACK #-} !Word64
+ Lightning.Protocol.BOLT1: [tlv_value] :: TlvRecord -> !ByteString
+ Lightning.Protocol.BOLT1: [warning_channel_id] :: Warning -> !ChannelId
+ Lightning.Protocol.BOLT1: [warning_data] :: Warning -> !ByteString
+ Lightning.Protocol.BOLT1: [warning_tlvs] :: Warning -> !TlvStream
+ Lightning.Protocol.BOLT1: add_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi
+ Lightning.Protocol.BOLT1: add_sat :: Satoshi -> Satoshi -> Maybe Satoshi
+ Lightning.Protocol.BOLT1: all_channels :: ChannelId
+ Lightning.Protocol.BOLT1: chain_hash :: ByteString -> Maybe ChainHash
+ Lightning.Protocol.BOLT1: channel_id :: ByteString -> Maybe ChannelId
+ Lightning.Protocol.BOLT1: data MilliSatoshi
+ Lightning.Protocol.BOLT1: data PaymentHash
+ Lightning.Protocol.BOLT1: data PaymentPreimage
+ Lightning.Protocol.BOLT1: data PerCommitmentSecret
+ Lightning.Protocol.BOLT1: data Point
+ Lightning.Protocol.BOLT1: data Satoshi
+ Lightning.Protocol.BOLT1: data Signature
+ Lightning.Protocol.BOLT1: decode_bigsize :: ByteString -> Maybe (Word64, ByteString)
+ Lightning.Protocol.BOLT1: decode_chain_hash :: ByteString -> Maybe (ChainHash, ByteString)
+ Lightning.Protocol.BOLT1: decode_channel_id :: ByteString -> Maybe (ChannelId, ByteString)
+ Lightning.Protocol.BOLT1: decode_envelope :: ByteString -> Either DecodeError (Word16, ByteString)
+ Lightning.Protocol.BOLT1: decode_error :: ByteString -> Either DecodeError Error
+ Lightning.Protocol.BOLT1: decode_init :: ByteString -> Either DecodeError Init
+ Lightning.Protocol.BOLT1: decode_message :: ByteString -> Either DecodeError Message
+ Lightning.Protocol.BOLT1: decode_milli_satoshi :: ByteString -> Maybe (MilliSatoshi, ByteString)
+ Lightning.Protocol.BOLT1: decode_payment_hash :: ByteString -> Maybe (PaymentHash, ByteString)
+ Lightning.Protocol.BOLT1: decode_payment_preimage :: ByteString -> Maybe (PaymentPreimage, ByteString)
+ Lightning.Protocol.BOLT1: decode_peer_storage :: ByteString -> Either DecodeError PeerStorage
+ Lightning.Protocol.BOLT1: decode_peer_storage_retrieval :: ByteString -> Either DecodeError PeerStorageRetrieval
+ Lightning.Protocol.BOLT1: decode_per_commitment_secret :: ByteString -> Maybe (PerCommitmentSecret, ByteString)
+ Lightning.Protocol.BOLT1: decode_ping :: ByteString -> Either DecodeError Ping
+ Lightning.Protocol.BOLT1: decode_point :: ByteString -> Maybe (Point, ByteString)
+ Lightning.Protocol.BOLT1: decode_pong :: ByteString -> Either DecodeError Pong
+ Lightning.Protocol.BOLT1: decode_s16 :: ByteString -> Maybe (Int16, ByteString)
+ Lightning.Protocol.BOLT1: decode_s32 :: ByteString -> Maybe (Int32, ByteString)
+ Lightning.Protocol.BOLT1: decode_s64 :: ByteString -> Maybe (Int64, ByteString)
+ Lightning.Protocol.BOLT1: decode_s8 :: ByteString -> Maybe (Int8, ByteString)
+ Lightning.Protocol.BOLT1: decode_satoshi :: ByteString -> Maybe (Satoshi, ByteString)
+ Lightning.Protocol.BOLT1: decode_short_channel_id :: ByteString -> Maybe (ShortChannelId, ByteString)
+ Lightning.Protocol.BOLT1: decode_signature :: ByteString -> Maybe (Signature, ByteString)
+ Lightning.Protocol.BOLT1: decode_tlv_stream :: (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
+ Lightning.Protocol.BOLT1: decode_tu16 :: ByteString -> Maybe Word16
+ Lightning.Protocol.BOLT1: decode_tu32 :: ByteString -> Maybe Word32
+ Lightning.Protocol.BOLT1: decode_tu64 :: ByteString -> Maybe Word64
+ Lightning.Protocol.BOLT1: decode_u16 :: ByteString -> Maybe (Word16, ByteString)
+ Lightning.Protocol.BOLT1: decode_u16_prefixed :: ByteString -> Maybe (ByteString, ByteString)
+ Lightning.Protocol.BOLT1: decode_u32 :: ByteString -> Maybe (Word32, ByteString)
+ Lightning.Protocol.BOLT1: decode_u64 :: ByteString -> Maybe (Word64, ByteString)
+ Lightning.Protocol.BOLT1: decode_warning :: ByteString -> Either DecodeError Warning
+ Lightning.Protocol.BOLT1: empty_tlv_stream :: TlvStream
+ Lightning.Protocol.BOLT1: encode_bigsize :: Word64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_envelope :: Word16 -> ByteString -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_error :: Error -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_init :: Init -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_message :: Message -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_milli_satoshi :: MilliSatoshi -> ByteString
+ Lightning.Protocol.BOLT1: encode_peer_storage :: PeerStorage -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_peer_storage_retrieval :: PeerStorageRetrieval -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_ping :: Ping -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_pong :: Pong -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_s16 :: Int16 -> ByteString
+ Lightning.Protocol.BOLT1: encode_s32 :: Int32 -> ByteString
+ Lightning.Protocol.BOLT1: encode_s64 :: Int64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_s8 :: Int8 -> ByteString
+ Lightning.Protocol.BOLT1: encode_satoshi :: Satoshi -> ByteString
+ Lightning.Protocol.BOLT1: encode_short_channel_id :: ShortChannelId -> ByteString
+ Lightning.Protocol.BOLT1: encode_tlv_stream :: TlvStream -> ByteString
+ Lightning.Protocol.BOLT1: encode_tu16 :: Word16 -> ByteString
+ Lightning.Protocol.BOLT1: encode_tu32 :: Word32 -> ByteString
+ Lightning.Protocol.BOLT1: encode_tu64 :: Word64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_u16 :: Word16 -> ByteString
+ Lightning.Protocol.BOLT1: encode_u16_prefixed :: ByteString -> Maybe ByteString
+ Lightning.Protocol.BOLT1: encode_u32 :: Word32 -> ByteString
+ Lightning.Protocol.BOLT1: encode_u64 :: Word64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_warning :: Warning -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: filter_tlv_stream :: (Word64 -> Bool) -> TlvStream -> TlvStream
+ Lightning.Protocol.BOLT1: lookup_tlv :: Word64 -> TlvStream -> Maybe ByteString
+ Lightning.Protocol.BOLT1: max_milli_satoshi :: MilliSatoshi
+ Lightning.Protocol.BOLT1: max_satoshi :: Satoshi
+ Lightning.Protocol.BOLT1: message_type :: Message -> Word16
+ Lightning.Protocol.BOLT1: milli_satoshi :: Word64 -> Maybe MilliSatoshi
+ Lightning.Protocol.BOLT1: msat_to_sat :: MilliSatoshi -> Satoshi
+ Lightning.Protocol.BOLT1: newtype ShortChannelId
+ Lightning.Protocol.BOLT1: payment_hash :: ByteString -> Maybe PaymentHash
+ Lightning.Protocol.BOLT1: payment_preimage :: ByteString -> Maybe PaymentPreimage
+ Lightning.Protocol.BOLT1: per_commitment_secret :: ByteString -> Maybe PerCommitmentSecret
+ Lightning.Protocol.BOLT1: ping_response :: Ping -> Maybe Pong
+ Lightning.Protocol.BOLT1: point :: ByteString -> Maybe Point
+ Lightning.Protocol.BOLT1: sat_to_msat :: Satoshi -> MilliSatoshi
+ Lightning.Protocol.BOLT1: satoshi :: Word64 -> Maybe Satoshi
+ Lightning.Protocol.BOLT1: scid_block_height :: ShortChannelId -> Word32
+ Lightning.Protocol.BOLT1: scid_output_index :: ShortChannelId -> Word16
+ Lightning.Protocol.BOLT1: scid_tx_index :: ShortChannelId -> Word32
+ Lightning.Protocol.BOLT1: short_channel_id :: Word32 -> Word32 -> Word16 -> Maybe ShortChannelId
+ Lightning.Protocol.BOLT1: signature :: ByteString -> Maybe Signature
+ Lightning.Protocol.BOLT1: sub_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi
+ Lightning.Protocol.BOLT1: sub_sat :: Satoshi -> Satoshi -> Maybe Satoshi
+ Lightning.Protocol.BOLT1: tlv_stream :: [TlvRecord] -> Maybe TlvStream
+ Lightning.Protocol.BOLT1: un_chain_hash :: ChainHash -> ByteString
+ Lightning.Protocol.BOLT1: un_channel_id :: ChannelId -> ByteString
+ Lightning.Protocol.BOLT1: un_milli_satoshi :: MilliSatoshi -> Word64
+ Lightning.Protocol.BOLT1: un_payment_hash :: PaymentHash -> ByteString
+ Lightning.Protocol.BOLT1: un_payment_preimage :: PaymentPreimage -> ByteString
+ Lightning.Protocol.BOLT1: un_per_commitment_secret :: PerCommitmentSecret -> ByteString
+ Lightning.Protocol.BOLT1: un_point :: Point -> ByteString
+ Lightning.Protocol.BOLT1: un_satoshi :: Satoshi -> Word64
+ Lightning.Protocol.BOLT1: un_signature :: Signature -> ByteString
+ Lightning.Protocol.BOLT1: un_tlv_stream :: TlvStream -> [TlvRecord]
- Lightning.Protocol.BOLT1: Error :: !ChannelId -> !ByteString -> Error
+ Lightning.Protocol.BOLT1: Error :: !ChannelId -> !ByteString -> !TlvStream -> Error
- Lightning.Protocol.BOLT1: Init :: !ByteString -> !ByteString -> ![InitTlv] -> Init
+ Lightning.Protocol.BOLT1: Init :: !FeatureVector -> !FeatureVector -> !Maybe [ChainHash] -> !Maybe ByteString -> !TlvStream -> Init
- Lightning.Protocol.BOLT1: MsgError :: MsgType
+ Lightning.Protocol.BOLT1: MsgError :: !Error -> Message
- Lightning.Protocol.BOLT1: MsgInit :: MsgType
+ Lightning.Protocol.BOLT1: MsgInit :: !Init -> Message
- Lightning.Protocol.BOLT1: MsgPeerStorage :: MsgType
+ Lightning.Protocol.BOLT1: MsgPeerStorage :: !PeerStorage -> Message
- Lightning.Protocol.BOLT1: MsgPing :: MsgType
+ Lightning.Protocol.BOLT1: MsgPing :: !Ping -> Message
- Lightning.Protocol.BOLT1: MsgPong :: MsgType
+ Lightning.Protocol.BOLT1: MsgPong :: !Pong -> Message
- Lightning.Protocol.BOLT1: MsgWarning :: MsgType
+ Lightning.Protocol.BOLT1: MsgWarning :: !Warning -> Message
- Lightning.Protocol.BOLT1: PeerStorage :: !ByteString -> PeerStorage
+ Lightning.Protocol.BOLT1: PeerStorage :: !ByteString -> !TlvStream -> PeerStorage
- Lightning.Protocol.BOLT1: PeerStorageRetrieval :: !ByteString -> PeerStorageRetrieval
+ Lightning.Protocol.BOLT1: PeerStorageRetrieval :: !ByteString -> !TlvStream -> PeerStorageRetrieval
- Lightning.Protocol.BOLT1: Ping :: {-# UNPACK #-} !Word16 -> !ByteString -> Ping
+ Lightning.Protocol.BOLT1: Ping :: {-# UNPACK #-} !Word16 -> !ByteString -> !TlvStream -> Ping
- Lightning.Protocol.BOLT1: Pong :: !ByteString -> Pong
+ Lightning.Protocol.BOLT1: Pong :: !ByteString -> !TlvStream -> Pong
- Lightning.Protocol.BOLT1: Warning :: !ChannelId -> !ByteString -> Warning
+ Lightning.Protocol.BOLT1: Warning :: !ChannelId -> !ByteString -> !TlvStream -> Warning
Files
- CHANGELOG +31/−1
- bench/Fixtures.hs +34/−417
- bench/Main.hs +24/−517
- bench/Weight.hs +19/−338
- lib/Lightning/Protocol/BOLT1.hs +126/−70
- lib/Lightning/Protocol/BOLT1/Codec.hs +232/−297
- lib/Lightning/Protocol/BOLT1/Message.hs +74/−155
- lib/Lightning/Protocol/BOLT1/Prim.hs +804/−527
- lib/Lightning/Protocol/BOLT1/TLV.hs +107/−195
- ppad-bolt1.cabal +8/−5
- test/Main.hs +542/−693
CHANGELOG view
@@ -1,4 +1,34 @@ # Changelog -- 0.0.1 (2025-01-25)+- 0.1.0 (2026-10-10)+ * A substantial rewrite, with breaking changes throughout:++ * The API is exported from Lightning.Protocol.BOLT1 alone, and uses+ snake_case names (e.g. encode_message, channel_id, un_channel_id).++ * Fundamental types are abstract, constructed only via validating+ smart constructors. Points must carry a compressed-encoding+ prefix. Satoshi and MilliSatoshi are bounded at 21M BTC and+ provide checked arithmetic instead of Num.++ * Every message decoder consumes its whole payload and preserves+ unknown odd TLV records, so encoding a decoded message reproduces+ its wire bytes. Init has typed networks and remote_addr fields,+ and uses ppad-bolt9 feature vectors.++ * A single TLV decoder takes the set of known types, rejects+ unknown even types, and keeps unknown odd ones.++ * Removed the Envelope type, the minimal signed integer codecs and+ the Internal module; added ping_response and fixed-size field+ codecs.++ * Fixes TLV decoding, which accepted lengths of 2^63 or more as+ empty values.++ * Fixed-width encoders no longer allocate kilobytes per call.++ * Tests now cover every BOLT #1 appendix vector.++- 0.0.1 (2026-04-18) * Initial release.
bench/Fixtures.hs view
@@ -1,433 +1,50 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} --- |--- Module: Fixtures--- Copyright: (c) 2025 Jared Tobin--- License: MIT--- Maintainer: Jared Tobin <jared@ppad.tech>------ Test fixtures for BOLT #1 benchmarks.- module Fixtures where import qualified Data.ByteString as BS-import Data.Maybe (fromJust)+import Data.Maybe (fromMaybe) import Lightning.Protocol.BOLT1---- Sample ByteStrings --------------------------------------------------------- | 64-byte sample data.-bytes64 :: BS.ByteString-bytes64 = BS.replicate 64 0xAB-{-# NOINLINE bytes64 #-}---- | 1KB sample data.-bytes1k :: BS.ByteString-bytes1k = BS.replicate 1024 0xCD-{-# NOINLINE bytes1k #-}---- | 16KB sample data.-bytes16k :: BS.ByteString-bytes16k = BS.replicate 16384 0xEF-{-# NOINLINE bytes16k #-}---- Sample chain hashes (32 bytes each) ---------------------------------------- | Bitcoin mainnet genesis block hash (reversed, as used in LN).-mainnetChainHash :: ChainHash-mainnetChainHash = fromJust $ chainHash $ BS.pack- [ 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72- , 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f- , 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c- , 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00- ]-{-# NOINLINE mainnetChainHash #-}---- | Bitcoin testnet genesis block hash (reversed, as used in LN).-testnetChainHash :: ChainHash-testnetChainHash = fromJust $ chainHash $ BS.pack- [ 0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71- , 0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae- , 0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad- , 0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00- ]-{-# NOINLINE testnetChainHash #-}---- Sample channel IDs (32 bytes each) -------------------------------------+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- | Sample channel ID (non-zero).-sampleChannelId :: ChannelId-sampleChannelId = fromJust $ channelId $ BS.pack- [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08- , 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10- , 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18- , 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20- ]-{-# NOINLINE sampleChannelId #-}+{-# NOINLINE tlvs #-}+tlvs :: TlvStream+tlvs = fromMaybe empty_tlv_stream $+ tlv_stream [ TlvRecord t (BS.replicate 32 0x2a) | t <- [1, 3 .. 41] ] --- Sample Init messages ---------------------------------------------------+{-# NOINLINE tlvs_bytes #-}+tlvs_bytes :: BS.ByteString+tlvs_bytes = encode_tlv_stream tlvs --- | Minimal Init message (empty features, no TLVs).-minimalInit :: Init-minimalInit = Init- { initGlobalFeatures = BS.empty- , initFeatures = BS.empty- , initTlvs = []+{-# NOINLINE init_msg #-}+init_msg :: Message+init_msg = MsgInit Init {+ init_global_features = BOLT9.parse ""+ , init_features = BOLT9.parse "\x02\xaa\x52\x69\xa1"+ , init_networks = fmap pure (chain_hash (BS.replicate 32 0x6f))+ , init_remote_addr = Just "\x01\x7f\x00\x00\x01\x26\x07"+ , init_tlvs = empty_tlv_stream }-{-# NOINLINE minimalInit #-} --- | Init with feature bits set.-initWithFeatures :: Init-initWithFeatures = Init- { initGlobalFeatures = BS.pack [0x00, 0x01] -- 2 bytes- , initFeatures = BS.pack [0x02, 0xa2] -- data_loss_protect, etc.- , initTlvs = []- }-{-# NOINLINE initWithFeatures #-}---- | Init with TLV extensions.-initWithTlvs :: Init-initWithTlvs = Init- { initGlobalFeatures = BS.empty- , initFeatures = BS.pack [0x02, 0xa2]- , initTlvs = [InitNetworks [mainnetChainHash]]- }-{-# NOINLINE initWithTlvs #-}---- | Init with multiple chain hashes.-initWithMultipleChains :: Init-initWithMultipleChains = Init- { initGlobalFeatures = BS.empty- , initFeatures = BS.pack [0x02, 0xa2]- , initTlvs = [InitNetworks [mainnetChainHash, testnetChainHash]]- }-{-# NOINLINE initWithMultipleChains #-}---- | Full Init with features and remote_addr TLV.-fullInit :: Init-fullInit = Init- { initGlobalFeatures = BS.pack [0x00, 0x01]- , initFeatures = BS.pack [0x02, 0xa2, 0x01]- , initTlvs =- [ InitNetworks [mainnetChainHash]- , InitRemoteAddr (BS.pack [0x01, 0x7f, 0x00, 0x00, 0x01, 0x27, 0x10])- ]- }-{-# NOINLINE fullInit #-}---- Sample Error messages ------------------------------------------------------ | Minimal Error message (connection-level, empty data).-minimalError :: Error-minimalError = Error- { errorChannelId = allChannels- , errorData = BS.empty- }-{-# NOINLINE minimalError #-}---- | Error with channel ID and message.-errorWithData :: Error-errorWithData = Error- { errorChannelId = sampleChannelId- , errorData = "funding transaction failed"- }-{-# NOINLINE errorWithData #-}---- | Error with longer data.-errorWithLongData :: Error-errorWithLongData = Error- { errorChannelId = sampleChannelId- , errorData = bytes1k- }-{-# NOINLINE errorWithLongData #-}---- Sample Warning messages ---------------------------------------------------- | Minimal Warning message.-minimalWarning :: Warning-minimalWarning = Warning- { warningChannelId = allChannels- , warningData = BS.empty- }-{-# NOINLINE minimalWarning #-}---- | Warning with message.-warningWithData :: Warning-warningWithData = Warning- { warningChannelId = sampleChannelId- , warningData = "channel fee too low"- }-{-# NOINLINE warningWithData #-}---- Sample Ping messages ------------------------------------------------------- | Minimal Ping (no padding, no response requested).-minimalPing :: Ping-minimalPing = Ping- { pingNumPongBytes = 0- , pingIgnored = BS.empty- }-{-# NOINLINE minimalPing #-}---- | Ping with response requested but no padding.-pingWithResponse :: Ping-pingWithResponse = Ping- { pingNumPongBytes = 64- , pingIgnored = BS.empty- }-{-# NOINLINE pingWithResponse #-}---- | Ping with padding (64 bytes).-pingWithPadding :: Ping-pingWithPadding = Ping- { pingNumPongBytes = 64- , pingIgnored = bytes64- }-{-# NOINLINE pingWithPadding #-}---- | Ping with large padding (1KB).-pingWithLargePadding :: Ping-pingWithLargePadding = Ping- { pingNumPongBytes = 128- , pingIgnored = bytes1k- }-{-# NOINLINE pingWithLargePadding #-}---- Sample Pong messages ------------------------------------------------------- | Minimal Pong (no ignored bytes).-minimalPong :: Pong-minimalPong = Pong- { pongIgnored = BS.empty- }-{-# NOINLINE minimalPong #-}---- | Pong with padding (64 bytes).-pongWithPadding :: Pong-pongWithPadding = Pong- { pongIgnored = bytes64- }-{-# NOINLINE pongWithPadding #-}---- | Pong with large padding (1KB).-pongWithLargePadding :: Pong-pongWithLargePadding = Pong- { pongIgnored = bytes1k- }-{-# NOINLINE pongWithLargePadding #-}---- Sample PeerStorage messages ------------------------------------------------ | Minimal PeerStorage (empty blob).-minimalPeerStorage :: PeerStorage-minimalPeerStorage = PeerStorage- { peerStorageBlob = BS.empty- }-{-# NOINLINE minimalPeerStorage #-}---- | PeerStorage with 1KB blob.-peerStorageSmall :: PeerStorage-peerStorageSmall = PeerStorage- { peerStorageBlob = bytes1k- }-{-# NOINLINE peerStorageSmall #-}---- | PeerStorage with 16KB blob.-peerStorageLarge :: PeerStorage-peerStorageLarge = PeerStorage- { peerStorageBlob = bytes16k- }-{-# NOINLINE peerStorageLarge #-}---- Sample PeerStorageRetrieval messages --------------------------------------- | Minimal PeerStorageRetrieval (empty blob).-minimalPeerStorageRetrieval :: PeerStorageRetrieval-minimalPeerStorageRetrieval = PeerStorageRetrieval- { peerStorageRetrievalBlob = BS.empty- }-{-# NOINLINE minimalPeerStorageRetrieval #-}---- | PeerStorageRetrieval with 1KB blob.-peerStorageRetrievalSmall :: PeerStorageRetrieval-peerStorageRetrievalSmall = PeerStorageRetrieval- { peerStorageRetrievalBlob = bytes1k- }-{-# NOINLINE peerStorageRetrievalSmall #-}---- | PeerStorageRetrieval with 16KB blob.-peerStorageRetrievalLarge :: PeerStorageRetrieval-peerStorageRetrievalLarge = PeerStorageRetrieval- { peerStorageRetrievalBlob = bytes16k- }-{-# NOINLINE peerStorageRetrievalLarge #-}---- Sample TLV streams --------------------------------------------------------- | Empty TLV stream.-emptyTlvStream :: TlvStream-emptyTlvStream = unsafeTlvStream []-{-# NOINLINE emptyTlvStream #-}---- | TLV stream with 1 record.-smallTlvStream :: TlvStream-smallTlvStream = unsafeTlvStream- [ TlvRecord 1 (BS.replicate 32 0x01)- ]-{-# NOINLINE smallTlvStream #-}---- | TLV stream with 5 records.-mediumTlvStream :: TlvStream-mediumTlvStream = unsafeTlvStream- [ TlvRecord 1 (BS.replicate 8 0x01)- , TlvRecord 3 (BS.replicate 16 0x03)- , TlvRecord 5 (BS.replicate 32 0x05)- , TlvRecord 7 (BS.replicate 64 0x07)- , TlvRecord 9 (BS.replicate 128 0x09)- ]-{-# NOINLINE mediumTlvStream #-}---- | TLV stream with 20 records.-largeTlvStream :: TlvStream-largeTlvStream = unsafeTlvStream- [ TlvRecord 1 (BS.replicate 8 0x01)- , TlvRecord 3 (BS.replicate 16 0x02)- , TlvRecord 5 (BS.replicate 8 0x03)- , TlvRecord 7 (BS.replicate 16 0x04)- , TlvRecord 9 (BS.replicate 8 0x05)- , TlvRecord 11 (BS.replicate 16 0x06)- , TlvRecord 13 (BS.replicate 8 0x07)- , TlvRecord 15 (BS.replicate 16 0x08)- , TlvRecord 17 (BS.replicate 8 0x09)- , TlvRecord 19 (BS.replicate 16 0x0a)- , TlvRecord 21 (BS.replicate 8 0x0b)- , TlvRecord 23 (BS.replicate 16 0x0c)- , TlvRecord 25 (BS.replicate 8 0x0d)- , TlvRecord 27 (BS.replicate 16 0x0e)- , TlvRecord 29 (BS.replicate 8 0x0f)- , TlvRecord 31 (BS.replicate 16 0x10)- , TlvRecord 33 (BS.replicate 8 0x11)- , TlvRecord 35 (BS.replicate 16 0x12)- , TlvRecord 37 (BS.replicate 8 0x13)- , TlvRecord 39 (BS.replicate 16 0x14)- ]-{-# NOINLINE largeTlvStream #-}---- Encoded message bytes (for decode benchmarks) ------------------------------ Helper to encode or fail.-encodeOrFail :: Either EncodeError BS.ByteString -> BS.ByteString-encodeOrFail (Right bs) = bs-encodeOrFail (Left _) = error "encodeOrFail: encoding failed"---- | Encoded minimal Init.-encodedMinimalInit :: BS.ByteString-encodedMinimalInit = encodeOrFail $ encodeEnvelope (MsgInitVal minimalInit) Nothing-{-# NOINLINE encodedMinimalInit #-}---- | Encoded Init with TLVs.-encodedInitWithTlvs :: BS.ByteString-encodedInitWithTlvs = encodeOrFail $ encodeEnvelope (MsgInitVal initWithTlvs) Nothing-{-# NOINLINE encodedInitWithTlvs #-}---- | Encoded full Init.-encodedFullInit :: BS.ByteString-encodedFullInit = encodeOrFail $ encodeEnvelope (MsgInitVal fullInit) Nothing-{-# NOINLINE encodedFullInit #-}---- | Encoded minimal Error.-encodedMinimalError :: BS.ByteString-encodedMinimalError = encodeOrFail $ encodeEnvelope (MsgErrorVal minimalError) Nothing-{-# NOINLINE encodedMinimalError #-}---- | Encoded Error with data.-encodedErrorWithData :: BS.ByteString-encodedErrorWithData = encodeOrFail $ encodeEnvelope (MsgErrorVal errorWithData) Nothing-{-# NOINLINE encodedErrorWithData #-}---- | Encoded minimal Warning.-encodedMinimalWarning :: BS.ByteString-encodedMinimalWarning = encodeOrFail $- encodeEnvelope (MsgWarningVal minimalWarning) Nothing-{-# NOINLINE encodedMinimalWarning #-}---- | Encoded Warning with data.-encodedWarningWithData :: BS.ByteString-encodedWarningWithData = encodeOrFail $- encodeEnvelope (MsgWarningVal warningWithData) Nothing-{-# NOINLINE encodedWarningWithData #-}---- | Encoded minimal Ping.-encodedMinimalPing :: BS.ByteString-encodedMinimalPing = encodeOrFail $ encodeEnvelope (MsgPingVal minimalPing) Nothing-{-# NOINLINE encodedMinimalPing #-}---- | Encoded Ping with padding.-encodedPingWithPadding :: BS.ByteString-encodedPingWithPadding = encodeOrFail $- encodeEnvelope (MsgPingVal pingWithPadding) Nothing-{-# NOINLINE encodedPingWithPadding #-}---- | Encoded Ping with large padding.-encodedPingWithLargePadding :: BS.ByteString-encodedPingWithLargePadding = encodeOrFail $- encodeEnvelope (MsgPingVal pingWithLargePadding) Nothing-{-# NOINLINE encodedPingWithLargePadding #-}---- | Encoded minimal Pong.-encodedMinimalPong :: BS.ByteString-encodedMinimalPong = encodeOrFail $ encodeEnvelope (MsgPongVal minimalPong) Nothing-{-# NOINLINE encodedMinimalPong #-}---- | Encoded Pong with padding.-encodedPongWithPadding :: BS.ByteString-encodedPongWithPadding = encodeOrFail $- encodeEnvelope (MsgPongVal pongWithPadding) Nothing-{-# NOINLINE encodedPongWithPadding #-}---- | Encoded minimal PeerStorage.-encodedMinimalPeerStorage :: BS.ByteString-encodedMinimalPeerStorage = encodeOrFail $- encodeEnvelope (MsgPeerStorageVal minimalPeerStorage) Nothing-{-# NOINLINE encodedMinimalPeerStorage #-}---- | Encoded PeerStorage with 1KB blob.-encodedPeerStorageSmall :: BS.ByteString-encodedPeerStorageSmall = encodeOrFail $- encodeEnvelope (MsgPeerStorageVal peerStorageSmall) Nothing-{-# NOINLINE encodedPeerStorageSmall #-}---- | Encoded minimal PeerStorageRetrieval.-encodedMinimalPeerStorageRetrieval :: BS.ByteString-encodedMinimalPeerStorageRetrieval = encodeOrFail $- encodeEnvelope (MsgPeerStorageRetrievalVal minimalPeerStorageRetrieval) Nothing-{-# NOINLINE encodedMinimalPeerStorageRetrieval #-}---- | Encoded PeerStorageRetrieval with 1KB blob.-encodedPeerStorageRetrievalSmall :: BS.ByteString-encodedPeerStorageRetrievalSmall = encodeOrFail $- encodeEnvelope (MsgPeerStorageRetrievalVal peerStorageRetrievalSmall) Nothing-{-# NOINLINE encodedPeerStorageRetrievalSmall #-}+{-# NOINLINE ping_msg #-}+ping_msg :: Message+ping_msg = MsgPing (Ping 128 (BS.replicate 64 0) empty_tlv_stream) --- Encoded TLV streams (for decode benchmarks) ----------------------------+{-# NOINLINE error_msg #-}+error_msg :: Message+error_msg = MsgError (Error all_channels (BS.replicate 128 0x61) tlvs) --- | Encoded empty TLV stream.-encodedEmptyTlvStream :: BS.ByteString-encodedEmptyTlvStream = encodeTlvStream emptyTlvStream-{-# NOINLINE encodedEmptyTlvStream #-}+wire :: Message -> BS.ByteString+wire m = either (const BS.empty) id (encode_message m) --- | Encoded small TLV stream (1 record).-encodedSmallTlvStream :: BS.ByteString-encodedSmallTlvStream = encodeTlvStream smallTlvStream-{-# NOINLINE encodedSmallTlvStream #-}+{-# NOINLINE init_bytes #-}+init_bytes :: BS.ByteString+init_bytes = wire init_msg --- | Encoded medium TLV stream (5 records).-encodedMediumTlvStream :: BS.ByteString-encodedMediumTlvStream = encodeTlvStream mediumTlvStream-{-# NOINLINE encodedMediumTlvStream #-}+{-# NOINLINE ping_bytes #-}+ping_bytes :: BS.ByteString+ping_bytes = wire ping_msg --- | Encoded large TLV stream (20 records).-encodedLargeTlvStream :: BS.ByteString-encodedLargeTlvStream = encodeTlvStream largeTlvStream-{-# NOINLINE encodedLargeTlvStream #-}+{-# NOINLINE error_bytes #-}+error_bytes :: BS.ByteString+error_bytes = wire error_msg
bench/Main.hs view
@@ -1,526 +1,33 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Criterion.Main-import qualified Data.ByteString as BS-import Data.Word (Word16, Word32, Word64)-import Data.Int (Int8, Int16, Int32, Int64)+import Fixtures import Lightning.Protocol.BOLT1-import Lightning.Protocol.BOLT1.Codec-import Lightning.Protocol.BOLT1.TLV (encodeInitTlvs, encodeTlvRecord, parseInitTlvs) --- Fixtures ------------------------------------------------------------------------ Prevent constant folding by marking fixtures as NOINLINE.--{-# NOINLINE u16Val #-}-u16Val :: Word16-u16Val = 0x1234--{-# NOINLINE u32Val #-}-u32Val :: Word32-u32Val = 0x12345678--{-# NOINLINE u64Val #-}-u64Val :: Word64-u64Val = 0x123456789ABCDEF0--{-# NOINLINE s8Val #-}-s8Val :: Int8-s8Val = -42--{-# NOINLINE s16Val #-}-s16Val :: Int16-s16Val = -1234--{-# NOINLINE s32Val #-}-s32Val :: Int32-s32Val = -12345678--{-# NOINLINE s64Val #-}-s64Val :: Int64-s64Val = -123456789012345---- Truncated values--{-# NOINLINE tu16Zero #-}-tu16Zero :: Word16-tu16Zero = 0--{-# NOINLINE tu16Small #-}-tu16Small :: Word16-tu16Small = 0x42--{-# NOINLINE tu16Max #-}-tu16Max :: Word16-tu16Max = 0xFFFF--{-# NOINLINE tu32Zero #-}-tu32Zero :: Word32-tu32Zero = 0--{-# NOINLINE tu32Small #-}-tu32Small :: Word32-tu32Small = 0x42--{-# NOINLINE tu32Max #-}-tu32Max :: Word32-tu32Max = 0xFFFFFFFF--{-# NOINLINE tu64Zero #-}-tu64Zero :: Word64-tu64Zero = 0--{-# NOINLINE tu64Small #-}-tu64Small :: Word64-tu64Small = 0x42--{-# NOINLINE tu64Max #-}-tu64Max :: Word64-tu64Max = 0xFFFFFFFFFFFFFFFF---- MinSigned values--{-# NOINLINE ms0 #-}-ms0 :: Int64-ms0 = 0--{-# NOINLINE ms127 #-}-ms127 :: Int64-ms127 = 127--{-# NOINLINE ms128 #-}-ms128 :: Int64-ms128 = 128--{-# NOINLINE msNeg128 #-}-msNeg128 :: Int64-msNeg128 = -128--{-# NOINLINE msNeg129 #-}-msNeg129 :: Int64-msNeg129 = -129---- BigSize values--{-# NOINLINE bs0 #-}-bs0 :: Word64-bs0 = 0--{-# NOINLINE bs252 #-}-bs252 :: Word64-bs252 = 252--{-# NOINLINE bs253 #-}-bs253 :: Word64-bs253 = 253--{-# NOINLINE bs65535 #-}-bs65535 :: Word64-bs65535 = 65535--{-# NOINLINE bs65536 #-}-bs65536 :: Word64-bs65536 = 65536--{-# NOINLINE bsLarge #-}-bsLarge :: Word64-bsLarge = 0x100000000---- Encoded bytes for decode benchmarks--{-# NOINLINE encodedU16 #-}-encodedU16 :: BS.ByteString-encodedU16 = encodeU16 u16Val--{-# NOINLINE encodedU32 #-}-encodedU32 :: BS.ByteString-encodedU32 = encodeU32 u32Val--{-# NOINLINE encodedU64 #-}-encodedU64 :: BS.ByteString-encodedU64 = encodeU64 u64Val--{-# NOINLINE encodedS8 #-}-encodedS8 :: BS.ByteString-encodedS8 = encodeS8 s8Val--{-# NOINLINE encodedS16 #-}-encodedS16 :: BS.ByteString-encodedS16 = encodeS16 s16Val--{-# NOINLINE encodedS32 #-}-encodedS32 :: BS.ByteString-encodedS32 = encodeS32 s32Val--{-# NOINLINE encodedS64 #-}-encodedS64 :: BS.ByteString-encodedS64 = encodeS64 s64Val--{-# NOINLINE encodedTu16Small #-}-encodedTu16Small :: BS.ByteString-encodedTu16Small = encodeTu16 tu16Small--{-# NOINLINE encodedTu32Small #-}-encodedTu32Small :: BS.ByteString-encodedTu32Small = encodeTu32 tu32Small--{-# NOINLINE encodedTu64Small #-}-encodedTu64Small :: BS.ByteString-encodedTu64Small = encodeTu64 tu64Small--{-# NOINLINE encodedMs127 #-}-encodedMs127 :: BS.ByteString-encodedMs127 = encodeMinSigned ms127--{-# NOINLINE encodedMsNeg129 #-}-encodedMsNeg129 :: BS.ByteString-encodedMsNeg129 = encodeMinSigned msNeg129--{-# NOINLINE encodedBs0 #-}-encodedBs0 :: BS.ByteString-encodedBs0 = encodeBigSize bs0--{-# NOINLINE encodedBs253 #-}-encodedBs253 :: BS.ByteString-encodedBs253 = encodeBigSize bs253--{-# NOINLINE encodedBs65536 #-}-encodedBs65536 :: BS.ByteString-encodedBs65536 = encodeBigSize bs65536--{-# NOINLINE encodedBsLarge #-}-encodedBsLarge :: BS.ByteString-encodedBsLarge = encodeBigSize bsLarge---- TLV fixtures--{-# NOINLINE tlvRec1 #-}-tlvRec1 :: TlvRecord-tlvRec1 = TlvRecord 1 "test"--{-# NOINLINE tlvRec3 #-}-tlvRec3 :: TlvRecord-tlvRec3 = TlvRecord 3 "addr"--{-# NOINLINE tlvRec5 #-}-tlvRec5 :: TlvRecord-tlvRec5 = TlvRecord 5 "value"--{-# NOINLINE tlvStream1 #-}-tlvStream1 :: TlvStream-tlvStream1 = unsafeTlvStream [tlvRec1]--{-# NOINLINE tlvStream5 #-}-tlvStream5 :: TlvStream-tlvStream5 = unsafeTlvStream- [ TlvRecord 1 "one"- , TlvRecord 3 "three"- , TlvRecord 5 "five"- , TlvRecord 7 "seven"- , TlvRecord 9 "nine"- ]--{-# NOINLINE tlvStream20 #-}-tlvStream20 :: TlvStream-tlvStream20 = unsafeTlvStream- [ TlvRecord (2*i + 1) (BS.replicate 10 (fromIntegral i))- | i <- [0..19]- ]--{-# NOINLINE encodedTlvStream1 #-}-encodedTlvStream1 :: BS.ByteString-encodedTlvStream1 = encodeTlvStream tlvStream1--{-# NOINLINE encodedTlvStream5 #-}-encodedTlvStream5 :: BS.ByteString-encodedTlvStream5 = encodeTlvStream tlvStream5--{-# NOINLINE encodedTlvStream20 #-}-encodedTlvStream20 :: BS.ByteString-encodedTlvStream20 = encodeTlvStream tlvStream20---- Init TLV fixtures--{-# NOINLINE chainHash1 #-}-chainHash1 :: ChainHash-chainHash1 = case chainHash (BS.replicate 32 0x01) of- Just ch -> ch- Nothing -> error "impossible"--{-# NOINLINE initTlvNetworks #-}-initTlvNetworks :: [InitTlv]-initTlvNetworks = [InitNetworks [chainHash1]]--{-# NOINLINE initTlvRemoteAddr #-}-initTlvRemoteAddr :: [InitTlv]-initTlvRemoteAddr = [InitRemoteAddr "127.0.0.1"]--{-# NOINLINE encodedInitTlvs #-}-encodedInitTlvs :: BS.ByteString-encodedInitTlvs = encodeTlvStream (encodeInitTlvs initTlvNetworks)---- Message fixtures--{-# NOINLINE initMinimal #-}-initMinimal :: Init-initMinimal = Init BS.empty BS.empty []--{-# NOINLINE initWithTlvs #-}-initWithTlvs :: Init-initWithTlvs = Init (BS.pack [0x00, 0x01]) (BS.pack [0x02, 0x03]) initTlvNetworks--{-# NOINLINE errorMinimal #-}-errorMinimal :: Error-errorMinimal = Error allChannels BS.empty--{-# NOINLINE errorWithData #-}-errorWithData :: Error-errorWithData = Error allChannels "Connection reset by peer"--{-# NOINLINE warningMsg #-}-warningMsg :: Warning-warningMsg = Warning allChannels "Low disk space"--{-# NOINLINE pingMinimal #-}-pingMinimal :: Ping-pingMinimal = Ping 64 BS.empty--{-# NOINLINE pingWithPadding #-}-pingWithPadding :: Ping-pingWithPadding = Ping 64 (BS.replicate 64 0x00)--{-# NOINLINE pongMsg #-}-pongMsg :: Pong-pongMsg = Pong (BS.replicate 64 0x00)--{-# NOINLINE peerStorageMsg #-}-peerStorageMsg :: PeerStorage-peerStorageMsg = PeerStorage (BS.replicate 100 0xAB)--{-# NOINLINE peerStorageRetMsg #-}-peerStorageRetMsg :: PeerStorageRetrieval-peerStorageRetMsg = PeerStorageRetrieval (BS.replicate 100 0xCD)---- Encoded messages for decode benchmarks--{-# NOINLINE encodedInitMinimal #-}-encodedInitMinimal :: BS.ByteString-encodedInitMinimal = case encodeInit initMinimal of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedInitWithTlvs #-}-encodedInitWithTlvs :: BS.ByteString-encodedInitWithTlvs = case encodeInit initWithTlvs of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedErrorMinimal #-}-encodedErrorMinimal :: BS.ByteString-encodedErrorMinimal = case encodeError errorMinimal of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedErrorWithData #-}-encodedErrorWithData :: BS.ByteString-encodedErrorWithData = case encodeError errorWithData of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedWarning #-}-encodedWarning :: BS.ByteString-encodedWarning = case encodeWarning warningMsg of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedPingMinimal #-}-encodedPingMinimal :: BS.ByteString-encodedPingMinimal = case encodePing pingMinimal of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedPingWithPadding #-}-encodedPingWithPadding :: BS.ByteString-encodedPingWithPadding = case encodePing pingWithPadding of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedPong #-}-encodedPong :: BS.ByteString-encodedPong = case encodePong pongMsg of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedPeerStorage #-}-encodedPeerStorage :: BS.ByteString-encodedPeerStorage = case encodePeerStorage peerStorageMsg of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedPeerStorageRet #-}-encodedPeerStorageRet :: BS.ByteString-encodedPeerStorageRet = case encodePeerStorageRetrieval peerStorageRetMsg of- Right bs -> bs- Left _ -> error "impossible"---- Envelope fixtures--{-# NOINLINE msgInit #-}-msgInit :: Message-msgInit = MsgInitVal initMinimal--{-# NOINLINE msgPing #-}-msgPing :: Message-msgPing = MsgPingVal pingMinimal--{-# NOINLINE encodedEnvelopeNoExt #-}-encodedEnvelopeNoExt :: BS.ByteString-encodedEnvelopeNoExt = case encodeEnvelope msgPing Nothing of- Right bs -> bs- Left _ -> error "impossible"--{-# NOINLINE encodedEnvelopeWithExt #-}-encodedEnvelopeWithExt :: BS.ByteString-encodedEnvelopeWithExt = case encodeEnvelope msgPing (Just tlvStream5) of- Right bs -> bs- Left _ -> error "impossible"---- Main ------------------------------------------------------------------------- main :: IO ()-main = defaultMain- [ bgroup "prim/encode"- [ bench "encodeU16" $ whnf encodeU16 u16Val- , bench "encodeU32" $ whnf encodeU32 u32Val- , bench "encodeU64" $ whnf encodeU64 u64Val- , bench "encodeS8" $ whnf encodeS8 s8Val- , bench "encodeS16" $ whnf encodeS16 s16Val- , bench "encodeS32" $ whnf encodeS32 s32Val- , bench "encodeS64" $ whnf encodeS64 s64Val- , bench "encodeTu16/0" $ whnf encodeTu16 tu16Zero- , bench "encodeTu16/small" $ whnf encodeTu16 tu16Small- , bench "encodeTu16/max" $ whnf encodeTu16 tu16Max- , bench "encodeTu32/0" $ whnf encodeTu32 tu32Zero- , bench "encodeTu32/small" $ whnf encodeTu32 tu32Small- , bench "encodeTu32/max" $ whnf encodeTu32 tu32Max- , bench "encodeTu64/0" $ whnf encodeTu64 tu64Zero- , bench "encodeTu64/small" $ whnf encodeTu64 tu64Small- , bench "encodeTu64/max" $ whnf encodeTu64 tu64Max- , bench "encodeMinSigned/0" $ whnf encodeMinSigned ms0- , bench "encodeMinSigned/127" $ whnf encodeMinSigned ms127- , bench "encodeMinSigned/128" $ whnf encodeMinSigned ms128- , bench "encodeMinSigned/-128" $ whnf encodeMinSigned msNeg128- , bench "encodeMinSigned/-129" $ whnf encodeMinSigned msNeg129- , bench "encodeBigSize/0" $ whnf encodeBigSize bs0- , bench "encodeBigSize/252" $ whnf encodeBigSize bs252- , bench "encodeBigSize/253" $ whnf encodeBigSize bs253- , bench "encodeBigSize/65535" $ whnf encodeBigSize bs65535- , bench "encodeBigSize/65536" $ whnf encodeBigSize bs65536- , bench "encodeBigSize/large" $ whnf encodeBigSize bsLarge- ]-- , bgroup "prim/decode"- [ bench "decodeU16" $ nf decodeU16 encodedU16- , bench "decodeU32" $ nf decodeU32 encodedU32- , bench "decodeU64" $ nf decodeU64 encodedU64- , bench "decodeS8" $ nf decodeS8 encodedS8- , bench "decodeS16" $ nf decodeS16 encodedS16- , bench "decodeS32" $ nf decodeS32 encodedS32- , bench "decodeS64" $ nf decodeS64 encodedS64- , bench "decodeTu16" $ nf (decodeTu16 1) encodedTu16Small- , bench "decodeTu32" $ nf (decodeTu32 1) encodedTu32Small- , bench "decodeTu64" $ nf (decodeTu64 1) encodedTu64Small- , bench "decodeMinSigned/1" $ nf (decodeMinSigned 1) encodedMs127- , bench "decodeMinSigned/2" $ nf (decodeMinSigned 2) encodedMsNeg129- , bench "decodeBigSize/0" $ nf decodeBigSize encodedBs0- , bench "decodeBigSize/253" $ nf decodeBigSize encodedBs253- , bench "decodeBigSize/65536" $ nf decodeBigSize encodedBs65536- , bench "decodeBigSize/large" $ nf decodeBigSize encodedBsLarge- ]-- , bgroup "tlv/encode"- [ bench "encodeTlvRecord" $ whnf encodeTlvRecord tlvRec1- , bench "encodeTlvStream/1" $ whnf encodeTlvStream tlvStream1- , bench "encodeTlvStream/5" $ whnf encodeTlvStream tlvStream5- , bench "encodeTlvStream/20" $ whnf encodeTlvStream tlvStream20- , bench "encodeInitTlvs" $ nf encodeInitTlvs initTlvNetworks- ]-- , bgroup "tlv/decode"- [ bench "decodeTlvStreamRaw/1" $ nf decodeTlvStreamRaw encodedTlvStream1- , bench "decodeTlvStreamRaw/5" $ nf decodeTlvStreamRaw encodedTlvStream5- , bench "decodeTlvStreamRaw/20" $ nf decodeTlvStreamRaw encodedTlvStream20- , bench "decodeTlvStream" $ nf decodeTlvStream encodedInitTlvs- , bench "decodeTlvStreamWith" $- nf (decodeTlvStreamWith (const True)) encodedTlvStream5- , bench "parseInitTlvs" $- nf parseInitTlvs (encodeInitTlvs initTlvNetworks)- ]-- , bgroup "message/encode"- [ bench "encodeInit/minimal" $ nf encodeInit initMinimal- , bench "encodeInit/with-tlvs" $ nf encodeInit initWithTlvs- , bench "encodeError/minimal" $ nf encodeError errorMinimal- , bench "encodeError/with-data" $ nf encodeError errorWithData- , bench "encodeWarning" $ nf encodeWarning warningMsg- , bench "encodePing/minimal" $ nf encodePing pingMinimal- , bench "encodePing/with-padding" $ nf encodePing pingWithPadding- , bench "encodePong" $ nf encodePong pongMsg- , bench "encodePeerStorage" $ nf encodePeerStorage peerStorageMsg- , bench "encodePeerStorageRetrieval" $- nf encodePeerStorageRetrieval peerStorageRetMsg- ]-- , bgroup "message/decode"- [ bench "decodeInit/minimal" $ nf decodeInit encodedInitMinimal- , bench "decodeInit/with-tlvs" $ nf decodeInit encodedInitWithTlvs- , bench "decodeError/minimal" $ nf decodeError encodedErrorMinimal- , bench "decodeError/with-data" $ nf decodeError encodedErrorWithData- , bench "decodeWarning" $ nf decodeWarning encodedWarning- , bench "decodePing/minimal" $ nf decodePing encodedPingMinimal- , bench "decodePing/with-padding" $ nf decodePing encodedPingWithPadding- , bench "decodePong" $ nf decodePong encodedPong- , bench "decodePeerStorage" $ nf decodePeerStorage encodedPeerStorage- , bench "decodePeerStorageRetrieval" $- nf decodePeerStorageRetrieval encodedPeerStorageRet- ]-- , bgroup "envelope"- [ bench "encodeEnvelope/no-ext" $ nf (encodeEnvelope msgPing) Nothing- , bench "encodeEnvelope/with-ext" $- nf (encodeEnvelope msgPing) (Just tlvStream5)- , bench "decodeEnvelope/no-ext" $ nf decodeEnvelope encodedEnvelopeNoExt- , bench "decodeEnvelope/with-ext" $- nf decodeEnvelope encodedEnvelopeWithExt- , bench "decodeEnvelopeWith" $- nf (decodeEnvelopeWith (const True)) encodedEnvelopeWithExt- ]-- , bgroup "roundtrip"- [ bench "init/minimal" $ nf (decodeInit . forceRight . encodeInit)- initMinimal- , bench "init/with-tlvs" $ nf (decodeInit . forceRight . encodeInit)- initWithTlvs- , bench "error" $ nf (decodeError . forceRight . encodeError) errorWithData- , bench "warning" $ nf (decodeWarning . forceRight . encodeWarning)- warningMsg- , bench "ping" $ nf (decodePing . forceRight . encodePing) pingWithPadding- , bench "pong" $ nf (decodePong . forceRight . encodePong) pongMsg- , bench "peer-storage" $- nf (decodePeerStorage . forceRight . encodePeerStorage) peerStorageMsg- , bench "peer-storage-retrieval" $- nf (decodePeerStorageRetrieval . forceRight . encodePeerStorageRetrieval)- peerStorageRetMsg- , bench "envelope" $ nf- (decodeEnvelope . forceRight . encodeEnvelope msgPing) (Just tlvStream5)- ]+main = defaultMain [+ bgroup "primitives" [+ bench "encode_u64" $ nf encode_u64 0x0102030405060708+ , bench "decode_u64" $ nf decode_u64 "\x01\x02\x03\x04\x05\x06\x07\x08"+ , bench "encode_tu64" $ nf encode_tu64 0x010203+ , bench "decode_tu64" $ nf decode_tu64 "\x01\x02\x03"+ , bench "encode_bigsize (9 bytes)" $ nf encode_bigsize 0x100000000+ , bench "decode_bigsize (9 bytes)" $+ nf decode_bigsize "\xff\x00\x00\x00\x01\x00\x00\x00\x00"+ ]+ , bgroup "tlv" [+ bench "encode_tlv_stream (21 records)" $ nf encode_tlv_stream tlvs+ , bench "decode_tlv_stream (21 records)" $+ nf (decode_tlv_stream (const False)) tlvs_bytes+ ]+ , bgroup "messages" [+ bench "encode init" $ nf encode_message init_msg+ , bench "decode init" $ nf decode_message init_bytes+ , bench "encode ping" $ nf encode_message ping_msg+ , bench "decode ping" $ nf decode_message ping_bytes+ , bench "encode error (with tlvs)" $ nf encode_message error_msg+ , bench "decode error (with tlvs)" $ nf decode_message error_bytes+ ] ]---- Helper for roundtrip benchmarks-forceRight :: Either a b -> b-forceRight (Right b) = b-forceRight (Left _) = error "forceRight: Left"-{-# INLINE forceRight #-}
bench/Weight.hs view
@@ -1,347 +1,28 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} module Main where -import qualified Data.ByteString as BS-import Data.Word (Word16, Word32, Word64)-import Data.Int (Int8, Int16, Int32, Int64)+import Fixtures import Lightning.Protocol.BOLT1-import Lightning.Protocol.BOLT1.Codec-import Lightning.Protocol.BOLT1.TLV (encodeTlvRecord) import Weigh --- Fixtures ------------------------------------------------------------------------ Prevent constant folding with NOINLINE--{-# NOINLINE w16Val #-}-w16Val :: Word16-w16Val = 0x1234--{-# NOINLINE w32Val #-}-w32Val :: Word32-w32Val = 0x12345678--{-# NOINLINE w64Val #-}-w64Val :: Word64-w64Val = 0x0102030405060708--{-# NOINLINE s8Val #-}-s8Val :: Int8-s8Val = -42--{-# NOINLINE s16Val #-}-s16Val :: Int16-s16Val = -1000--{-# NOINLINE s32Val #-}-s32Val :: Int32-s32Val = -100000--{-# NOINLINE s64Val #-}-s64Val :: Int64-s64Val = -10000000000--{-# NOINLINE tu16Small #-}-tu16Small :: Word16-tu16Small = 0x7f--{-# NOINLINE tu16Full #-}-tu16Full :: Word16-tu16Full = 0xffff--{-# NOINLINE tu32Small #-}-tu32Small :: Word32-tu32Small = 0x42--{-# NOINLINE tu32Full #-}-tu32Full :: Word32-tu32Full = 0xffffffff--{-# NOINLINE tu64Small #-}-tu64Small :: Word64-tu64Small = 0x10--{-# NOINLINE tu64Full #-}-tu64Full :: Word64-tu64Full = 0xffffffffffffffff--{-# NOINLINE bigSizeSmall #-}-bigSizeSmall :: Word64-bigSizeSmall = 0xfc--{-# NOINLINE bigSizeMedium #-}-bigSizeMedium :: Word64-bigSizeMedium = 0x1000--{-# NOINLINE bigSizeLarge #-}-bigSizeLarge :: Word64-bigSizeLarge = 0x100000000---- Pre-encoded bytes for decoder benchmarks--{-# NOINLINE u16Bytes #-}-u16Bytes :: BS.ByteString-u16Bytes = encodeU16 w16Val--{-# NOINLINE u32Bytes #-}-u32Bytes :: BS.ByteString-u32Bytes = encodeU32 w32Val--{-# NOINLINE u64Bytes #-}-u64Bytes :: BS.ByteString-u64Bytes = encodeU64 w64Val--{-# NOINLINE s8Bytes #-}-s8Bytes :: BS.ByteString-s8Bytes = encodeS8 s8Val--{-# NOINLINE s16Bytes #-}-s16Bytes :: BS.ByteString-s16Bytes = encodeS16 s16Val--{-# NOINLINE s32Bytes #-}-s32Bytes :: BS.ByteString-s32Bytes = encodeS32 s32Val--{-# NOINLINE s64Bytes #-}-s64Bytes :: BS.ByteString-s64Bytes = encodeS64 s64Val--{-# NOINLINE bigSizeBytes #-}-bigSizeBytes :: BS.ByteString-bigSizeBytes = encodeBigSize bigSizeLarge---- TLV fixtures--{-# NOINLINE tlvRecord1 #-}-tlvRecord1 :: TlvRecord-tlvRecord1 = TlvRecord 1 "test-value"--{-# NOINLINE tlvStream1 #-}-tlvStream1 :: TlvStream-tlvStream1 = unsafeTlvStream [tlvRecord1]--{-# NOINLINE tlvStream5 #-}-tlvStream5 :: TlvStream-tlvStream5 = unsafeTlvStream- [ TlvRecord 1 "value1"- , TlvRecord 3 "value3"- , TlvRecord 5 "value5"- , TlvRecord 7 "value7"- , TlvRecord 9 "value9"- ]--{-# NOINLINE tlvStream20 #-}-tlvStream20 :: TlvStream-tlvStream20 = unsafeTlvStream- [ TlvRecord (2 * i + 1) (BS.replicate 10 (fromIntegral i))- | i <- [0..19]- ]--{-# NOINLINE tlvStreamBytes1 #-}-tlvStreamBytes1 :: BS.ByteString-tlvStreamBytes1 = encodeTlvStream tlvStream1--{-# NOINLINE tlvStreamBytes5 #-}-tlvStreamBytes5 :: BS.ByteString-tlvStreamBytes5 = encodeTlvStream tlvStream5--{-# NOINLINE tlvStreamBytes20 #-}-tlvStreamBytes20 :: BS.ByteString-tlvStreamBytes20 = encodeTlvStream tlvStream20---- Message fixtures--{-# NOINLINE minimalInit #-}-minimalInit :: Init-minimalInit = Init BS.empty BS.empty []--{-# NOINLINE initWithFeatures #-}-initWithFeatures :: Init-initWithFeatures = Init "\x00\x08" "\x00\x0a\x8a" []--{-# NOINLINE initWithTlvs #-}-initWithTlvs :: Init-initWithTlvs = Init BS.empty "\x00\x01" [InitRemoteAddr "127.0.0.1"]--{-# NOINLINE errorMsg #-}-errorMsg :: Error-errorMsg = Error allChannels "something bad happened"--{-# NOINLINE warningMsg #-}-warningMsg :: Warning-warningMsg = Warning allChannels "something concerning"--{-# NOINLINE pingMinimal #-}-pingMinimal :: Ping-pingMinimal = Ping 4 BS.empty--{-# NOINLINE pingWithPadding #-}-pingWithPadding :: Ping-pingWithPadding = Ping 4 (BS.replicate 64 0x00)--{-# NOINLINE pongMsg #-}-pongMsg :: Pong-pongMsg = Pong (BS.replicate 4 0x00)--{-# NOINLINE peerStorageMsg #-}-peerStorageMsg :: PeerStorage-peerStorageMsg = PeerStorage (BS.replicate 100 0xab)--{-# NOINLINE peerStorageRetrievalMsg #-}-peerStorageRetrievalMsg :: PeerStorageRetrieval-peerStorageRetrievalMsg = PeerStorageRetrieval (BS.replicate 50 0xcd)---- Pre-encoded message bytes for decoder benchmarks--{-# NOINLINE initMinimalBytes #-}-initMinimalBytes :: BS.ByteString-initMinimalBytes = either (const BS.empty) id (encodeInit minimalInit)--{-# NOINLINE initWithTlvsBytes #-}-initWithTlvsBytes :: BS.ByteString-initWithTlvsBytes = either (const BS.empty) id (encodeInit initWithTlvs)--{-# NOINLINE errorBytes #-}-errorBytes :: BS.ByteString-errorBytes = either (const BS.empty) id (encodeError errorMsg)--{-# NOINLINE warningBytes #-}-warningBytes :: BS.ByteString-warningBytes = either (const BS.empty) id (encodeWarning warningMsg)--{-# NOINLINE pingMinimalBytes #-}-pingMinimalBytes :: BS.ByteString-pingMinimalBytes = either (const BS.empty) id (encodePing pingMinimal)--{-# NOINLINE pingWithPaddingBytes #-}-pingWithPaddingBytes :: BS.ByteString-pingWithPaddingBytes = either (const BS.empty) id (encodePing pingWithPadding)--{-# NOINLINE pongBytes #-}-pongBytes :: BS.ByteString-pongBytes = either (const BS.empty) id (encodePong pongMsg)--{-# NOINLINE peerStorageBytes #-}-peerStorageBytes :: BS.ByteString-peerStorageBytes = either (const BS.empty) id (encodePeerStorage peerStorageMsg)--{-# NOINLINE peerStorageRetrievalBytes #-}-peerStorageRetrievalBytes :: BS.ByteString-peerStorageRetrievalBytes =- either (const BS.empty) id (encodePeerStorageRetrieval peerStorageRetrievalMsg)---- Envelope fixtures--{-# NOINLINE initMessage #-}-initMessage :: Message-initMessage = MsgInitVal minimalInit--{-# NOINLINE pingMessage #-}-pingMessage :: Message-pingMessage = MsgPingVal pingMinimal--{-# NOINLINE envelopeBytes #-}-envelopeBytes :: BS.ByteString-envelopeBytes = either (const BS.empty) id (encodeEnvelope initMessage Nothing)---- Main ------------------------------------------------------------------------- main :: IO () main = mainWith $ do- setColumns [Case, Allocated, GCs, Max]-- -- Primitive encoders ---------------------------------------------------------- wgroup "Primitive Encoders" $ do- func "encodeU16" encodeU16 w16Val- func "encodeU32" encodeU32 w32Val- func "encodeU64" encodeU64 w64Val- func "encodeS8" encodeS8 s8Val- func "encodeS16" encodeS16 s16Val- func "encodeS32" encodeS32 s32Val- func "encodeS64" encodeS64 s64Val-- wgroup "Truncated Unsigned Encoders" $ do- func "encodeTu16/small" encodeTu16 tu16Small- func "encodeTu16/full" encodeTu16 tu16Full- func "encodeTu32/small" encodeTu32 tu32Small- func "encodeTu32/full" encodeTu32 tu32Full- func "encodeTu64/small" encodeTu64 tu64Small- func "encodeTu64/full" encodeTu64 tu64Full-- wgroup "Minimal Signed Encoder" $ do- func "encodeMinSigned/1-byte" encodeMinSigned (0 :: Int64)- func "encodeMinSigned/2-byte" encodeMinSigned (1000 :: Int64)- func "encodeMinSigned/4-byte" encodeMinSigned (100000 :: Int64)- func "encodeMinSigned/8-byte" encodeMinSigned s64Val-- wgroup "BigSize Encoder" $ do- func "encodeBigSize/1-byte" encodeBigSize bigSizeSmall- func "encodeBigSize/3-byte" encodeBigSize bigSizeMedium- func "encodeBigSize/9-byte" encodeBigSize bigSizeLarge-- -- Primitive decoders ---------------------------------------------------------- wgroup "Primitive Decoders" $ do- func "decodeU16" decodeU16 u16Bytes- func "decodeU32" decodeU32 u32Bytes- func "decodeU64" decodeU64 u64Bytes- func "decodeS8" decodeS8 s8Bytes- func "decodeS16" decodeS16 s16Bytes- func "decodeS32" decodeS32 s32Bytes- func "decodeS64" decodeS64 s64Bytes- func "decodeBigSize" decodeBigSize bigSizeBytes-- -- TLV operations -------------------------------------------------------------- wgroup "TLV Encoding" $ do- func "encodeTlvRecord" encodeTlvRecord tlvRecord1- func "encodeTlvStream/1-record" encodeTlvStream tlvStream1- func "encodeTlvStream/5-records" encodeTlvStream tlvStream5- func "encodeTlvStream/20-records" encodeTlvStream tlvStream20-- wgroup "TLV Decoding" $ do- func "decodeTlvStreamRaw/1-record" decodeTlvStreamRaw tlvStreamBytes1- func "decodeTlvStreamRaw/5-records" decodeTlvStreamRaw tlvStreamBytes5- func "decodeTlvStreamRaw/20-records" decodeTlvStreamRaw tlvStreamBytes20- func "decodeTlvStream/1-record" decodeTlvStream tlvStreamBytes1- func "decodeTlvStream/5-records" decodeTlvStream tlvStreamBytes5-- -- Message encoders ------------------------------------------------------------ wgroup "Message Encoders" $ do- func "encodeInit/minimal" encodeInit minimalInit- func "encodeInit/with-features" encodeInit initWithFeatures- func "encodeInit/with-tlvs" encodeInit initWithTlvs- func "encodeError" encodeError errorMsg- func "encodeWarning" encodeWarning warningMsg- func "encodePing/minimal" encodePing pingMinimal- func "encodePing/with-padding" encodePing pingWithPadding- func "encodePong" encodePong pongMsg- func "encodePeerStorage" encodePeerStorage peerStorageMsg- func "encodePeerStorageRetrieval" encodePeerStorageRetrieval- peerStorageRetrievalMsg-- -- Message decoders ------------------------------------------------------------ wgroup "Message Decoders" $ do- func "decodeInit/minimal" decodeInit initMinimalBytes- func "decodeInit/with-tlvs" decodeInit initWithTlvsBytes- func "decodeError" decodeError errorBytes- func "decodeWarning" decodeWarning warningBytes- func "decodePing/minimal" decodePing pingMinimalBytes- func "decodePing/with-padding" decodePing pingWithPaddingBytes- func "decodePong" decodePong pongBytes- func "decodePeerStorage" decodePeerStorage peerStorageBytes- func "decodePeerStorageRetrieval" decodePeerStorageRetrieval- peerStorageRetrievalBytes-- -- Envelope operations --------------------------------------------------------- wgroup "Envelope Operations" $ do- func "encodeEnvelope/init" (flip encodeEnvelope Nothing) initMessage- func "encodeEnvelope/ping" (flip encodeEnvelope Nothing) pingMessage- func "decodeEnvelope" decodeEnvelope envelopeBytes+ wgroup "primitives" $ do+ func "baseline (weigh overhead)" (+ (1 :: Int)) 1+ func "encode_u16" encode_u16 0x0102+ func "encode_u64" encode_u64 0x0102030405060708+ func "decode_u64" decode_u64 "\x01\x02\x03\x04\x05\x06\x07\x08"+ func "encode_tu64" encode_tu64 0x010203+ func "encode_bigsize (9 bytes)" encode_bigsize 0x100000000+ wgroup "tlv" $ do+ func "encode_tlv_stream (21 records)" encode_tlv_stream tlvs+ func "decode_tlv_stream (21 records)"+ (decode_tlv_stream (const False)) tlvs_bytes+ wgroup "messages" $ do+ func "encode init" encode_message init_msg+ func "decode init" decode_message init_bytes+ func "encode ping" encode_message ping_msg+ func "decode ping" decode_message ping_bytes+ func "encode error (with tlvs)" encode_message error_msg+ func "decode error (with tlvs)" decode_message error_bytes
lib/Lightning/Protocol/BOLT1.hs view
@@ -6,96 +6,152 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Base protocol for the Lightning Network, per--- [BOLT #1](https://github.com/lightning/bolts/blob/master/01-messaging.md).+-- The base protocol of the Lightning Network, per+-- [BOLT #1](https://github.com/lightning/bolts/blob/master/01-messaging.md):+-- fundamental types and their encodings, the TLV format, and the+-- @init@, @error@, @warning@, @ping@, @pong@, @peer_storage@ and+-- @peer_storage_retrieval@ messages.+--+-- The fundamental types and TLV machinery here are shared by the other+-- ppad BOLT libraries. module Lightning.Protocol.BOLT1 (- -- * Message types+ -- * Messages Message(..)- , MsgType(..)- , msgTypeWord-- -- * Channel identifiers- , ChannelId- , channelId- , allChannels-- -- ** Setup messages+ , message_type , Init(..) , Error(..) , Warning(..)-- -- ** Control messages , Ping(..) , Pong(..)-- -- ** Peer storage+ , ping_response , PeerStorage(..) , PeerStorageRetrieval(..) + -- * Encoding and decoding messages+ , encode_message+ , decode_message+ , encode_envelope+ , decode_envelope+ , EncodeError(..)+ , DecodeError(..)++ -- ** Payloads+ , encode_init+ , decode_init+ , encode_error+ , decode_error+ , encode_warning+ , decode_warning+ , encode_ping+ , decode_ping+ , encode_pong+ , decode_pong+ , encode_peer_storage+ , decode_peer_storage+ , encode_peer_storage_retrieval+ , decode_peer_storage_retrieval+ -- * TLV , TlvRecord(..) , TlvStream- , unTlvStream- , tlvStream- , unsafeTlvStream+ , tlv_stream+ , un_tlv_stream+ , empty_tlv_stream+ , lookup_tlv+ , filter_tlv_stream+ , encode_tlv_stream+ , decode_tlv_stream , TlvError(..)- , encodeTlvStream- , decodeTlvStream- , decodeTlvStreamWith- , decodeTlvStreamRaw - -- ** Init TLVs- , InitTlv(..)+ -- * Fundamental types , ChainHash- , chainHash- , unChainHash-- -- * Message envelope- , Envelope(..)-- -- * Encoding- , EncodeError(..)- , encodeMessage- , encodeEnvelope-- -- * Decoding- , DecodeError(..)- , decodeMessage- , decodeEnvelope- , decodeEnvelopeWith+ , chain_hash+ , un_chain_hash+ , ChannelId+ , channel_id+ , un_channel_id+ , all_channels+ , Signature+ , signature+ , un_signature+ , Point+ , point+ , un_point+ , PaymentHash+ , payment_hash+ , un_payment_hash+ , PaymentPreimage+ , payment_preimage+ , un_payment_preimage+ , PerCommitmentSecret+ , per_commitment_secret+ , un_per_commitment_secret+ , ShortChannelId(..)+ , short_channel_id+ , scid_block_height+ , scid_tx_index+ , scid_output_index - -- * Primitive encoding- , encodeU16- , encodeU32- , encodeU64- , encodeS8- , encodeS16- , encodeS32- , encodeS64- , encodeTu16- , encodeTu32- , encodeTu64- , encodeMinSigned- , encodeBigSize+ -- ** Amounts+ , Satoshi+ , satoshi+ , un_satoshi+ , max_satoshi+ , MilliSatoshi+ , milli_satoshi+ , un_milli_satoshi+ , max_milli_satoshi+ , sat_to_msat+ , msat_to_sat+ , add_sat+ , sub_sat+ , add_msat+ , sub_msat - -- * Primitive decoding- , decodeU16- , decodeU32- , decodeU64- , decodeS8- , decodeS16- , decodeS32- , decodeS64- , decodeTu16- , decodeTu32- , decodeTu64- , decodeMinSigned- , decodeBigSize+ -- * Primitive encodings+ -- | Decoders return the decoded value and the remaining input, except+ -- for the truncated integers, which occupy their whole input.+ , encode_u16+ , encode_u32+ , encode_u64+ , decode_u16+ , decode_u32+ , decode_u64+ , encode_s8+ , encode_s16+ , encode_s32+ , encode_s64+ , decode_s8+ , decode_s16+ , decode_s32+ , decode_s64+ , encode_tu16+ , encode_tu32+ , encode_tu64+ , decode_tu16+ , decode_tu32+ , decode_tu64+ , encode_bigsize+ , decode_bigsize+ , encode_u16_prefixed+ , decode_u16_prefixed+ , decode_chain_hash+ , decode_channel_id+ , decode_signature+ , decode_point+ , decode_payment_hash+ , decode_payment_preimage+ , decode_per_commitment_secret+ , encode_short_channel_id+ , decode_short_channel_id+ , encode_satoshi+ , decode_satoshi+ , encode_milli_satoshi+ , decode_milli_satoshi ) where --- Re-export from sub-modules+import Lightning.Protocol.BOLT1.Codec+import Lightning.Protocol.BOLT1.Message import Lightning.Protocol.BOLT1.Prim import Lightning.Protocol.BOLT1.TLV-import Lightning.Protocol.BOLT1.Message-import Lightning.Protocol.BOLT1.Codec
lib/Lightning/Protocol/BOLT1/Codec.hs view
@@ -1,8 +1,6 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE LambdaCase #-} -- | -- Module: Lightning.Protocol.BOLT1.Codec@@ -10,340 +8,277 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Message encoding and decoding for BOLT #1.+-- Encoding and decoding of BOLT #1 messages. module Lightning.Protocol.BOLT1.Codec (- -- * Encoding errors EncodeError(..)+ , DecodeError(..) - -- * Message encoding- , encodeInit- , encodeError- , encodeWarning- , encodePing- , encodePong- , encodePeerStorage- , encodePeerStorageRetrieval- , encodeMessage- , encodeEnvelope+ , encode_envelope+ , decode_envelope - -- * Decoding errors- , DecodeError(..)+ , encode_message+ , decode_message - -- * Message decoding- , decodeInit- , decodeError- , decodeWarning- , decodePing- , decodePong- , decodePeerStorage- , decodePeerStorageRetrieval- , decodeMessage- , decodeEnvelope- , decodeEnvelopeWith+ , encode_init+ , decode_init+ , encode_error+ , decode_error+ , encode_warning+ , decode_warning+ , encode_ping+ , decode_ping+ , encode_pong+ , decode_pong+ , encode_peer_storage+ , decode_peer_storage+ , encode_peer_storage_retrieval+ , decode_peer_storage_retrieval++ , ping_response ) where import Control.DeepSeq (NFData)-import Control.Monad (when, unless) import qualified Data.ByteString as BS import Data.Word (Word16, Word64) import GHC.Generics (Generic)+import Lightning.Protocol.BOLT1.Message import Lightning.Protocol.BOLT1.Prim import Lightning.Protocol.BOLT1.TLV-import Lightning.Protocol.BOLT1.Message---- Encoding errors -------------------------------------------------------------+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- | Encoding errors.+-- | Why a message failed to encode. data EncodeError- = EncodeLengthOverflow -- ^ Field length exceeds u16 max (65535 bytes)- | EncodeMessageTooLarge -- ^ Total message size exceeds 65535 bytes- deriving stock (Eq, Show, Generic)+ = EncodeLengthOverflow+ -- ^ a length-prefixed field exceeds 65535 bytes+ | EncodeMessageTooLarge+ -- ^ the message (type and payload) exceeds 65535 bytes+ | EncodeInvalidTlvs+ -- ^ a record in a @_tlvs@ field duplicates a typed field+ deriving (Eq, Show, Generic) instance NFData EncodeError --- Message encoding ------------------------------------------------------------+-- | Why a message failed to decode.+data DecodeError+ = DecodeInsufficientBytes+ -- ^ the input ended before a field did+ | DecodeTlvError !TlvError+ -- ^ the message's TLV stream is malformed+ | DecodeInvalidTlvValue !Word64+ -- ^ a known TLV record (of the given type) has a malformed value+ | DecodeUnknownEvenType !Word16+ -- ^ an unknown even message type; the connection must be closed+ | DecodeUnknownOddType !Word16+ -- ^ an unknown odd message type; the message must be ignored+ deriving (Eq, Show, Generic) --- | Encode an Init message payload.-encodeInit :: Init -> Either EncodeError BS.ByteString-encodeInit (Init gf feat tlvs) = do- gfLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength gf)- featLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength feat)- Right $ mconcat- [ gfLen- , gf- , featLen- , feat- , encodeTlvStream (encodeInitTlvs tlvs)- ]+instance NFData DecodeError --- | Encode an Error message payload.-encodeError :: Error -> Either EncodeError BS.ByteString-encodeError (Error cid dat) = do- datLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength dat)- Right $ mconcat [unChannelId cid, datLen, dat]+-- framing -------------------------------------------------------------------- --- | Encode a Warning message payload.-encodeWarning :: Warning -> Either EncodeError BS.ByteString-encodeWarning (Warning cid dat) = do- datLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength dat)- Right $ mconcat [unChannelId cid, datLen, dat]+-- | Prefix a payload with its message type. Fails if the result+-- exceeds the 65535-byte message limit.+--+-- >>> encode_envelope 19 "\NUL\NUL"+-- Right "\NUL\DC3\NUL\NUL"+encode_envelope :: Word16 -> BS.ByteString -> Either EncodeError BS.ByteString+encode_envelope t payload+ | BS.length payload > 65533 = Left EncodeMessageTooLarge+ | otherwise = Right (encode_u16 t <> payload) --- | Encode a Ping message payload.-encodePing :: Ping -> Either EncodeError BS.ByteString-encodePing (Ping numPong ignored) = do- ignoredLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength ignored)- Right $ mconcat [encodeU16 numPong, ignoredLen, ignored]+-- | Split a message into its type and payload.+--+-- >>> decode_envelope "\NUL\DC3\NUL\NUL"+-- Right (19,"\NUL\NUL")+decode_envelope :: BS.ByteString -> Either DecodeError (Word16, BS.ByteString)+decode_envelope bs = case decode_u16 bs of+ Just r -> Right r+ Nothing -> Left DecodeInsufficientBytes --- | Encode a Pong message payload.-encodePong :: Pong -> Either EncodeError BS.ByteString-encodePong (Pong ignored) = do- ignoredLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength ignored)- Right $ mconcat [ignoredLen, ignored]+-- | Encode a BOLT #1 message, including its type.+--+-- >>> encode_message (MsgPong (Pong "" empty_tlv_stream))+-- Right "\NUL\DC3\NUL\NUL"+encode_message :: Message -> Either EncodeError BS.ByteString+encode_message m = do+ payload <- case m of+ MsgInit a -> encode_init a+ MsgError a -> encode_error a+ MsgWarning a -> encode_warning a+ MsgPing a -> encode_ping a+ MsgPong a -> encode_pong a+ MsgPeerStorage a -> encode_peer_storage a+ MsgPeerStorageRetrieval a -> encode_peer_storage_retrieval a+ encode_envelope (message_type m) payload --- | Encode a PeerStorage message payload.-encodePeerStorage :: PeerStorage -> Either EncodeError BS.ByteString-encodePeerStorage (PeerStorage blob) = do- blobLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength blob)- Right $ mconcat [blobLen, blob]+-- | Decode a BOLT #1 message, including its type.+--+-- A type that BOLT #1 doesn't define yields 'DecodeUnknownEvenType'+-- or 'DecodeUnknownOddType'; callers dispatching messages from other+-- BOLTs should use 'decode_envelope' first.+--+-- >>> decode_message "\NUL\DC3\NUL\NUL"+-- Right (MsgPong (Pong {pong_ignored = "", pong_tlvs = TlvStream []}))+-- >>> decode_message "\NUL\DC4\NUL\NUL"+-- Left (DecodeUnknownEvenType 20)+decode_message :: BS.ByteString -> Either DecodeError Message+decode_message bs = do+ (t, payload) <- decode_envelope bs+ case t of+ 16 -> MsgInit <$> decode_init payload+ 17 -> MsgError <$> decode_error payload+ 1 -> MsgWarning <$> decode_warning payload+ 18 -> MsgPing <$> decode_ping payload+ 19 -> MsgPong <$> decode_pong payload+ 7 -> MsgPeerStorage <$> decode_peer_storage payload+ 9 -> MsgPeerStorageRetrieval <$> decode_peer_storage_retrieval payload+ _ | even t -> Left (DecodeUnknownEvenType t)+ | otherwise -> Left (DecodeUnknownOddType t) --- | Encode a PeerStorageRetrieval message payload.-encodePeerStorageRetrieval- :: PeerStorageRetrieval -> Either EncodeError BS.ByteString-encodePeerStorageRetrieval (PeerStorageRetrieval blob) = do- blobLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength blob)- Right $ mconcat [blobLen, blob]+-- helpers -------------------------------------------------------------------- --- | Encode a message to its payload bytes.------ Checks that the payload does not exceed 65533 bytes (the maximum--- possible given the 2-byte type field and 65535-byte message limit).-encodeMessage :: Message -> Either EncodeError BS.ByteString-encodeMessage msg = do- payload <- case msg of- MsgInitVal m -> encodeInit m- MsgErrorVal m -> encodeError m- MsgWarningVal m -> encodeWarning m- MsgPingVal m -> encodePing m- MsgPongVal m -> encodePong m- MsgPeerStorageVal m -> encodePeerStorage m- MsgPeerStorageRetrievalVal m -> encodePeerStorageRetrieval m- -- Payload must leave room for 2-byte type (max 65533 bytes)- when (BS.length payload > 65533) $- Left EncodeMessageTooLarge- Right payload+prefixed :: BS.ByteString -> Either EncodeError BS.ByteString+prefixed bs = maybe (Left EncodeLengthOverflow) Right (encode_u16_prefixed bs) --- | Encode a message as a complete envelope (type + payload + extension).------ Per BOLT #1, the total message size must not exceed 65535 bytes.-encodeEnvelope :: Message -> Maybe TlvStream -> Either EncodeError BS.ByteString-encodeEnvelope msg mext = do- payload <- encodeMessage msg- let !typeBytes = encodeU16 (msgTypeWord (messageType msg))- !extBytes = maybe BS.empty encodeTlvStream mext- !result = mconcat [typeBytes, payload, extBytes]- -- Per BOLT #1: message size must fit in 2 bytes (max 65535)- when (BS.length result > 65535) $- Left EncodeMessageTooLarge- Right result+need :: Maybe a -> Either DecodeError a+need = maybe (Left DecodeInsufficientBytes) Right+{-# INLINE need #-} --- Decoding errors -------------------------------------------------------------+-- an extension stream in which no types are known+extension :: BS.ByteString -> Either DecodeError TlvStream+extension bs = either (Left . DecodeTlvError) Right+ (decode_tlv_stream (const False) bs) --- | Decoding errors.-data DecodeError- = DecodeInsufficientBytes- | DecodeInvalidLength- | DecodeUnknownEvenType !Word16- | DecodeUnknownOddType !Word16- | DecodeTlvError !TlvError- | DecodeInvalidChannelId- | DecodeInvalidExtension !TlvError- deriving stock (Eq, Show, Generic)+-- init ----------------------------------------------------------------------- -instance NFData DecodeError+-- | Encode an 'Init' payload.+encode_init :: Init -> Either EncodeError BS.ByteString+encode_init (Init gf f nets addr tlvs) = do+ gf' <- prefixed (BOLT9.render gf)+ f' <- prefixed (BOLT9.render f)+ let known = maybe [] (\cs -> [TlvRecord 1 (mconcat (map un_chain_hash cs))])+ nets+ <> maybe [] (\a -> [TlvRecord 3 a]) addr+ stream <- maybe (Left EncodeInvalidTlvs) Right+ (tlv_stream (known <> un_tlv_stream tlvs))+ pure (gf' <> f' <> encode_tlv_stream stream) --- Message decoding ------------------------------------------------------------+-- | Decode an 'Init' payload.+decode_init :: BS.ByteString -> Either DecodeError Init+decode_init bs = do+ (gf, r0) <- need (decode_u16_prefixed bs)+ (f, r1) <- need (decode_u16_prefixed r0)+ stream <- either (Left . DecodeTlvError) Right+ (decode_tlv_stream (\t -> t == 1 || t == 3) r1)+ nets <- traverse networks (lookup_tlv 1 stream)+ pure Init+ { init_global_features = BOLT9.parse gf+ , init_features = BOLT9.parse f+ , init_networks = nets+ , init_remote_addr = lookup_tlv 3 stream+ , init_tlvs = filter_tlv_stream (\t -> t /= 1 && t /= 3) stream+ }+ where+ networks v+ | BS.length v `rem` 32 /= 0 = Left (DecodeInvalidTlvValue 1)+ | otherwise = Right (chunks v)+ chunks !v = case decode_chain_hash v of+ Just (h, rest) -> h : chunks rest+ Nothing -> [] --- | Decode an Init message from payload bytes.------ Returns the decoded message and any remaining bytes.-decodeInit :: BS.ByteString -> Either DecodeError (Init, BS.ByteString)-decodeInit !bs = do- (gfLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 bs)- unless (BS.length rest1 >= fromIntegral gfLen) $- Left DecodeInsufficientBytes- let !gf = BS.take (fromIntegral gfLen) rest1- !rest2 = BS.drop (fromIntegral gfLen) rest1- (fLen, rest3) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 rest2)- unless (BS.length rest3 >= fromIntegral fLen) $- Left DecodeInsufficientBytes- let !feat = BS.take (fromIntegral fLen) rest3- !rest4 = BS.drop (fromIntegral fLen) rest3- -- Parse optional TLV stream (consumes all remaining bytes for init)- tlvs <- if BS.null rest4- then Right (unsafeTlvStream [])- else either (Left . DecodeTlvError) Right (decodeTlvStream rest4)- initTlvList <- either (Left . DecodeTlvError) Right- (parseInitTlvs tlvs)- -- Init consumes all bytes (TLVs are part of init, not extensions)- Right (Init gf feat initTlvList, BS.empty)+-- error, warning ------------------------------------------------------------- --- | Decode an Error message from payload bytes.-decodeError :: BS.ByteString -> Either DecodeError (Error, BS.ByteString)-decodeError !bs = do- unless (BS.length bs >= 32) $ Left DecodeInsufficientBytes- let !cidBytes = BS.take 32 bs- !rest1 = BS.drop 32 bs- cid <- maybe (Left DecodeInvalidChannelId) Right (channelId cidBytes)- (dLen, rest2) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 rest1)- unless (BS.length rest2 >= fromIntegral dLen) $- Left DecodeInsufficientBytes- let !dat = BS.take (fromIntegral dLen) rest2- !rest3 = BS.drop (fromIntegral dLen) rest2- Right (Error cid dat, rest3)+-- | Encode an 'Error' payload.+encode_error :: Error -> Either EncodeError BS.ByteString+encode_error (Error cid dat tlvs) = do+ dat' <- prefixed dat+ pure (un_channel_id cid <> dat' <> encode_tlv_stream tlvs) --- | Decode a Warning message from payload bytes.-decodeWarning :: BS.ByteString -> Either DecodeError (Warning, BS.ByteString)-decodeWarning !bs = do- unless (BS.length bs >= 32) $ Left DecodeInsufficientBytes- let !cidBytes = BS.take 32 bs- !rest1 = BS.drop 32 bs- cid <- maybe (Left DecodeInvalidChannelId) Right (channelId cidBytes)- (dLen, rest2) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 rest1)- unless (BS.length rest2 >= fromIntegral dLen) $- Left DecodeInsufficientBytes- let !dat = BS.take (fromIntegral dLen) rest2- !rest3 = BS.drop (fromIntegral dLen) rest2- Right (Warning cid dat, rest3)+-- | Decode an 'Error' payload.+decode_error :: BS.ByteString -> Either DecodeError Error+decode_error bs = do+ (cid, r0) <- need (decode_channel_id bs)+ (dat, r1) <- need (decode_u16_prefixed r0)+ Error cid dat <$> extension r1 --- | Decode a Ping message from payload bytes.-decodePing :: BS.ByteString -> Either DecodeError (Ping, BS.ByteString)-decodePing !bs = do- (numPong, rest1) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 bs)- (bLen, rest2) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 rest1)- unless (BS.length rest2 >= fromIntegral bLen) $- Left DecodeInsufficientBytes- let !ignored = BS.take (fromIntegral bLen) rest2- !rest3 = BS.drop (fromIntegral bLen) rest2- Right (Ping numPong ignored, rest3)+-- | Encode a 'Warning' payload.+encode_warning :: Warning -> Either EncodeError BS.ByteString+encode_warning (Warning cid dat tlvs) = do+ dat' <- prefixed dat+ pure (un_channel_id cid <> dat' <> encode_tlv_stream tlvs) --- | Decode a Pong message from payload bytes.-decodePong :: BS.ByteString -> Either DecodeError (Pong, BS.ByteString)-decodePong !bs = do- (bLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 bs)- unless (BS.length rest1 >= fromIntegral bLen) $- Left DecodeInsufficientBytes- let !ignored = BS.take (fromIntegral bLen) rest1- !rest2 = BS.drop (fromIntegral bLen) rest1- Right (Pong ignored, rest2)+-- | Decode a 'Warning' payload.+decode_warning :: BS.ByteString -> Either DecodeError Warning+decode_warning bs = do+ (cid, r0) <- need (decode_channel_id bs)+ (dat, r1) <- need (decode_u16_prefixed r0)+ Warning cid dat <$> extension r1 --- | Decode a PeerStorage message from payload bytes.-decodePeerStorage- :: BS.ByteString -> Either DecodeError (PeerStorage, BS.ByteString)-decodePeerStorage !bs = do- (bLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 bs)- unless (BS.length rest1 >= fromIntegral bLen) $- Left DecodeInsufficientBytes- let !blob = BS.take (fromIntegral bLen) rest1- !rest2 = BS.drop (fromIntegral bLen) rest1- Right (PeerStorage blob, rest2)+-- ping, pong ----------------------------------------------------------------- --- | Decode a PeerStorageRetrieval message from payload bytes.-decodePeerStorageRetrieval- :: BS.ByteString- -> Either DecodeError (PeerStorageRetrieval, BS.ByteString)-decodePeerStorageRetrieval !bs = do- (bLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 bs)- unless (BS.length rest1 >= fromIntegral bLen) $- Left DecodeInsufficientBytes- let !blob = BS.take (fromIntegral bLen) rest1- !rest2 = BS.drop (fromIntegral bLen) rest1- Right (PeerStorageRetrieval blob, rest2)+-- | Encode a 'Ping' payload.+encode_ping :: Ping -> Either EncodeError BS.ByteString+encode_ping (Ping n ign tlvs) = do+ ign' <- prefixed ign+ pure (encode_u16 n <> ign' <> encode_tlv_stream tlvs) --- | Decode a message from its type and payload.------ Returns the decoded message and any remaining bytes (for extensions).--- For unknown types, returns an appropriate error.-decodeMessage- :: MsgType -> BS.ByteString -> Either DecodeError (Message, BS.ByteString)-decodeMessage MsgInit bs = do- (m, rest) <- decodeInit bs- Right (MsgInitVal m, rest)-decodeMessage MsgError bs = do- (m, rest) <- decodeError bs- Right (MsgErrorVal m, rest)-decodeMessage MsgWarning bs = do- (m, rest) <- decodeWarning bs- Right (MsgWarningVal m, rest)-decodeMessage MsgPing bs = do- (m, rest) <- decodePing bs- Right (MsgPingVal m, rest)-decodeMessage MsgPong bs = do- (m, rest) <- decodePong bs- Right (MsgPongVal m, rest)-decodeMessage MsgPeerStorage bs = do- (m, rest) <- decodePeerStorage bs- Right (MsgPeerStorageVal m, rest)-decodeMessage MsgPeerStorageRet bs = do- (m, rest) <- decodePeerStorageRetrieval bs- Right (MsgPeerStorageRetrievalVal m, rest)-decodeMessage (MsgUnknown w) _- | even w = Left (DecodeUnknownEvenType w)- | otherwise = Left (DecodeUnknownOddType w)+-- | Decode a 'Ping' payload.+decode_ping :: BS.ByteString -> Either DecodeError Ping+decode_ping bs = do+ (n, r0) <- need (decode_u16 bs)+ (ign, r1) <- need (decode_u16_prefixed r0)+ Ping n ign <$> extension r1 --- | Decode a complete envelope (type + payload + optional extension).------ Per BOLT #1:--- - Unknown odd message types are ignored (returns Nothing for message)--- - Unknown even message types cause connection close (returns error)--- - Invalid extension TLV causes connection close (returns error)------ This uses the default policy of treating all extension TLV types as--- unknown. Use 'decodeEnvelopeWith' for configurable extension handling.------ Returns the decoded message (if known) and any extension TLVs.-decodeEnvelope- :: BS.ByteString- -> Either DecodeError (Maybe Message, Maybe TlvStream)-decodeEnvelope = decodeEnvelopeWith (const False)+-- | Encode a 'Pong' payload.+encode_pong :: Pong -> Either EncodeError BS.ByteString+encode_pong (Pong ign tlvs) = do+ ign' <- prefixed ign+ pure (ign' <> encode_tlv_stream tlvs) --- | Decode a complete envelope with configurable extension TLV handling.------ The predicate determines which extension TLV types are "known" and--- should be preserved. Unknown even types cause failure; unknown odd--- types are skipped.------ Use @decodeEnvelopeWith (const False)@ to reject all even extension--- types (the default behavior of 'decodeEnvelope').+-- | Decode a 'Pong' payload.+decode_pong :: BS.ByteString -> Either DecodeError Pong+decode_pong bs = do+ (ign, r0) <- need (decode_u16_prefixed bs)+ Pong ign <$> extension r0++-- | The 'Pong' a node must send in reply to a 'Ping', if any. Per+-- BOLT #1, a ping requesting 65532 or more bytes is ignored. ----- Use @decodeEnvelopeWith (const True)@ to accept all extension types.-decodeEnvelopeWith- :: (Word64 -> Bool) -- ^ Predicate: is this extension TLV type known?- -> BS.ByteString- -> Either DecodeError (Maybe Message, Maybe TlvStream)-decodeEnvelopeWith isKnownExt !bs = do- (typeWord, rest1) <- maybe (Left DecodeInsufficientBytes) Right- (decodeU16 bs)- let !msgType = parseMsgType typeWord- case msgType of- MsgUnknown w- | even w -> Left (DecodeUnknownEvenType w)- | otherwise -> Right (Nothing, Nothing) -- Ignore unknown odd types- _ -> do- (msg, rest2) <- decodeMessage msgType rest1- -- Parse any remaining bytes as extension TLV- ext <- if BS.null rest2- then Right Nothing- else case decodeTlvStreamWith isKnownExt rest2 of- Left e -> Left (DecodeInvalidExtension e)- Right s -> Right (Just s)- Right (Just msg, ext)+-- >>> ping_response (Ping 4 "" empty_tlv_stream)+-- Just (Pong {pong_ignored = "\NUL\NUL\NUL\NUL", pong_tlvs = TlvStream []})+-- >>> ping_response (Ping 65532 "" empty_tlv_stream)+-- Nothing+ping_response :: Ping -> Maybe Pong+ping_response (Ping n _ _)+ | n >= 65532 = Nothing+ | otherwise =+ Just (Pong (BS.replicate (fromIntegral n) 0x00) empty_tlv_stream)++-- peer storage ---------------------------------------------------------------++-- | Encode a 'PeerStorage' payload.+encode_peer_storage :: PeerStorage -> Either EncodeError BS.ByteString+encode_peer_storage (PeerStorage blob tlvs) = do+ blob' <- prefixed blob+ pure (blob' <> encode_tlv_stream tlvs)++-- | Decode a 'PeerStorage' payload.+decode_peer_storage :: BS.ByteString -> Either DecodeError PeerStorage+decode_peer_storage bs = do+ (blob, r0) <- need (decode_u16_prefixed bs)+ PeerStorage blob <$> extension r0++-- | Encode a 'PeerStorageRetrieval' payload.+encode_peer_storage_retrieval+ :: PeerStorageRetrieval -> Either EncodeError BS.ByteString+encode_peer_storage_retrieval (PeerStorageRetrieval blob tlvs) = do+ blob' <- prefixed blob+ pure (blob' <> encode_tlv_stream tlvs)++-- | Decode a 'PeerStorageRetrieval' payload.+decode_peer_storage_retrieval+ :: BS.ByteString -> Either DecodeError PeerStorageRetrieval+decode_peer_storage_retrieval bs = do+ (blob, r0) <- need (decode_u16_prefixed bs)+ PeerStorageRetrieval blob <$> extension r0
lib/Lightning/Protocol/BOLT1/Message.hs view
@@ -1,6 +1,5 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-} -- | -- Module: Lightning.Protocol.BOLT1.Message@@ -8,206 +7,126 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Message types for BOLT #1.+-- The messages defined by BOLT #1. module Lightning.Protocol.BOLT1.Message (- -- * Message types- MsgType(..)- , msgTypeWord- , parseMsgType-- -- * Channel identifiers- , ChannelId- , channelId- , unChannelId- , allChannels-- -- * Setup messages- , Init(..)+ Init(..) , Error(..) , Warning(..)-- -- * Control messages , Ping(..) , Pong(..)-- -- * Peer storage messages , PeerStorage(..) , PeerStorageRetrieval(..)-- -- * Message envelope , Message(..)- , messageType- , Envelope(..)+ , message_type ) where import Control.DeepSeq (NFData) import qualified Data.ByteString as BS import Data.Word (Word16) import GHC.Generics (Generic)+import Lightning.Protocol.BOLT1.Prim import Lightning.Protocol.BOLT1.TLV---- Message types ------------------------------------------------------------------- | BOLT #1 message type codes.-data MsgType- = MsgInit -- ^ 16- | MsgError -- ^ 17- | MsgPing -- ^ 18- | MsgPong -- ^ 19- | MsgWarning -- ^ 1- | MsgPeerStorage -- ^ 7- | MsgPeerStorageRet -- ^ 9- | MsgUnknown !Word16 -- ^ Unknown type- deriving stock (Eq, Show, Generic)--instance NFData MsgType---- | Get the numeric type code for a message type.-msgTypeWord :: MsgType -> Word16-msgTypeWord MsgInit = 16-msgTypeWord MsgError = 17-msgTypeWord MsgPing = 18-msgTypeWord MsgPong = 19-msgTypeWord MsgWarning = 1-msgTypeWord MsgPeerStorage = 7-msgTypeWord MsgPeerStorageRet = 9-msgTypeWord (MsgUnknown w) = w---- | Parse a message type from a word.-parseMsgType :: Word16 -> MsgType-parseMsgType 16 = MsgInit-parseMsgType 17 = MsgError-parseMsgType 18 = MsgPing-parseMsgType 19 = MsgPong-parseMsgType 1 = MsgWarning-parseMsgType 7 = MsgPeerStorage-parseMsgType 9 = MsgPeerStorageRet-parseMsgType w = MsgUnknown w---- Channel identifiers ------------------------------------------------------------- | A 32-byte channel identifier.------ Use 'channelId' to construct, which validates the length.--- Use 'allChannels' for connection-level errors (all-zeros channel ID).-newtype ChannelId = ChannelId BS.ByteString- deriving stock (Eq, Show, Generic)--instance NFData ChannelId---- | Construct a 'ChannelId' from a 32-byte 'BS.ByteString'.------ Returns 'Nothing' if the input is not exactly 32 bytes.------ >>> channelId (BS.replicate 32 0x00)--- Just (ChannelId "\NUL\NUL...")--- >>> channelId "too short"--- Nothing-channelId :: BS.ByteString -> Maybe ChannelId-channelId bs- | BS.length bs == 32 = Just (ChannelId bs)- | otherwise = Nothing-{-# INLINE channelId #-}+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- | The all-zeros channel ID, used for connection-level errors.+-- | The @init@ message (type 16). ----- Per BOLT #1, setting channel_id to all zeros means the error applies--- to the connection rather than a specific channel.-allChannels :: ChannelId-allChannels = ChannelId (BS.replicate 32 0x00)---- | Extract the raw bytes from a 'ChannelId'.-unChannelId :: ChannelId -> BS.ByteString-unChannelId (ChannelId bs) = bs-{-# INLINE unChannelId #-}---- Message ADTs -------------------------------------------------------------------- | The init message (type 16).+-- The two feature vectors are kept as received. Per BOLT #1, a+-- receiver treats their union ('BOLT9.union') as the peer's+-- features. data Init = Init- { initGlobalFeatures :: !BS.ByteString- , initFeatures :: !BS.ByteString- , initTlvs :: ![InitTlv]- } deriving stock (Eq, Show, Generic)+ { init_global_features :: !BOLT9.FeatureVector+ , init_features :: !BOLT9.FeatureVector+ , init_networks :: !(Maybe [ChainHash])+ -- ^ @networks@ (TLV type 1): chains the node is interested in+ , init_remote_addr :: !(Maybe BS.ByteString)+ -- ^ @remote_addr@ (TLV type 3): a raw BOLT #7 address descriptor+ , init_tlvs :: !TlvStream+ -- ^ unknown odd TLV records+ } deriving (Eq, Show, Generic) instance NFData Init --- | The error message (type 17).+-- | The @error@ message (type 17). data Error = Error- { errorChannelId :: !ChannelId- , errorData :: !BS.ByteString- } deriving stock (Eq, Show, Generic)+ { error_channel_id :: !ChannelId+ , error_data :: !BS.ByteString+ , error_tlvs :: !TlvStream+ -- ^ extension TLV records (all unknown odd)+ } deriving (Eq, Show, Generic) instance NFData Error --- | The warning message (type 1).+-- | The @warning@ message (type 1). data Warning = Warning- { warningChannelId :: !ChannelId- , warningData :: !BS.ByteString- } deriving stock (Eq, Show, Generic)+ { warning_channel_id :: !ChannelId+ , warning_data :: !BS.ByteString+ , warning_tlvs :: !TlvStream+ -- ^ extension TLV records (all unknown odd)+ } deriving (Eq, Show, Generic) instance NFData Warning --- | The ping message (type 18).+-- | The @ping@ message (type 18). data Ping = Ping- { pingNumPongBytes :: {-# UNPACK #-} !Word16- , pingIgnored :: !BS.ByteString- } deriving stock (Eq, Show, Generic)+ { ping_num_pong_bytes :: {-# UNPACK #-} !Word16+ , ping_ignored :: !BS.ByteString+ , ping_tlvs :: !TlvStream+ -- ^ extension TLV records (all unknown odd)+ } deriving (Eq, Show, Generic) instance NFData Ping --- | The pong message (type 19).+-- | The @pong@ message (type 19). data Pong = Pong- { pongIgnored :: !BS.ByteString- } deriving stock (Eq, Show, Generic)+ { pong_ignored :: !BS.ByteString+ , pong_tlvs :: !TlvStream+ -- ^ extension TLV records (all unknown odd)+ } deriving (Eq, Show, Generic) instance NFData Pong --- | The peer_storage message (type 7).+-- | The @peer_storage@ message (type 7). data PeerStorage = PeerStorage- { peerStorageBlob :: !BS.ByteString- } deriving stock (Eq, Show, Generic)+ { peer_storage_blob :: !BS.ByteString+ , peer_storage_tlvs :: !TlvStream+ -- ^ extension TLV records (all unknown odd)+ } deriving (Eq, Show, Generic) instance NFData PeerStorage --- | The peer_storage_retrieval message (type 9).+-- | The @peer_storage_retrieval@ message (type 9). data PeerStorageRetrieval = PeerStorageRetrieval- { peerStorageRetrievalBlob :: !BS.ByteString- } deriving stock (Eq, Show, Generic)+ { peer_storage_retrieval_blob :: !BS.ByteString+ , peer_storage_retrieval_tlvs :: !TlvStream+ -- ^ extension TLV records (all unknown odd)+ } deriving (Eq, Show, Generic) instance NFData PeerStorageRetrieval --- | All BOLT #1 messages.+-- | A BOLT #1 message. data Message- = MsgInitVal !Init- | MsgErrorVal !Error- | MsgWarningVal !Warning- | MsgPingVal !Ping- | MsgPongVal !Pong- | MsgPeerStorageVal !PeerStorage- | MsgPeerStorageRetrievalVal !PeerStorageRetrieval- deriving stock (Eq, Show, Generic)+ = MsgInit !Init+ | MsgError !Error+ | MsgWarning !Warning+ | MsgPing !Ping+ | MsgPong !Pong+ | MsgPeerStorage !PeerStorage+ | MsgPeerStorageRetrieval !PeerStorageRetrieval+ deriving (Eq, Show, Generic) instance NFData Message --- | Get the message type for a message.-messageType :: Message -> MsgType-messageType (MsgInitVal _) = MsgInit-messageType (MsgErrorVal _) = MsgError-messageType (MsgWarningVal _) = MsgWarning-messageType (MsgPingVal _) = MsgPing-messageType (MsgPongVal _) = MsgPong-messageType (MsgPeerStorageVal _) = MsgPeerStorage-messageType (MsgPeerStorageRetrievalVal _) = MsgPeerStorageRet---- Message envelope ---------------------------------------------------------------- | A complete message envelope with type, payload, and optional extension.-data Envelope = Envelope- { envType :: !MsgType- , envPayload :: !BS.ByteString- , envExtension :: !(Maybe TlvStream)- } deriving stock (Eq, Show, Generic)--instance NFData Envelope+-- | The wire type of a 'Message'.+--+-- >>> message_type (MsgPong (Pong "" empty_tlv_stream))+-- 19+message_type :: Message -> Word16+message_type m = case m of+ MsgInit {} -> 16+ MsgError {} -> 17+ MsgWarning {} -> 1+ MsgPing {} -> 18+ MsgPong {} -> 19+ MsgPeerStorage {} -> 7+ MsgPeerStorageRetrieval {} -> 9
lib/Lightning/Protocol/BOLT1/Prim.hs view
@@ -1,527 +1,804 @@-{-# OPTIONS_HADDOCK prune #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}---- |--- Module: Lightning.Protocol.BOLT1.Prim--- Copyright: (c) 2025 Jared Tobin--- License: MIT--- Maintainer: Jared Tobin <jared@ppad.tech>------ Primitive type encoding and decoding for BOLT #1.--module Lightning.Protocol.BOLT1.Prim (- -- * Chain hash- ChainHash- , chainHash- , unChainHash-- -- * Unsigned integer encoding- , encodeU16- , encodeU32- , encodeU64-- -- * Signed integer encoding- , encodeS8- , encodeS16- , encodeS32- , encodeS64-- -- * Truncated unsigned integer encoding- , encodeTu16- , encodeTu32- , encodeTu64-- -- * Minimal signed integer encoding- , encodeMinSigned-- -- * BigSize encoding- , encodeBigSize-- -- * Unsigned integer decoding- , decodeU16- , decodeU32- , decodeU64-- -- * Signed integer decoding- , decodeS8- , decodeS16- , decodeS32- , decodeS64-- -- * Truncated unsigned integer decoding- , decodeTu16- , decodeTu32- , decodeTu64-- -- * Minimal signed integer decoding- , decodeMinSigned-- -- * BigSize decoding- , decodeBigSize-- -- * Internal helpers- , encodeLength- ) where--import Control.DeepSeq (NFData)-import Data.Bits (unsafeShiftL, unsafeShiftR, (.|.))-import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as BSB-import qualified Data.ByteString.Lazy as BSL-import Data.Int (Int8, Int16, Int32, Int64)-import Data.Word (Word16, Word32, Word64)-import GHC.Generics (Generic)---- Chain hash ---------------------------------------------------------------------- | A chain hash (32-byte hash identifying a blockchain).-newtype ChainHash = ChainHash BS.ByteString- deriving stock (Eq, Show, Generic)--instance NFData ChainHash---- | Construct a chain hash from a 32-byte bytestring.------ Returns 'Nothing' if the input is not exactly 32 bytes.-chainHash :: BS.ByteString -> Maybe ChainHash-chainHash bs- | BS.length bs == 32 = Just (ChainHash bs)- | otherwise = Nothing-{-# INLINE chainHash #-}---- | Extract the raw bytes from a chain hash.-unChainHash :: ChainHash -> BS.ByteString-unChainHash (ChainHash bs) = bs-{-# INLINE unChainHash #-}---- Unsigned integer encoding ------------------------------------------------------- | Encode a 16-bit unsigned integer (big-endian).------ >>> encodeU16 0x0102--- "\SOH\STX"-encodeU16 :: Word16 -> BS.ByteString-encodeU16 = BSL.toStrict . BSB.toLazyByteString . BSB.word16BE-{-# INLINE encodeU16 #-}---- | Encode a 32-bit unsigned integer (big-endian).------ >>> encodeU32 0x01020304--- "\SOH\STX\ETX\EOT"-encodeU32 :: Word32 -> BS.ByteString-encodeU32 = BSL.toStrict . BSB.toLazyByteString . BSB.word32BE-{-# INLINE encodeU32 #-}---- | Encode a 64-bit unsigned integer (big-endian).------ >>> encodeU64 0x0102030405060708--- "\SOH\STX\ETX\EOT\ENQ\ACK\a\b"-encodeU64 :: Word64 -> BS.ByteString-encodeU64 = BSL.toStrict . BSB.toLazyByteString . BSB.word64BE-{-# INLINE encodeU64 #-}---- Signed integer encoding --------------------------------------------------------- | Encode an 8-bit signed integer.------ >>> encodeS8 42--- "*"--- >>> encodeS8 (-42)--- "\214"-encodeS8 :: Int8 -> BS.ByteString-encodeS8 = BS.singleton . fromIntegral-{-# INLINE encodeS8 #-}---- | Encode a 16-bit signed integer (big-endian two's complement).------ >>> encodeS16 0x0102--- "\SOH\STX"--- >>> encodeS16 (-1)--- "\255\255"-encodeS16 :: Int16 -> BS.ByteString-encodeS16 = BSL.toStrict . BSB.toLazyByteString . BSB.int16BE-{-# INLINE encodeS16 #-}---- | Encode a 32-bit signed integer (big-endian two's complement).------ >>> encodeS32 0x01020304--- "\SOH\STX\ETX\EOT"--- >>> encodeS32 (-1)--- "\255\255\255\255"-encodeS32 :: Int32 -> BS.ByteString-encodeS32 = BSL.toStrict . BSB.toLazyByteString . BSB.int32BE-{-# INLINE encodeS32 #-}---- | Encode a 64-bit signed integer (big-endian two's complement).------ >>> encodeS64 0x0102030405060708--- "\SOH\STX\ETX\EOT\ENQ\ACK\a\b"--- >>> encodeS64 (-1)--- "\255\255\255\255\255\255\255\255"-encodeS64 :: Int64 -> BS.ByteString-encodeS64 = BSL.toStrict . BSB.toLazyByteString . BSB.int64BE-{-# INLINE encodeS64 #-}---- Truncated unsigned integer encoding --------------------------------------------- | Encode a truncated 16-bit unsigned integer (0-2 bytes).------ Leading zeros are omitted per BOLT #1. Zero encodes to empty.------ >>> encodeTu16 0--- ""--- >>> encodeTu16 1--- "\SOH"--- >>> encodeTu16 256--- "\SOH\NUL"-encodeTu16 :: Word16 -> BS.ByteString-encodeTu16 0 = BS.empty-encodeTu16 !x- | x < 0x100 = BS.singleton (fromIntegral x)- | otherwise = encodeU16 x-{-# INLINE encodeTu16 #-}---- | Encode a truncated 32-bit unsigned integer (0-4 bytes).------ Leading zeros are omitted per BOLT #1. Zero encodes to empty.------ >>> encodeTu32 0--- ""--- >>> encodeTu32 1--- "\SOH"--- >>> encodeTu32 0x010000--- "\SOH\NUL\NUL"-encodeTu32 :: Word32 -> BS.ByteString-encodeTu32 0 = BS.empty-encodeTu32 !x- | x < 0x100 = BS.singleton (fromIntegral x)- | x < 0x10000 = encodeU16 (fromIntegral x)- | x < 0x1000000 = BS.pack [ fromIntegral (x `unsafeShiftR` 16)- , fromIntegral (x `unsafeShiftR` 8)- , fromIntegral x- ]- | otherwise = encodeU32 x-{-# INLINE encodeTu32 #-}---- | Encode a truncated 64-bit unsigned integer (0-8 bytes).------ Leading zeros are omitted per BOLT #1. Zero encodes to empty.------ >>> encodeTu64 0--- ""--- >>> encodeTu64 1--- "\SOH"--- >>> encodeTu64 0x0100000000--- "\SOH\NUL\NUL\NUL\NUL"-encodeTu64 :: Word64 -> BS.ByteString-encodeTu64 0 = BS.empty-encodeTu64 !x- | x < 0x100 = BS.singleton (fromIntegral x)- | x < 0x10000 = encodeU16 (fromIntegral x)- | x < 0x1000000 = BS.pack [ fromIntegral (x `unsafeShiftR` 16)- , fromIntegral (x `unsafeShiftR` 8)- , fromIntegral x- ]- | x < 0x100000000 = encodeU32 (fromIntegral x)- | x < 0x10000000000 = BS.pack [ fromIntegral (x `unsafeShiftR` 32)- , fromIntegral (x `unsafeShiftR` 24)- , fromIntegral (x `unsafeShiftR` 16)- , fromIntegral (x `unsafeShiftR` 8)- , fromIntegral x- ]- | x < 0x1000000000000 = BS.pack [ fromIntegral (x `unsafeShiftR` 40)- , fromIntegral (x `unsafeShiftR` 32)- , fromIntegral (x `unsafeShiftR` 24)- , fromIntegral (x `unsafeShiftR` 16)- , fromIntegral (x `unsafeShiftR` 8)- , fromIntegral x- ]- | x < 0x100000000000000 = BS.pack [ fromIntegral (x `unsafeShiftR` 48)- , fromIntegral (x `unsafeShiftR` 40)- , fromIntegral (x `unsafeShiftR` 32)- , fromIntegral (x `unsafeShiftR` 24)- , fromIntegral (x `unsafeShiftR` 16)- , fromIntegral (x `unsafeShiftR` 8)- , fromIntegral x- ]- | otherwise = encodeU64 x-{-# INLINE encodeTu64 #-}---- Minimal signed integer encoding ------------------------------------------------- | Encode a signed 64-bit integer using minimal bytes.------ Uses the smallest number of bytes that can represent the value--- in two's complement. Per BOLT #1 Appendix D test vectors.------ >>> encodeMinSigned 0--- "\NUL"--- >>> encodeMinSigned 127--- "\DEL"--- >>> encodeMinSigned 128--- "\NUL\128"--- >>> encodeMinSigned (-1)--- "\255"--- >>> encodeMinSigned (-128)--- "\128"--- >>> encodeMinSigned (-129)--- "\255\DEL"-encodeMinSigned :: Int64 -> BS.ByteString-encodeMinSigned !x- | x >= -128 && x <= 127 =- -- Fits in 1 byte- BS.singleton (fromIntegral x)- | x >= -32768 && x <= 32767 =- -- Fits in 2 bytes- encodeS16 (fromIntegral x)- | x >= -2147483648 && x <= 2147483647 =- -- Fits in 4 bytes- encodeS32 (fromIntegral x)- | otherwise =- -- Need 8 bytes- encodeS64 x-{-# INLINE encodeMinSigned #-}---- BigSize encoding ---------------------------------------------------------------- | Encode a BigSize value (variable-length unsigned integer).------ >>> encodeBigSize 0--- "\NUL"--- >>> encodeBigSize 252--- "\252"--- >>> encodeBigSize 253--- "\253\NUL\253"--- >>> encodeBigSize 65536--- "\254\NUL\SOH\NUL\NUL"-encodeBigSize :: Word64 -> BS.ByteString-encodeBigSize !x- | x < 0xfd = BS.singleton (fromIntegral x)- | x < 0x10000 = BS.cons 0xfd (encodeU16 (fromIntegral x))- | x < 0x100000000 = BS.cons 0xfe (encodeU32 (fromIntegral x))- | otherwise = BS.cons 0xff (encodeU64 x)-{-# INLINE encodeBigSize #-}---- Length encoding ----------------------------------------------------------------- | Encode a length as u16, checking bounds.------ Returns Nothing if the length exceeds 65535.-encodeLength :: BS.ByteString -> Maybe BS.ByteString-encodeLength !bs- | BS.length bs > 65535 = Nothing- | otherwise = Just (encodeU16 (fromIntegral (BS.length bs)))-{-# INLINE encodeLength #-}---- Unsigned integer decoding ------------------------------------------------------- | Decode a 16-bit unsigned integer (big-endian).-decodeU16 :: BS.ByteString -> Maybe (Word16, BS.ByteString)-decodeU16 !bs- | BS.length bs < 2 = Nothing- | otherwise =- let !b0 = fromIntegral (BS.index bs 0)- !b1 = fromIntegral (BS.index bs 1)- !val = (b0 `unsafeShiftL` 8) .|. b1- in Just (val, BS.drop 2 bs)-{-# INLINE decodeU16 #-}---- | Decode a 32-bit unsigned integer (big-endian).-decodeU32 :: BS.ByteString -> Maybe (Word32, BS.ByteString)-decodeU32 !bs- | BS.length bs < 4 = Nothing- | otherwise =- let !b0 = fromIntegral (BS.index bs 0)- !b1 = fromIntegral (BS.index bs 1)- !b2 = fromIntegral (BS.index bs 2)- !b3 = fromIntegral (BS.index bs 3)- !val = (b0 `unsafeShiftL` 24) .|. (b1 `unsafeShiftL` 16)- .|. (b2 `unsafeShiftL` 8) .|. b3- in Just (val, BS.drop 4 bs)-{-# INLINE decodeU32 #-}---- | Decode a 64-bit unsigned integer (big-endian).-decodeU64 :: BS.ByteString -> Maybe (Word64, BS.ByteString)-decodeU64 !bs- | BS.length bs < 8 = Nothing- | otherwise =- let !b0 = fromIntegral (BS.index bs 0)- !b1 = fromIntegral (BS.index bs 1)- !b2 = fromIntegral (BS.index bs 2)- !b3 = fromIntegral (BS.index bs 3)- !b4 = fromIntegral (BS.index bs 4)- !b5 = fromIntegral (BS.index bs 5)- !b6 = fromIntegral (BS.index bs 6)- !b7 = fromIntegral (BS.index bs 7)- !val = (b0 `unsafeShiftL` 56) .|. (b1 `unsafeShiftL` 48)- .|. (b2 `unsafeShiftL` 40) .|. (b3 `unsafeShiftL` 32)- .|. (b4 `unsafeShiftL` 24) .|. (b5 `unsafeShiftL` 16)- .|. (b6 `unsafeShiftL` 8) .|. b7- in Just (val, BS.drop 8 bs)-{-# INLINE decodeU64 #-}---- Signed integer decoding --------------------------------------------------------- | Decode an 8-bit signed integer.-decodeS8 :: BS.ByteString -> Maybe (Int8, BS.ByteString)-decodeS8 !bs- | BS.null bs = Nothing- | otherwise = Just (fromIntegral (BS.index bs 0), BS.drop 1 bs)-{-# INLINE decodeS8 #-}---- | Decode a 16-bit signed integer (big-endian two's complement).-decodeS16 :: BS.ByteString -> Maybe (Int16, BS.ByteString)-decodeS16 !bs = do- (w, rest) <- decodeU16 bs- Just (fromIntegral w, rest)-{-# INLINE decodeS16 #-}---- | Decode a 32-bit signed integer (big-endian two's complement).-decodeS32 :: BS.ByteString -> Maybe (Int32, BS.ByteString)-decodeS32 !bs = do- (w, rest) <- decodeU32 bs- Just (fromIntegral w, rest)-{-# INLINE decodeS32 #-}---- | Decode a 64-bit signed integer (big-endian two's complement).-decodeS64 :: BS.ByteString -> Maybe (Int64, BS.ByteString)-decodeS64 !bs = do- (w, rest) <- decodeU64 bs- Just (fromIntegral w, rest)-{-# INLINE decodeS64 #-}---- Truncated unsigned integer decoding --------------------------------------------- | Decode a truncated 16-bit unsigned integer (0-2 bytes).------ Returns Nothing if the encoding is non-minimal (has leading zeros).-decodeTu16 :: Int -> BS.ByteString -> Maybe (Word16, BS.ByteString)-decodeTu16 !len !bs- | len < 0 || len > 2 = Nothing- | BS.length bs < len = Nothing- | len == 0 = Just (0, bs)- | otherwise =- let !bytes = BS.take len bs- !rest = BS.drop len bs- in if BS.index bytes 0 == 0- then Nothing -- non-minimal: leading zero- else Just (decodeBeWord16 bytes, rest)- where- decodeBeWord16 :: BS.ByteString -> Word16- decodeBeWord16 b = case BS.length b of- 1 -> fromIntegral (BS.index b 0)- 2 -> (fromIntegral (BS.index b 0) `unsafeShiftL` 8)- .|. fromIntegral (BS.index b 1)- _ -> 0-{-# INLINE decodeTu16 #-}---- | Decode a truncated 32-bit unsigned integer (0-4 bytes).------ Returns Nothing if the encoding is non-minimal (has leading zeros).-decodeTu32 :: Int -> BS.ByteString -> Maybe (Word32, BS.ByteString)-decodeTu32 !len !bs- | len < 0 || len > 4 = Nothing- | BS.length bs < len = Nothing- | len == 0 = Just (0, bs)- | otherwise =- let !bytes = BS.take len bs- !rest = BS.drop len bs- in if BS.index bytes 0 == 0- then Nothing -- non-minimal: leading zero- else Just (decodeBeWord32 len bytes, rest)- where- decodeBeWord32 :: Int -> BS.ByteString -> Word32- decodeBeWord32 n b = go 0 0- where- go !acc !i- | i >= n = acc- | otherwise = go ((acc `unsafeShiftL` 8)- .|. fromIntegral (BS.index b i)) (i + 1)-{-# INLINE decodeTu32 #-}---- | Decode a truncated 64-bit unsigned integer (0-8 bytes).------ Returns Nothing if the encoding is non-minimal (has leading zeros).-decodeTu64 :: Int -> BS.ByteString -> Maybe (Word64, BS.ByteString)-decodeTu64 !len !bs- | len < 0 || len > 8 = Nothing- | BS.length bs < len = Nothing- | len == 0 = Just (0, bs)- | otherwise =- let !bytes = BS.take len bs- !rest = BS.drop len bs- in if BS.index bytes 0 == 0- then Nothing -- non-minimal: leading zero- else Just (decodeBeWord64 len bytes, rest)- where- decodeBeWord64 :: Int -> BS.ByteString -> Word64- decodeBeWord64 n b = go 0 0- where- go !acc !i- | i >= n = acc- | otherwise = go ((acc `unsafeShiftL` 8)- .|. fromIntegral (BS.index b i)) (i + 1)-{-# INLINE decodeTu64 #-}---- Minimal signed integer decoding ------------------------------------------------- | Decode a minimal signed integer (1, 2, 4, or 8 bytes).------ Validates that the encoding is minimal: the value could not be--- represented in fewer bytes. Per BOLT #1 Appendix D test vectors.-decodeMinSigned :: Int -> BS.ByteString -> Maybe (Int64, BS.ByteString)-decodeMinSigned !len !bs- | BS.length bs < len = Nothing- | otherwise = case len of- 1 -> do- (v, rest) <- decodeS8 bs- Just (fromIntegral v, rest)- 2 -> do- (v, rest) <- decodeS16 bs- -- Must not fit in 1 byte- if v >= -128 && v <= 127- then Nothing- else Just (fromIntegral v, rest)- 4 -> do- (v, rest) <- decodeS32 bs- -- Must not fit in 2 bytes- if v >= -32768 && v <= 32767- then Nothing- else Just (fromIntegral v, rest)- 8 -> do- (v, rest) <- decodeS64 bs- -- Must not fit in 4 bytes- if v >= -2147483648 && v <= 2147483647- then Nothing- else Just (v, rest)- _ -> Nothing-{-# INLINE decodeMinSigned #-}---- BigSize decoding ---------------------------------------------------------------- | Decode a BigSize value with minimality check.-decodeBigSize :: BS.ByteString -> Maybe (Word64, BS.ByteString)-decodeBigSize !bs- | BS.null bs = Nothing- | otherwise = case BS.index bs 0 of- 0xff -> do- (val, rest) <- decodeU64 (BS.drop 1 bs)- -- Must be >= 0x100000000 for minimal encoding- if val >= 0x100000000- then Just (val, rest)- else Nothing- 0xfe -> do- (val, rest) <- decodeU32 (BS.drop 1 bs)- -- Must be >= 0x10000 for minimal encoding- if val >= 0x10000- then Just (fromIntegral val, rest)- else Nothing- 0xfd -> do- (val, rest) <- decodeU16 (BS.drop 1 bs)- -- Must be >= 0xfd for minimal encoding- if val >= 0xfd- then Just (fromIntegral val, rest)- else Nothing- b -> Just (fromIntegral b, BS.drop 1 bs)+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveGeneric #-}++-- |+-- Module: Lightning.Protocol.BOLT1.Prim+-- Copyright: (c) 2025 Jared Tobin+-- License: MIT+-- Maintainer: Jared Tobin <jared@ppad.tech>+--+-- Fundamental types and primitive encodings for BOLT #1.++module Lightning.Protocol.BOLT1.Prim (+ -- * Fixed-size byte types+ ChainHash+ , chain_hash+ , un_chain_hash+ , ChannelId+ , channel_id+ , un_channel_id+ , all_channels+ , Signature+ , signature+ , un_signature+ , Point+ , point+ , un_point+ , PaymentHash+ , payment_hash+ , un_payment_hash+ , PaymentPreimage+ , payment_preimage+ , un_payment_preimage+ , PerCommitmentSecret+ , per_commitment_secret+ , un_per_commitment_secret++ -- * Short channel identifiers+ , ShortChannelId(..)+ , short_channel_id+ , scid_block_height+ , scid_tx_index+ , scid_output_index++ -- * Amounts+ , Satoshi+ , satoshi+ , un_satoshi+ , max_satoshi+ , MilliSatoshi+ , milli_satoshi+ , un_milli_satoshi+ , max_milli_satoshi+ , sat_to_msat+ , msat_to_sat+ , add_sat+ , sub_sat+ , add_msat+ , sub_msat++ -- * Unsigned integers+ , encode_u16+ , encode_u32+ , encode_u64+ , decode_u16+ , decode_u32+ , decode_u64++ -- * Signed integers+ , encode_s8+ , encode_s16+ , encode_s32+ , encode_s64+ , decode_s8+ , decode_s16+ , decode_s32+ , decode_s64++ -- * Truncated unsigned integers+ , encode_tu16+ , encode_tu32+ , encode_tu64+ , decode_tu16+ , decode_tu32+ , decode_tu64++ -- * BigSize+ , encode_bigsize+ , decode_bigsize+ , BigSizeError(..)+ , decode_bigsize_detailed++ -- * Length-prefixed bytes+ , encode_u16_prefixed+ , decode_u16_prefixed++ -- * Fixed-size field codecs+ , decode_chain_hash+ , decode_channel_id+ , decode_signature+ , decode_point+ , decode_payment_hash+ , decode_payment_preimage+ , decode_per_commitment_secret+ , encode_short_channel_id+ , decode_short_channel_id+ , encode_satoshi+ , decode_satoshi+ , encode_milli_satoshi+ , decode_milli_satoshi+ ) where++import Control.DeepSeq (NFData(..))+import Data.Bits ((.&.), (.|.), unsafeShiftL, unsafeShiftR, xor)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Internal as BI+import qualified Data.ByteString.Unsafe as BU+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Word (Word8, Word16, Word32, Word64)+import Foreign.Ptr (Ptr)+import Foreign.Storable (pokeByteOff)+import GHC.Generics (Generic)++fi :: (Integral a, Num b) => a -> b+fi = fromIntegral+{-# INLINE fi #-}++-- constant-time equality on equal-length bytestrings+ct_eq :: BS.ByteString -> BS.ByteString -> Bool+ct_eq a b+ | BS.length a /= BS.length b = False+ | otherwise = go 0 0+ where+ !n = BS.length a+ go :: Word8 -> Int -> Bool+ go !acc !i+ | i == n = acc == 0+ | otherwise =+ go (acc .|. (BU.unsafeIndex a i `xor` BU.unsafeIndex b i))+ (i + 1)++-- take exactly n bytes from the front of the input+take_n :: Int -> BS.ByteString -> Maybe (BS.ByteString, BS.ByteString)+take_n !n !bs+ | BS.length bs < n = Nothing+ | otherwise = Just (BU.unsafeTake n bs, BU.unsafeDrop n bs)+{-# INLINE take_n #-}++-- fixed-size byte types ------------------------------------------------------++-- | A 32-byte chain hash, identifying the chain a channel or message+-- belongs to.+newtype ChainHash = ChainHash BS.ByteString+ deriving (Eq, Ord, Show, Generic)++instance NFData ChainHash++-- | Construct a 'ChainHash' from exactly 32 bytes.+--+-- >>> fmap (BS.length . un_chain_hash) (chain_hash (BS.replicate 32 0))+-- Just 32+-- >>> chain_hash "too short"+-- Nothing+chain_hash :: BS.ByteString -> Maybe ChainHash+chain_hash bs+ | BS.length bs == 32 = Just (ChainHash bs)+ | otherwise = Nothing+{-# INLINE chain_hash #-}++-- | The bytes of a 'ChainHash'.+un_chain_hash :: ChainHash -> BS.ByteString+un_chain_hash (ChainHash bs) = bs+{-# INLINE un_chain_hash #-}++-- | A 32-byte channel identifier.+newtype ChannelId = ChannelId BS.ByteString+ deriving (Eq, Ord, Show, Generic)++instance NFData ChannelId++-- | Construct a 'ChannelId' from exactly 32 bytes.+--+-- >>> fmap (BS.length . un_channel_id) (channel_id (BS.replicate 32 1))+-- Just 32+channel_id :: BS.ByteString -> Maybe ChannelId+channel_id bs+ | BS.length bs == 32 = Just (ChannelId bs)+ | otherwise = Nothing+{-# INLINE channel_id #-}++-- | The bytes of a 'ChannelId'.+un_channel_id :: ChannelId -> BS.ByteString+un_channel_id (ChannelId bs) = bs+{-# INLINE un_channel_id #-}++-- | The all-zero channel identifier, which refers to all channels+-- (e.g. in connection-level @error@ and @warning@ messages).+all_channels :: ChannelId+all_channels = ChannelId (BS.replicate 32 0x00)++-- | A 64-byte compact ECDSA signature.+newtype Signature = Signature BS.ByteString+ deriving (Eq, Ord, Show, Generic)++instance NFData Signature++-- | Construct a 'Signature' from exactly 64 bytes.+--+-- >>> signature (BS.replicate 63 0x00)+-- Nothing+signature :: BS.ByteString -> Maybe Signature+signature bs+ | BS.length bs == 64 = Just (Signature bs)+ | otherwise = Nothing+{-# INLINE signature #-}++-- | The bytes of a 'Signature'.+un_signature :: Signature -> BS.ByteString+un_signature (Signature bs) = bs+{-# INLINE un_signature #-}++-- | A 33-byte compressed secp256k1 point.+newtype Point = Point BS.ByteString+ deriving (Eq, Ord, Show, Generic)++instance NFData Point++-- | Construct a 'Point' from 33 bytes with a compressed-encoding+-- prefix (0x02 or 0x03). The curve equation is not checked; use+-- ppad-secp256k1 to parse the point if it must be on the curve.+--+-- >>> point (BS.cons 0x04 (BS.replicate 32 0x01))+-- Nothing+point :: BS.ByteString -> Maybe Point+point bs+ | BS.length bs == 33+ , let !h = BU.unsafeIndex bs 0+ , h == 0x02 || h == 0x03 = Just (Point bs)+ | otherwise = Nothing+{-# INLINE point #-}++-- | The bytes of a 'Point'.+un_point :: Point -> BS.ByteString+un_point (Point bs) = bs+{-# INLINE un_point #-}++-- | A 32-byte SHA256 payment hash.+newtype PaymentHash = PaymentHash BS.ByteString+ deriving (Eq, Ord, Show, Generic)++instance NFData PaymentHash++-- | Construct a 'PaymentHash' from exactly 32 bytes.+payment_hash :: BS.ByteString -> Maybe PaymentHash+payment_hash bs+ | BS.length bs == 32 = Just (PaymentHash bs)+ | otherwise = Nothing+{-# INLINE payment_hash #-}++-- | The bytes of a 'PaymentHash'.+un_payment_hash :: PaymentHash -> BS.ByteString+un_payment_hash (PaymentHash bs) = bs+{-# INLINE un_payment_hash #-}++-- | A 32-byte payment preimage.+--+-- This is secret material: its 'Show' instance is redacted and its+-- 'Eq' instance runs in constant time.+newtype PaymentPreimage = PaymentPreimage BS.ByteString++instance Eq PaymentPreimage where+ PaymentPreimage a == PaymentPreimage b = ct_eq a b++instance Show PaymentPreimage where+ showsPrec d _ = showParen (d > 10) $+ showString "PaymentPreimage <redacted>"++instance NFData PaymentPreimage where+ rnf (PaymentPreimage bs) = rnf bs++-- | Construct a 'PaymentPreimage' from exactly 32 bytes.+payment_preimage :: BS.ByteString -> Maybe PaymentPreimage+payment_preimage bs+ | BS.length bs == 32 = Just (PaymentPreimage bs)+ | otherwise = Nothing+{-# INLINE payment_preimage #-}++-- | The bytes of a 'PaymentPreimage'.+un_payment_preimage :: PaymentPreimage -> BS.ByteString+un_payment_preimage (PaymentPreimage bs) = bs+{-# INLINE un_payment_preimage #-}++-- | A 32-byte per-commitment secret.+--+-- This is secret material: its 'Show' instance is redacted and its+-- 'Eq' instance runs in constant time.+newtype PerCommitmentSecret = PerCommitmentSecret BS.ByteString++instance Eq PerCommitmentSecret where+ PerCommitmentSecret a == PerCommitmentSecret b = ct_eq a b++instance Show PerCommitmentSecret where+ showsPrec d _ = showParen (d > 10) $+ showString "PerCommitmentSecret <redacted>"++instance NFData PerCommitmentSecret where+ rnf (PerCommitmentSecret bs) = rnf bs++-- | Construct a 'PerCommitmentSecret' from exactly 32 bytes.+per_commitment_secret :: BS.ByteString -> Maybe PerCommitmentSecret+per_commitment_secret bs+ | BS.length bs == 32 = Just (PerCommitmentSecret bs)+ | otherwise = Nothing+{-# INLINE per_commitment_secret #-}++-- | The bytes of a 'PerCommitmentSecret'.+un_per_commitment_secret :: PerCommitmentSecret -> BS.ByteString+un_per_commitment_secret (PerCommitmentSecret bs) = bs+{-# INLINE un_per_commitment_secret #-}++-- short channel identifiers --------------------------------------------------++-- | A short channel identifier: block height (3 bytes), transaction+-- index (3 bytes) and output index (2 bytes), packed big-endian into+-- a 'Word64'. Every 'Word64' is a valid packing.+newtype ShortChannelId = ShortChannelId Word64+ deriving (Eq, Ord, Show, Generic)++instance NFData ShortChannelId++-- | Construct a 'ShortChannelId' from its components. Fails if the+-- block height or transaction index exceed 24 bits.+--+-- >>> short_channel_id 539268 845 1+-- Just (ShortChannelId 592931436542885889)+short_channel_id+ :: Word32 -- ^ block height+ -> Word32 -- ^ transaction index+ -> Word16 -- ^ output index+ -> Maybe ShortChannelId+short_channel_id h t o+ | h > 0xFFFFFF = Nothing+ | t > 0xFFFFFF = Nothing+ | otherwise = Just $! ShortChannelId $!+ (fi h `unsafeShiftL` 40)+ .|. (fi t `unsafeShiftL` 16)+ .|. fi o+{-# INLINE short_channel_id #-}++-- | The block height of a 'ShortChannelId'.+scid_block_height :: ShortChannelId -> Word32+scid_block_height (ShortChannelId w) = fi (w `unsafeShiftR` 40)+{-# INLINE scid_block_height #-}++-- | The transaction index of a 'ShortChannelId'.+scid_tx_index :: ShortChannelId -> Word32+scid_tx_index (ShortChannelId w) = fi ((w `unsafeShiftR` 16) .&. 0xFFFFFF)+{-# INLINE scid_tx_index #-}++-- | The output index of a 'ShortChannelId'.+scid_output_index :: ShortChannelId -> Word16+scid_output_index (ShortChannelId w) = fi (w .&. 0xFFFF)+{-# INLINE scid_output_index #-}++-- amounts --------------------------------------------------------------------++-- | An amount in satoshis, at most 21 million BTC ('max_satoshi').+newtype Satoshi = Satoshi Word64+ deriving (Eq, Ord, Show, Generic)++instance NFData Satoshi++-- | An amount in millisatoshis, at most 21 million BTC+-- ('max_milli_satoshi').+newtype MilliSatoshi = MilliSatoshi Word64+ deriving (Eq, Ord, Show, Generic)++instance NFData MilliSatoshi++-- | 21 million BTC, in satoshis (0x000775f05a074000).+max_satoshi :: Satoshi+max_satoshi = Satoshi 0x000775f05a074000++-- | 21 million BTC, in millisatoshis (0x1d24b2dfac520000).+max_milli_satoshi :: MilliSatoshi+max_milli_satoshi = MilliSatoshi 0x1d24b2dfac520000++-- | Construct a 'Satoshi' amount, failing above 21 million BTC.+--+-- >>> satoshi 1000+-- Just (Satoshi 1000)+-- >>> satoshi 0xffffffffffffffff+-- Nothing+satoshi :: Word64 -> Maybe Satoshi+satoshi w+ | w <= 0x000775f05a074000 = Just (Satoshi w)+ | otherwise = Nothing+{-# INLINE satoshi #-}++-- | The number of satoshis.+un_satoshi :: Satoshi -> Word64+un_satoshi (Satoshi w) = w+{-# INLINE un_satoshi #-}++-- | Construct a 'MilliSatoshi' amount, failing above 21 million BTC.+milli_satoshi :: Word64 -> Maybe MilliSatoshi+milli_satoshi w+ | w <= 0x1d24b2dfac520000 = Just (MilliSatoshi w)+ | otherwise = Nothing+{-# INLINE milli_satoshi #-}++-- | The number of millisatoshis.+un_milli_satoshi :: MilliSatoshi -> Word64+un_milli_satoshi (MilliSatoshi w) = w+{-# INLINE un_milli_satoshi #-}++-- | Convert satoshis to millisatoshis. Total, since the bounds+-- correspond.+--+-- >>> fmap sat_to_msat (satoshi 5)+-- Just (MilliSatoshi 5000)+sat_to_msat :: Satoshi -> MilliSatoshi+sat_to_msat (Satoshi s) = MilliSatoshi (s * 1000)+{-# INLINE sat_to_msat #-}++-- | Convert millisatoshis to satoshis, rounding down.+msat_to_sat :: MilliSatoshi -> Satoshi+msat_to_sat (MilliSatoshi m) = Satoshi (m `quot` 1000)+{-# INLINE msat_to_sat #-}++-- | Add two amounts, failing above 21 million BTC. (Amounts are+-- bounded well below 2^63, so the sum can't wrap.)+add_sat :: Satoshi -> Satoshi -> Maybe Satoshi+add_sat (Satoshi a) (Satoshi b) = satoshi (a + b)+{-# INLINE add_sat #-}++-- | Subtract the second amount from the first, failing if the result+-- would be negative.+sub_sat :: Satoshi -> Satoshi -> Maybe Satoshi+sub_sat (Satoshi a) (Satoshi b)+ | b > a = Nothing+ | otherwise = Just (Satoshi (a - b))+{-# INLINE sub_sat #-}++-- | Add two amounts, failing above 21 million BTC.+add_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi+add_msat (MilliSatoshi a) (MilliSatoshi b) = milli_satoshi (a + b)+{-# INLINE add_msat #-}++-- | Subtract the second amount from the first, failing if the result+-- would be negative.+sub_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi+sub_msat (MilliSatoshi a) (MilliSatoshi b)+ | b > a = Nothing+ | otherwise = Just (MilliSatoshi (a - b))+{-# INLINE sub_msat #-}++-- unsigned integers ----------------------------------------------------------++poke8 :: Ptr Word8 -> Int -> Word8 -> IO ()+poke8 = pokeByteOff+{-# INLINE poke8 #-}++-- | Encode a 'Word16' as 2 big-endian bytes.+--+-- >>> encode_u16 0x0102+-- "\SOH\STX"+encode_u16 :: Word16 -> BS.ByteString+encode_u16 w = BI.unsafeCreate 2 $ \p -> do+ poke8 p 0 (fi (w `unsafeShiftR` 8))+ poke8 p 1 (fi w)+{-# INLINE encode_u16 #-}++-- | Encode a 'Word32' as 4 big-endian bytes.+encode_u32 :: Word32 -> BS.ByteString+encode_u32 w = BI.unsafeCreate 4 $ \p -> do+ poke8 p 0 (fi (w `unsafeShiftR` 24))+ poke8 p 1 (fi (w `unsafeShiftR` 16))+ poke8 p 2 (fi (w `unsafeShiftR` 8))+ poke8 p 3 (fi w)+{-# INLINE encode_u32 #-}++-- | Encode a 'Word64' as 8 big-endian bytes.+encode_u64 :: Word64 -> BS.ByteString+encode_u64 w = BI.unsafeCreate 8 $ \p -> do+ poke8 p 0 (fi (w `unsafeShiftR` 56))+ poke8 p 1 (fi (w `unsafeShiftR` 48))+ poke8 p 2 (fi (w `unsafeShiftR` 40))+ poke8 p 3 (fi (w `unsafeShiftR` 32))+ poke8 p 4 (fi (w `unsafeShiftR` 24))+ poke8 p 5 (fi (w `unsafeShiftR` 16))+ poke8 p 6 (fi (w `unsafeShiftR` 8))+ poke8 p 7 (fi w)+{-# INLINE encode_u64 #-}++-- big-endian word from the first n bytes (n <= 8, length checked)+be :: Int -> BS.ByteString -> Word64+be n bs = go 0 0+ where+ go !acc !i+ | i == n = acc+ | otherwise =+ go ((acc `unsafeShiftL` 8) .|. fi (BU.unsafeIndex bs i)) (i + 1)+{-# INLINE be #-}++-- | Decode 2 big-endian bytes, returning the remaining input.+--+-- >>> decode_u16 "\SOH\STXrest"+-- Just (258,"rest")+decode_u16 :: BS.ByteString -> Maybe (Word16, BS.ByteString)+decode_u16 bs+ | BS.length bs < 2 = Nothing+ | otherwise = Just (fi (be 2 bs), BU.unsafeDrop 2 bs)+{-# INLINE decode_u16 #-}++-- | Decode 4 big-endian bytes, returning the remaining input.+decode_u32 :: BS.ByteString -> Maybe (Word32, BS.ByteString)+decode_u32 bs+ | BS.length bs < 4 = Nothing+ | otherwise = Just (fi (be 4 bs), BU.unsafeDrop 4 bs)+{-# INLINE decode_u32 #-}++-- | Decode 8 big-endian bytes, returning the remaining input.+decode_u64 :: BS.ByteString -> Maybe (Word64, BS.ByteString)+decode_u64 bs+ | BS.length bs < 8 = Nothing+ | otherwise = Just (be 8 bs, BU.unsafeDrop 8 bs)+{-# INLINE decode_u64 #-}++-- signed integers ------------------------------------------------------------++-- | Encode an 'Int8' as 1 byte (two's complement).+--+-- >>> encode_s8 (-42)+-- "\214"+encode_s8 :: Int8 -> BS.ByteString+encode_s8 = BS.singleton . fi+{-# INLINE encode_s8 #-}++-- | Encode an 'Int16' as 2 big-endian bytes (two's complement).+encode_s16 :: Int16 -> BS.ByteString+encode_s16 = encode_u16 . fi+{-# INLINE encode_s16 #-}++-- | Encode an 'Int32' as 4 big-endian bytes (two's complement).+encode_s32 :: Int32 -> BS.ByteString+encode_s32 = encode_u32 . fi+{-# INLINE encode_s32 #-}++-- | Encode an 'Int64' as 8 big-endian bytes (two's complement).+encode_s64 :: Int64 -> BS.ByteString+encode_s64 = encode_u64 . fi+{-# INLINE encode_s64 #-}++-- | Decode 1 byte (two's complement), returning the remaining input.+decode_s8 :: BS.ByteString -> Maybe (Int8, BS.ByteString)+decode_s8 bs = case BS.uncons bs of+ Just (h, t) -> Just (fi h, t)+ Nothing -> Nothing+{-# INLINE decode_s8 #-}++-- | Decode 2 big-endian bytes (two's complement).+decode_s16 :: BS.ByteString -> Maybe (Int16, BS.ByteString)+decode_s16 bs = case decode_u16 bs of+ Just (w, t) -> Just (fi w, t)+ Nothing -> Nothing+{-# INLINE decode_s16 #-}++-- | Decode 4 big-endian bytes (two's complement).+decode_s32 :: BS.ByteString -> Maybe (Int32, BS.ByteString)+decode_s32 bs = case decode_u32 bs of+ Just (w, t) -> Just (fi w, t)+ Nothing -> Nothing+{-# INLINE decode_s32 #-}++-- | Decode 8 big-endian bytes (two's complement).+decode_s64 :: BS.ByteString -> Maybe (Int64, BS.ByteString)+decode_s64 bs = case decode_u64 bs of+ Just (w, t) -> Just (fi w, t)+ Nothing -> Nothing+{-# INLINE decode_s64 #-}++-- truncated unsigned integers ------------------------------------------------++-- minimal big-endian encoding: no leading zero bytes (zero is empty)+encode_tu :: Word64 -> BS.ByteString+encode_tu w = BS.drop (lz 0) (encode_u64 w)+ where+ lz :: Int -> Int+ lz !i+ | i < 8 && (w `unsafeShiftR` (56 - 8 * i)) .&. 0xff == 0 = lz (i + 1)+ | otherwise = i++decode_tu :: Int -> BS.ByteString -> Maybe Word64+decode_tu maxlen bs+ | n > maxlen = Nothing+ | n > 0 && BU.unsafeIndex bs 0 == 0 = Nothing+ | otherwise = Just (be n bs)+ where+ !n = BS.length bs++-- | Encode a 'Word16' as a truncated integer (no leading zero bytes).+--+-- >>> encode_tu16 0+-- ""+-- >>> encode_tu16 0x0100+-- "\SOH\NUL"+encode_tu16 :: Word16 -> BS.ByteString+encode_tu16 = encode_tu . fi++-- | Encode a 'Word32' as a truncated integer (no leading zero bytes).+encode_tu32 :: Word32 -> BS.ByteString+encode_tu32 = encode_tu . fi++-- | Encode a 'Word64' as a truncated integer (no leading zero bytes).+encode_tu64 :: Word64 -> BS.ByteString+encode_tu64 = encode_tu++-- | Decode a truncated 16-bit integer occupying the whole input. Fails+-- on more than 2 bytes or a non-minimal encoding.+--+-- >>> decode_tu16 "\SOH\NUL"+-- Just 256+-- >>> decode_tu16 "\NUL\SOH"+-- Nothing+decode_tu16 :: BS.ByteString -> Maybe Word16+decode_tu16 = fmap fi . decode_tu 2++-- | Decode a truncated 32-bit integer occupying the whole input. Fails+-- on more than 4 bytes or a non-minimal encoding.+decode_tu32 :: BS.ByteString -> Maybe Word32+decode_tu32 = fmap fi . decode_tu 4++-- | Decode a truncated 64-bit integer occupying the whole input. Fails+-- on more than 8 bytes or a non-minimal encoding.+decode_tu64 :: BS.ByteString -> Maybe Word64+decode_tu64 = decode_tu 8++-- bigsize --------------------------------------------------------------------++-- | Encode a 'Word64' in the minimal BigSize format.+--+-- >>> encode_bigsize 252+-- "\252"+-- >>> encode_bigsize 253+-- "\253\NUL\253"+encode_bigsize :: Word64 -> BS.ByteString+encode_bigsize w+ | w < 0xfd = BS.singleton (fi w)+ | w <= 0xffff = BS.cons 0xfd (encode_u16 (fi w))+ | w <= 0xffffffff = BS.cons 0xfe (encode_u32 (fi w))+ | otherwise = BS.cons 0xff (encode_u64 w)++-- | Why a BigSize failed to decode.+data BigSizeError+ = BigSizeTruncated -- ^ input ended early+ | BigSizeNonMinimal -- ^ a shorter encoding exists+ deriving (Eq, Show, Generic)++instance NFData BigSizeError++-- | Decode a BigSize, distinguishing truncation from non-minimal+-- encodings.+decode_bigsize_detailed+ :: BS.ByteString -> Either BigSizeError (Word64, BS.ByteString)+decode_bigsize_detailed bs = case BS.uncons bs of+ Nothing -> Left BigSizeTruncated+ Just (h, t) -> case h of+ 0xfd -> wide 2 0xfd t+ 0xfe -> wide 4 0x10000 t+ 0xff -> wide 8 0x100000000 t+ _ -> Right (fi h, t)+ where+ wide n lo t = case take_n n t of+ Nothing -> Left BigSizeTruncated+ Just (v, r)+ | w < lo -> Left BigSizeNonMinimal+ | otherwise -> Right (w, r)+ where+ !w = be n v++-- | Decode a minimally-encoded BigSize, returning the remaining input.+--+-- >>> decode_bigsize "\253\NUL\253rest"+-- Just (253,"rest")+-- >>> decode_bigsize "\253\NUL\252"+-- Nothing+decode_bigsize :: BS.ByteString -> Maybe (Word64, BS.ByteString)+decode_bigsize bs = case decode_bigsize_detailed bs of+ Right r -> Just r+ Left _ -> Nothing++-- length-prefixed bytes ------------------------------------------------------++-- | Prefix bytes with their length as a u16. Fails if the input+-- exceeds 65535 bytes.+--+-- >>> encode_u16_prefixed "abc"+-- Just "\NUL\ETXabc"+encode_u16_prefixed :: BS.ByteString -> Maybe BS.ByteString+encode_u16_prefixed bs+ | BS.length bs > 0xffff = Nothing+ | otherwise = Just (encode_u16 (fi (BS.length bs)) <> bs)++-- | Decode u16-length-prefixed bytes, returning the remaining input.+--+-- >>> decode_u16_prefixed "\NUL\ETXabcrest"+-- Just ("abc","rest")+decode_u16_prefixed+ :: BS.ByteString -> Maybe (BS.ByteString, BS.ByteString)+decode_u16_prefixed bs = do+ (n, rest) <- decode_u16 bs+ take_n (fi n) rest++-- fixed-size field codecs ----------------------------------------------------++-- | Decode a 32-byte 'ChainHash', returning the remaining input.+decode_chain_hash :: BS.ByteString -> Maybe (ChainHash, BS.ByteString)+decode_chain_hash bs = do+ (h, r) <- take_n 32 bs+ pure (ChainHash h, r)++-- | Decode a 32-byte 'ChannelId', returning the remaining input.+decode_channel_id :: BS.ByteString -> Maybe (ChannelId, BS.ByteString)+decode_channel_id bs = do+ (h, r) <- take_n 32 bs+ pure (ChannelId h, r)++-- | Decode a 64-byte 'Signature', returning the remaining input.+decode_signature :: BS.ByteString -> Maybe (Signature, BS.ByteString)+decode_signature bs = do+ (h, r) <- take_n 64 bs+ pure (Signature h, r)++-- | Decode a 33-byte 'Point', returning the remaining input. Fails if+-- the prefix byte is not 0x02 or 0x03.+decode_point :: BS.ByteString -> Maybe (Point, BS.ByteString)+decode_point bs = do+ (h, r) <- take_n 33 bs+ p <- point h+ pure (p, r)++-- | Decode a 32-byte 'PaymentHash', returning the remaining input.+decode_payment_hash :: BS.ByteString -> Maybe (PaymentHash, BS.ByteString)+decode_payment_hash bs = do+ (h, r) <- take_n 32 bs+ pure (PaymentHash h, r)++-- | Decode a 32-byte 'PaymentPreimage', returning the remaining input.+decode_payment_preimage+ :: BS.ByteString -> Maybe (PaymentPreimage, BS.ByteString)+decode_payment_preimage bs = do+ (h, r) <- take_n 32 bs+ pure (PaymentPreimage h, r)++-- | Decode a 32-byte 'PerCommitmentSecret', returning the remaining+-- input.+decode_per_commitment_secret+ :: BS.ByteString -> Maybe (PerCommitmentSecret, BS.ByteString)+decode_per_commitment_secret bs = do+ (h, r) <- take_n 32 bs+ pure (PerCommitmentSecret h, r)++-- | Encode a 'ShortChannelId' as 8 big-endian bytes.+encode_short_channel_id :: ShortChannelId -> BS.ByteString+encode_short_channel_id (ShortChannelId w) = encode_u64 w+{-# INLINE encode_short_channel_id #-}++-- | Decode an 8-byte 'ShortChannelId', returning the remaining input.+decode_short_channel_id+ :: BS.ByteString -> Maybe (ShortChannelId, BS.ByteString)+decode_short_channel_id bs = do+ (w, r) <- decode_u64 bs+ pure (ShortChannelId w, r)+{-# INLINE decode_short_channel_id #-}++-- | Encode a 'Satoshi' amount as a u64.+encode_satoshi :: Satoshi -> BS.ByteString+encode_satoshi (Satoshi w) = encode_u64 w+{-# INLINE encode_satoshi #-}++-- | Decode a u64 'Satoshi' amount, returning the remaining input. Fails+-- above 21 million BTC.+decode_satoshi :: BS.ByteString -> Maybe (Satoshi, BS.ByteString)+decode_satoshi bs = do+ (w, r) <- decode_u64 bs+ s <- satoshi w+ pure (s, r)+{-# INLINE decode_satoshi #-}++-- | Encode a 'MilliSatoshi' amount as a u64.+encode_milli_satoshi :: MilliSatoshi -> BS.ByteString+encode_milli_satoshi (MilliSatoshi w) = encode_u64 w+{-# INLINE encode_milli_satoshi #-}++-- | Decode a u64 'MilliSatoshi' amount, returning the remaining input.+-- Fails above 21 million BTC.+decode_milli_satoshi+ :: BS.ByteString -> Maybe (MilliSatoshi, BS.ByteString)+decode_milli_satoshi bs = do+ (w, r) <- decode_u64 bs+ m <- milli_satoshi w+ pure (m, r)+{-# INLINE decode_milli_satoshi #-}
lib/Lightning/Protocol/BOLT1/TLV.hs view
@@ -1,7 +1,6 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-} -- | -- Module: Lightning.Protocol.BOLT1.TLV@@ -9,236 +8,149 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- TLV (Type-Length-Value) format for BOLT #1.+-- The TLV (type-length-value) format of BOLT #1. module Lightning.Protocol.BOLT1.TLV (- -- * TLV types TlvRecord(..) , TlvStream- , unTlvStream- , tlvStream- , unsafeTlvStream+ , tlv_stream+ , un_tlv_stream+ , empty_tlv_stream+ , lookup_tlv+ , filter_tlv_stream , TlvError(..)-- -- * TLV encoding- , encodeTlvRecord- , encodeTlvStream-- -- * TLV decoding- , decodeTlvStream- , decodeTlvStreamWith- , decodeTlvStreamRaw-- -- * Init TLV types- , InitTlv(..)- , parseInitTlvs- , encodeInitTlvs-- -- * Re-exports- , ChainHash- , chainHash- , unChainHash+ , encode_tlv_stream+ , decode_tlv_stream ) where import Control.DeepSeq (NFData)-import Control.Monad (when) import qualified Data.ByteString as BS+import qualified Data.ByteString.Unsafe as BU+import qualified Data.List as L import Data.Word (Word64) import GHC.Generics (Generic) import Lightning.Protocol.BOLT1.Prim --- TLV types -------------------------------------------------------------------- -- | A single TLV record. data TlvRecord = TlvRecord- { tlvType :: {-# UNPACK #-} !Word64- , tlvValue :: !BS.ByteString- } deriving stock (Eq, Show, Generic)+ { tlv_type :: {-# UNPACK #-} !Word64+ , tlv_value :: !BS.ByteString+ } deriving (Eq, Show, Generic) instance NFData TlvRecord --- | A TLV stream (series of TLV records).-newtype TlvStream = TlvStream { unTlvStream :: [TlvRecord] }- deriving stock (Eq, Show, Generic)+-- | A TLV stream: records in strictly increasing type order.+newtype TlvStream = TlvStream [TlvRecord]+ deriving (Eq, Show, Generic) instance NFData TlvStream --- | Smart constructor for 'TlvStream' that validates records are--- strictly increasing by type.+-- | Construct a 'TlvStream' from records in any order. Fails if two+-- records share a type. ----- Returns 'Nothing' if types are not strictly increasing.-tlvStream :: [TlvRecord] -> Maybe TlvStream-tlvStream recs- | isStrictlyIncreasing (map tlvType recs) = Just (TlvStream recs)- | otherwise = Nothing+-- >>> let Just s = tlv_stream [TlvRecord 3 "b", TlvRecord 1 "a"]+-- >>> map tlv_type (un_tlv_stream s)+-- [1,3]+-- >>> tlv_stream [TlvRecord 1 "a", TlvRecord 1 "b"]+-- Nothing+tlv_stream :: [TlvRecord] -> Maybe TlvStream+tlv_stream rs+ | increasing sorted = Just (TlvStream sorted)+ | otherwise = Nothing where- isStrictlyIncreasing :: [Word64] -> Bool- isStrictlyIncreasing [] = True- isStrictlyIncreasing [_] = True- isStrictlyIncreasing (x:y:rest) = x < y && isStrictlyIncreasing (y:rest)+ sorted = L.sortOn tlv_type rs --- | Unsafe constructor for 'TlvStream' that skips validation.------ Use only when ordering is already guaranteed (e.g., in decode functions).-unsafeTlvStream :: [TlvRecord] -> TlvStream-unsafeTlvStream = TlvStream+increasing :: [TlvRecord] -> Bool+increasing (a:rest@(b:_)) = tlv_type a < tlv_type b && increasing rest+increasing _ = True --- | TLV decoding errors.-data TlvError- = TlvNonMinimalEncoding- | TlvNotStrictlyIncreasing- | TlvLengthExceedsBounds- | TlvUnknownEvenType !Word64- | TlvInvalidKnownType !Word64- deriving stock (Eq, Show, Generic)+-- | The records of a 'TlvStream', in increasing type order.+un_tlv_stream :: TlvStream -> [TlvRecord]+un_tlv_stream (TlvStream rs) = rs+{-# INLINE un_tlv_stream #-} -instance NFData TlvError+-- | The empty 'TlvStream'.+empty_tlv_stream :: TlvStream+empty_tlv_stream = TlvStream [] --- TLV encoding ----------------------------------------------------------------+-- | The value of the record with the given type, if present.+--+-- >>> lookup_tlv 3 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"]+-- Just "b"+lookup_tlv :: Word64 -> TlvStream -> Maybe BS.ByteString+lookup_tlv t (TlvStream rs) = go rs+ where+ go [] = Nothing+ go (TlvRecord u v : more)+ | u == t = Just v+ | u > t = Nothing+ | otherwise = go more --- | Encode a TLV record.-encodeTlvRecord :: TlvRecord -> BS.ByteString-encodeTlvRecord (TlvRecord typ val) = mconcat- [ encodeBigSize typ- , encodeBigSize (fromIntegral (BS.length val))- , val- ]+-- | Keep the records whose types satisfy the predicate.+--+-- >>> let Just s = tlv_stream [TlvRecord 1 "", TlvRecord 2 ""]+-- >>> map tlv_type (un_tlv_stream (filter_tlv_stream odd s))+-- [1]+filter_tlv_stream :: (Word64 -> Bool) -> TlvStream -> TlvStream+filter_tlv_stream p (TlvStream rs) = TlvStream (filter (p . tlv_type) rs) --- | Encode a TLV stream.-encodeTlvStream :: TlvStream -> BS.ByteString-encodeTlvStream (TlvStream recs) = mconcat (map encodeTlvRecord recs)+-- | Why a TLV stream failed to decode.+data TlvError+ = TlvTruncated -- ^ a type, length or value was cut off+ | TlvNonMinimalBigSize -- ^ a type or length wasn't minimal+ | TlvNotStrictlyIncreasing -- ^ types out of order or repeated+ | TlvUnknownEvenType !Word64 -- ^ an even type the context doesn't know+ deriving (Eq, Show, Generic) --- TLV decoding ----------------------------------------------------------------+instance NFData TlvError --- | Decode a TLV stream without any known-type validation.------ This decoder only enforces structural validity:--- - Types must be strictly increasing--- - Lengths must not exceed bounds+-- | Encode a 'TlvStream'. ----- All records are returned regardless of type. Note: this does NOT--- enforce the BOLT #1 unknown-even-type rule. Use 'decodeTlvStreamWith'--- with an appropriate predicate for spec-compliant parsing.-decodeTlvStreamRaw :: BS.ByteString -> Either TlvError TlvStream-decodeTlvStreamRaw = go Nothing []+-- >>> fmap encode_tlv_stream (tlv_stream [TlvRecord 1 "a"])+-- Just "\SOH\SOHa"+encode_tlv_stream :: TlvStream -> BS.ByteString+encode_tlv_stream (TlvStream rs) = mconcat (concatMap enc rs) where- go :: Maybe Word64 -> [TlvRecord] -> BS.ByteString- -> Either TlvError TlvStream- go !_ !acc !bs- | BS.null bs = Right (unsafeTlvStream (reverse acc))- go !mPrevType !acc !bs = do- (typ, rest1) <- maybe (Left TlvNonMinimalEncoding) Right- (decodeBigSize bs)- -- Strictly increasing check- case mPrevType of- Just prevType -> when (typ <= prevType) $- Left TlvNotStrictlyIncreasing- Nothing -> pure ()- (len, rest2) <- maybe (Left TlvNonMinimalEncoding) Right- (decodeBigSize rest1)- -- Length bounds check- when (fromIntegral len > BS.length rest2) $- Left TlvLengthExceedsBounds- let !val = BS.take (fromIntegral len) rest2- !rest3 = BS.drop (fromIntegral len) rest2- !rec = TlvRecord typ val- go (Just typ) (rec : acc) rest3+ enc (TlvRecord t v) =+ [encode_bigsize t, encode_bigsize (fromIntegral (BS.length v)), v] --- | Decode a TLV stream with configurable known-type predicate.+-- | Decode a TLV stream occupying the whole input, given a predicate+-- identifying the types known in the stream's context. ----- Per BOLT #1:--- - Types must be strictly increasing--- - Unknown even types cause failure--- - Unknown odd types are skipped+-- Per BOLT #1, decoding fails on truncation, non-minimal BigSize+-- encodings, types that are not strictly increasing, and unknown+-- even types. Every record is kept, including unknown odd ones, so+-- re-encoding reproduces the input. ----- The predicate determines which types are "known" for the context.-decodeTlvStreamWith- :: (Word64 -> Bool) -- ^ Predicate: is this type known?+-- >>> let Right s = decode_tlv_stream (== 1) "\SOH\SOHa\ETX\NUL"+-- >>> map tlv_type (un_tlv_stream s)+-- [1,3]+-- >>> decode_tlv_stream (== 1) "\STX\NUL"+-- Left (TlvUnknownEvenType 2)+decode_tlv_stream+ :: (Word64 -> Bool) -- ^ is this type known? -> BS.ByteString -> Either TlvError TlvStream-decodeTlvStreamWith isKnown = go Nothing []- where- go :: Maybe Word64 -> [TlvRecord] -> BS.ByteString- -> Either TlvError TlvStream- go !_ !acc !bs- | BS.null bs = Right (unsafeTlvStream (reverse acc))- go !mPrevType !acc !bs = do- (typ, rest1) <- maybe (Left TlvNonMinimalEncoding) Right- (decodeBigSize bs)- -- Strictly increasing check- case mPrevType of- Just prevType -> when (typ <= prevType) $- Left TlvNotStrictlyIncreasing- Nothing -> pure ()- (len, rest2) <- maybe (Left TlvNonMinimalEncoding) Right- (decodeBigSize rest1)- -- Length bounds check- when (fromIntegral len > BS.length rest2) $- Left TlvLengthExceedsBounds- let !val = BS.take (fromIntegral len) rest2- !rest3 = BS.drop (fromIntegral len) rest2- !rec = TlvRecord typ val- -- Unknown type handling: even = fail, odd = skip- if isKnown typ- then go (Just typ) (rec : acc) rest3- else if even typ- then Left (TlvUnknownEvenType typ)- else go (Just typ) acc rest3 -- skip unknown odd---- | Decode a TLV stream with BOLT #1 init_tlvs validation.------ This uses the default known types for init messages (1 and 3).--- For other contexts, use 'decodeTlvStreamWith' with an appropriate--- predicate.-decodeTlvStream :: BS.ByteString -> Either TlvError TlvStream-decodeTlvStream = decodeTlvStreamWith isInitTlvType- where- isInitTlvType :: Word64 -> Bool- isInitTlvType 1 = True -- networks- isInitTlvType 3 = True -- remote_addr- isInitTlvType _ = False---- Init TLV types ------------------------------------------------------------------ | TLV records for init message.-data InitTlv- = InitNetworks ![ChainHash] -- ^ Type 1: chain hashes (32 bytes each)- | InitRemoteAddr !BS.ByteString -- ^ Type 3: remote address- deriving stock (Eq, Show, Generic)--instance NFData InitTlv---- | Parse init TLVs from a TLV stream.-parseInitTlvs :: TlvStream -> Either TlvError [InitTlv]-parseInitTlvs (TlvStream recs) = traverse parseOne recs+decode_tlv_stream known = go Nothing [] where- parseOne (TlvRecord 1 val)- | BS.length val `mod` 32 == 0 =- Right (InitNetworks (map mkChainHash (chunksOf 32 val)))- | otherwise = Left (TlvInvalidKnownType 1)- parseOne (TlvRecord 3 val) = Right (InitRemoteAddr val)- parseOne (TlvRecord t _) = Left (TlvUnknownEvenType t)-- -- Each chunk is exactly 32 bytes from chunksOf, so chainHash always- -- succeeds. We use a partial pattern match as the Nothing case is- -- unreachable given our chunksOf guarantee.- mkChainHash bs = case chainHash bs of- Just ch -> ch- Nothing -> error "parseInitTlvs: impossible - chunk is not 32 bytes"---- | Split bytestring into chunks of given size.-chunksOf :: Int -> BS.ByteString -> [BS.ByteString]-chunksOf !n !bs- | BS.null bs = []- | otherwise =- let (!chunk, !rest) = BS.splitAt n bs- in chunk : chunksOf n rest+ go !_ !acc !bs | BS.null bs = Right (TlvStream (reverse acc))+ go !prev !acc !bs = do+ (t, r0) <- bigsize bs+ case prev of+ Just p | t <= p -> Left TlvNotStrictlyIncreasing+ _ -> pure ()+ (l, r1) <- bigsize r0+ if l > fromIntegral (BS.length r1)+ then Left TlvTruncated+ else do+ let !n = fromIntegral l+ !v = BU.unsafeTake n r1+ !r2 = BU.unsafeDrop n r1+ if not (known t) && even t+ then Left (TlvUnknownEvenType t)+ else go (Just t) (TlvRecord t v : acc) r2 --- | Encode init TLVs to a TLV stream.-encodeInitTlvs :: [InitTlv] -> TlvStream-encodeInitTlvs = unsafeTlvStream . map toRecord- where- toRecord (InitNetworks chains) =- TlvRecord 1 (mconcat (map unChainHash chains))- toRecord (InitRemoteAddr addr) =- TlvRecord 3 addr+ bigsize b = case decode_bigsize_detailed b of+ Right r -> Right r+ Left BigSizeTruncated -> Left TlvTruncated+ Left BigSizeNonMinimal -> Left TlvNonMinimalBigSize
ppad-bolt1.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: ppad-bolt1-version: 0.0.1+version: 0.1.0 synopsis: Base protocol per BOLT #1 license: MIT license-file: LICENSE@@ -8,7 +8,7 @@ maintainer: jared@ppad.tech category: Cryptography build-type: Simple-tested-with: GHC == 9.10.3+tested-with: GHC == { 9.10.3 } extra-doc-files: CHANGELOG description: Base protocol, per@@ -25,6 +25,7 @@ -Wall exposed-modules: Lightning.Protocol.BOLT1+ other-modules: Lightning.Protocol.BOLT1.Codec Lightning.Protocol.BOLT1.Message Lightning.Protocol.BOLT1.Prim@@ -33,6 +34,7 @@ base >= 4.9 && < 5 , bytestring >= 0.9 && < 0.13 , deepseq >= 1.4 && < 1.6+ , ppad-bolt9 >= 0.1 && < 0.2 test-suite bolt1-tests type: exitcode-stdio-1.0@@ -41,13 +43,14 @@ main-is: Main.hs ghc-options:- -rtsopts -Wall -O2+ -rtsopts -Wall build-depends: base , bytestring , ppad-base16 , ppad-bolt1+ , ppad-bolt9 , tasty , tasty-hunit , tasty-quickcheck@@ -66,8 +69,8 @@ base , bytestring , criterion- , deepseq , ppad-bolt1+ , ppad-bolt9 benchmark bolt1-weigh type: exitcode-stdio-1.0@@ -82,6 +85,6 @@ build-depends: base , bytestring- , deepseq , ppad-bolt1+ , ppad-bolt9 , weigh
test/Main.hs view
@@ -4,696 +4,545 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16-import Lightning.Protocol.BOLT1-import Test.Tasty-import Test.Tasty.HUnit-import Test.Tasty.QuickCheck--main :: IO ()-main = defaultMain $ testGroup "ppad-bolt1" [- bigsize_tests- , primitive_tests- , signed_tests- , truncated_tests- , minsigned_tests- , tlv_tests- , message_tests- , envelope_tests- , extension_tests- , bounds_tests- , property_tests- ]---- BigSize test vectors from BOLT #1 Appendix A ---------------------------------bigsize_tests :: TestTree-bigsize_tests = testGroup "BigSize (Appendix A)" [- testCase "zero" $- encodeBigSize 0 @?= unhex "00"- , testCase "one byte high (252)" $- encodeBigSize 252 @?= unhex "fc"- , testCase "two byte low (253)" $- encodeBigSize 253 @?= unhex "fd00fd"- , testCase "two byte high (65535)" $- encodeBigSize 65535 @?= unhex "fdffff"- , testCase "four byte low (65536)" $- encodeBigSize 65536 @?= unhex "fe00010000"- , testCase "four byte high (4294967295)" $- encodeBigSize 4294967295 @?= unhex "feffffffff"- , testCase "eight byte low (4294967296)" $- encodeBigSize 4294967296 @?= unhex "ff0000000100000000"- , testCase "eight byte high (max u64)" $- encodeBigSize 18446744073709551615 @?= unhex "ffffffffffffffffff"- , testCase "decode zero" $- decodeBigSize (unhex "00") @?= Just (0, "")- , testCase "decode 252" $- decodeBigSize (unhex "fc") @?= Just (252, "")- , testCase "decode 253" $- decodeBigSize (unhex "fd00fd") @?= Just (253, "")- , testCase "decode 65535" $- decodeBigSize (unhex "fdffff") @?= Just (65535, "")- , testCase "decode 65536" $- decodeBigSize (unhex "fe00010000") @?= Just (65536, "")- , testCase "decode 4294967295" $- decodeBigSize (unhex "feffffffff") @?= Just (4294967295, "")- , testCase "decode 4294967296" $- decodeBigSize (unhex "ff0000000100000000") @?= Just (4294967296, "")- , testCase "decode max u64" $- decodeBigSize (unhex "ffffffffffffffffff") @?=- Just (18446744073709551615, "")- , testCase "non-minimal 2-byte fails" $- decodeBigSize (unhex "fd00fc") @?= Nothing- , testCase "non-minimal 4-byte fails" $- decodeBigSize (unhex "fe0000ffff") @?= Nothing- , testCase "non-minimal 8-byte fails" $- decodeBigSize (unhex "ff00000000ffffffff") @?= Nothing- ]---- Primitive encode/decode tests -------------------------------------------------primitive_tests :: TestTree-primitive_tests = testGroup "Primitives" [- testCase "encodeU16 0x0102" $- encodeU16 0x0102 @?= BS.pack [0x01, 0x02]- , testCase "decodeU16 0x0102" $- decodeU16 (BS.pack [0x01, 0x02]) @?= Just (0x0102, "")- , testCase "encodeU32 0x01020304" $- encodeU32 0x01020304 @?= BS.pack [0x01, 0x02, 0x03, 0x04]- , testCase "decodeU32 0x01020304" $- decodeU32 (BS.pack [0x01, 0x02, 0x03, 0x04]) @?= Just (0x01020304, "")- , testCase "encodeU64" $- encodeU64 0x0102030405060708 @?=- BS.pack [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]- , testCase "decodeU64" $- decodeU64 (BS.pack [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]) @?=- Just (0x0102030405060708, "")- , testCase "decodeU16 insufficient" $- decodeU16 (BS.pack [0x01]) @?= Nothing- , testCase "decodeU32 insufficient" $- decodeU32 (BS.pack [0x01, 0x02]) @?= Nothing- , testCase "decodeU64 insufficient" $- decodeU64 (BS.pack [0x01, 0x02, 0x03, 0x04]) @?= Nothing- ]---- Signed integer tests -----------------------------------------------------------signed_tests :: TestTree-signed_tests = testGroup "Signed integers" [- testCase "encodeS8 42" $- encodeS8 42 @?= BS.pack [0x2a]- , testCase "encodeS8 -42" $- encodeS8 (-42) @?= BS.pack [0xd6]- , testCase "encodeS8 127" $- encodeS8 127 @?= BS.pack [0x7f]- , testCase "encodeS8 -128" $- encodeS8 (-128) @?= BS.pack [0x80]- , testCase "decodeS8 42" $- decodeS8 (BS.pack [0x2a]) @?= Just (42, "")- , testCase "decodeS8 -42" $- decodeS8 (BS.pack [0xd6]) @?= Just (-42, "")- , testCase "encodeS16 -1" $- encodeS16 (-1) @?= BS.pack [0xff, 0xff]- , testCase "encodeS16 32767" $- encodeS16 32767 @?= BS.pack [0x7f, 0xff]- , testCase "encodeS16 -32768" $- encodeS16 (-32768) @?= BS.pack [0x80, 0x00]- , testCase "decodeS16 -1" $- decodeS16 (BS.pack [0xff, 0xff]) @?= Just (-1, "")- , testCase "encodeS32 -1" $- encodeS32 (-1) @?= BS.pack [0xff, 0xff, 0xff, 0xff]- , testCase "encodeS32 2147483647" $- encodeS32 2147483647 @?= BS.pack [0x7f, 0xff, 0xff, 0xff]- , testCase "encodeS32 -2147483648" $- encodeS32 (-2147483648) @?= BS.pack [0x80, 0x00, 0x00, 0x00]- , testCase "decodeS32 -1" $- decodeS32 (BS.pack [0xff, 0xff, 0xff, 0xff]) @?= Just (-1, "")- , testCase "encodeS64 -1" $- encodeS64 (-1) @?=- BS.pack [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]- , testCase "decodeS64 -1" $- decodeS64 (BS.pack [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) @?=- Just (-1, "")- ]---- Truncated unsigned integer tests -----------------------------------------------truncated_tests :: TestTree-truncated_tests = testGroup "Truncated unsigned integers" [- testCase "encodeTu16 0" $- encodeTu16 0 @?= ""- , testCase "encodeTu16 1" $- encodeTu16 1 @?= BS.pack [0x01]- , testCase "encodeTu16 255" $- encodeTu16 255 @?= BS.pack [0xff]- , testCase "encodeTu16 256" $- encodeTu16 256 @?= BS.pack [0x01, 0x00]- , testCase "encodeTu16 65535" $- encodeTu16 65535 @?= BS.pack [0xff, 0xff]- , testCase "decodeTu16 0 bytes" $- decodeTu16 0 "" @?= Just (0, "")- , testCase "decodeTu16 1 byte" $- decodeTu16 1 (BS.pack [0x01]) @?= Just (1, "")- , testCase "decodeTu16 2 bytes" $- decodeTu16 2 (BS.pack [0x01, 0x00]) @?= Just (256, "")- , testCase "decodeTu16 non-minimal fails" $- decodeTu16 2 (BS.pack [0x00, 0x01]) @?= Nothing- , testCase "encodeTu32 0" $- encodeTu32 0 @?= ""- , testCase "encodeTu32 1" $- encodeTu32 1 @?= BS.pack [0x01]- , testCase "encodeTu32 0x010000" $- encodeTu32 0x010000 @?= BS.pack [0x01, 0x00, 0x00]- , testCase "encodeTu32 0x01000000" $- encodeTu32 0x01000000 @?= BS.pack [0x01, 0x00, 0x00, 0x00]- , testCase "decodeTu32 0 bytes" $- decodeTu32 0 "" @?= Just (0, "")- , testCase "decodeTu32 3 bytes" $- decodeTu32 3 (BS.pack [0x01, 0x00, 0x00]) @?= Just (0x010000, "")- , testCase "decodeTu32 non-minimal fails" $- decodeTu32 3 (BS.pack [0x00, 0x01, 0x00]) @?= Nothing- , testCase "encodeTu64 0" $- encodeTu64 0 @?= ""- , testCase "encodeTu64 0x0100000000" $- encodeTu64 0x0100000000 @?= BS.pack [0x01, 0x00, 0x00, 0x00, 0x00]- , testCase "decodeTu64 5 bytes" $- decodeTu64 5 (BS.pack [0x01, 0x00, 0x00, 0x00, 0x00]) @?=- Just (0x0100000000, "")- , testCase "decodeTu64 non-minimal fails" $- decodeTu64 5 (BS.pack [0x00, 0x01, 0x00, 0x00, 0x00]) @?= Nothing- ]---- Minimal signed integer tests (Appendix D) --------------------------------------minsigned_tests :: TestTree-minsigned_tests = testGroup "Minimal signed (Appendix D)" [- -- Test vectors from BOLT #1 Appendix D- testCase "encode 0" $- encodeMinSigned 0 @?= unhex "00"- , testCase "encode 42" $- encodeMinSigned 42 @?= unhex "2a"- , testCase "encode -42" $- encodeMinSigned (-42) @?= unhex "d6"- , testCase "encode 127" $- encodeMinSigned 127 @?= unhex "7f"- , testCase "encode -128" $- encodeMinSigned (-128) @?= unhex "80"- , testCase "encode 128" $- encodeMinSigned 128 @?= unhex "0080"- , testCase "encode -129" $- encodeMinSigned (-129) @?= unhex "ff7f"- , testCase "encode 15000" $- encodeMinSigned 15000 @?= unhex "3a98"- , testCase "encode -15000" $- encodeMinSigned (-15000) @?= unhex "c568"- , testCase "encode 32767" $- encodeMinSigned 32767 @?= unhex "7fff"- , testCase "encode -32768" $- encodeMinSigned (-32768) @?= unhex "8000"- , testCase "encode 32768" $- encodeMinSigned 32768 @?= unhex "00008000"- , testCase "encode -32769" $- encodeMinSigned (-32769) @?= unhex "ffff7fff"- , testCase "encode 21000000" $- encodeMinSigned 21000000 @?= unhex "01406f40"- , testCase "encode -21000000" $- encodeMinSigned (-21000000) @?= unhex "febf90c0"- , testCase "encode 2147483647" $- encodeMinSigned 2147483647 @?= unhex "7fffffff"- , testCase "encode -2147483648" $- encodeMinSigned (-2147483648) @?= unhex "80000000"- , testCase "encode 2147483648" $- encodeMinSigned 2147483648 @?= unhex "0000000080000000"- , testCase "encode -2147483649" $- encodeMinSigned (-2147483649) @?= unhex "ffffffff7fffffff"- , testCase "encode 500000000000" $- encodeMinSigned 500000000000 @?= unhex "000000746a528800"- , testCase "encode -500000000000" $- encodeMinSigned (-500000000000) @?= unhex "ffffff8b95ad7800"- , testCase "encode max int64" $- encodeMinSigned 9223372036854775807 @?= unhex "7fffffffffffffff"- , testCase "encode min int64" $- encodeMinSigned (-9223372036854775808) @?= unhex "8000000000000000"- -- Decode tests- , testCase "decode 1-byte 42" $- decodeMinSigned 1 (unhex "2a") @?= Just (42, "")- , testCase "decode 1-byte -42" $- decodeMinSigned 1 (unhex "d6") @?= Just (-42, "")- , testCase "decode 2-byte 128" $- decodeMinSigned 2 (unhex "0080") @?= Just (128, "")- , testCase "decode 2-byte -129" $- decodeMinSigned 2 (unhex "ff7f") @?= Just (-129, "")- , testCase "decode 4-byte 32768" $- decodeMinSigned 4 (unhex "00008000") @?= Just (32768, "")- , testCase "decode 8-byte 2147483648" $- decodeMinSigned 8 (unhex "0000000080000000") @?= Just (2147483648, "")- -- Minimality rejection- , testCase "decode 2-byte for 1-byte value fails" $- decodeMinSigned 2 (unhex "0042") @?= Nothing -- 42 fits in 1 byte- , testCase "decode 4-byte for 2-byte value fails" $- decodeMinSigned 4 (unhex "00000080") @?= Nothing -- 128 fits in 2 bytes- , testCase "decode 8-byte for 4-byte value fails" $- decodeMinSigned 8 (unhex "0000000000008000") @?= Nothing -- 32768 fits in 4- ]---- TLV tests ---------------------------------------------------------------------tlv_tests :: TestTree-tlv_tests = testGroup "TLV" [- testGroup "tlvStream smart constructor" [- testCase "empty list succeeds" $- tlvStream [] @?= Just (unsafeTlvStream [])- , testCase "single record succeeds" $- tlvStream [TlvRecord 1 "a"] @?= Just (unsafeTlvStream [TlvRecord 1 "a"])- , testCase "strictly increasing succeeds" $- tlvStream [TlvRecord 1 "a", TlvRecord 3 "b", TlvRecord 5 "c"] @?=- Just (unsafeTlvStream [TlvRecord 1 "a", TlvRecord 3 "b",- TlvRecord 5 "c"])- , testCase "non-increasing fails" $- tlvStream [TlvRecord 5 "a", TlvRecord 3 "b"] @?= Nothing- , testCase "duplicate types fails" $- tlvStream [TlvRecord 1 "a", TlvRecord 1 "b"] @?= Nothing- , testCase "equal adjacent types fails" $- tlvStream [TlvRecord 1 "a", TlvRecord 2 "b", TlvRecord 2 "c"] @?=- Nothing- ]- , testCase "empty stream" $- decodeTlvStream "" @?= Right (unsafeTlvStream [])- , testCase "single record type 1" $ do- let bs = mconcat [- encodeBigSize 1 -- type- , encodeBigSize 32 -- length- , BS.replicate 32 0x00 -- value (chain hash)- ]- case decodeTlvStream bs of- Right stream -> case unTlvStream stream of- [r] -> do- tlvType r @?= 1- BS.length (tlvValue r) @?= 32- _ -> assertFailure "expected single record"- Left e -> assertFailure $ "unexpected error: " ++ show e- , testCase "strictly increasing types" $ do- let bs = mconcat [- encodeBigSize 1, encodeBigSize 0- , encodeBigSize 3, encodeBigSize 4, "test"- ]- case decodeTlvStream bs of- Right stream -> length (unTlvStream stream) @?= 2- Left e -> assertFailure $ "unexpected error: " ++ show e- , testCase "non-increasing types fails" $ do- let bs = mconcat [- encodeBigSize 3, encodeBigSize 0- , encodeBigSize 1, encodeBigSize 0- ]- case decodeTlvStream bs of- Left TlvNotStrictlyIncreasing -> pure ()- other -> assertFailure $ "expected TlvNotStrictlyIncreasing: " ++- show other- , testCase "duplicate types fails" $ do- let bs = mconcat [- encodeBigSize 1, encodeBigSize 0- , encodeBigSize 1, encodeBigSize 0- ]- case decodeTlvStream bs of- Left TlvNotStrictlyIncreasing -> pure ()- other -> assertFailure $ "expected TlvNotStrictlyIncreasing: " ++- show other- , testCase "unknown even type fails" $ do- let bs = mconcat [encodeBigSize 2, encodeBigSize 0]- case decodeTlvStream bs of- Left (TlvUnknownEvenType 2) -> pure ()- other -> assertFailure $ "expected TlvUnknownEvenType: " ++ show other- , testCase "unknown odd type skipped" $ do- let bs = mconcat [- encodeBigSize 5, encodeBigSize 2, "hi"- , encodeBigSize 7, encodeBigSize 0- ]- case decodeTlvStream bs of- Right stream | null (unTlvStream stream) -> pure () -- both skipped- other -> assertFailure $ "expected empty stream: " ++ show other- , testCase "length exceeds bounds fails" $ do- let bs = mconcat [encodeBigSize 1, encodeBigSize 100, "short"]- case decodeTlvStream bs of- Left TlvLengthExceedsBounds -> pure ()- other -> assertFailure $ "expected TlvLengthExceedsBounds: " ++- show other- , testCase "decodeTlvStreamWith custom predicate" $ do- -- Use a predicate that only knows type 5- let isKnown t = t == 5- bs = mconcat [- encodeBigSize 5, encodeBigSize 2, "hi"- ]- case decodeTlvStreamWith isKnown bs of- Right stream -> case unTlvStream stream of- [r] -> tlvType r @?= 5- _ -> assertFailure "expected single record"- Left e -> assertFailure $ "unexpected error: " ++ show e- , testCase "decodeTlvStreamRaw returns all records" $ do- let bs = mconcat [- encodeBigSize 2, encodeBigSize 1, "a" -- even type- , encodeBigSize 5, encodeBigSize 1, "b" -- odd type- ]- case decodeTlvStreamRaw bs of- Right stream -> length (unTlvStream stream) @?= 2- Left e -> assertFailure $ "unexpected error: " ++ show e- ]---- Message encode/decode tests ---------------------------------------------------message_tests :: TestTree-message_tests = testGroup "Messages" [- testGroup "Init" [- testCase "encode/decode minimal init" $ do- let msg = Init "" "" []- case encodeMessage (MsgInitVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgInit encoded of- Right (MsgInitVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- , testCase "encode/decode init with features" $ do- let msg = Init (BS.pack [0x01]) (BS.pack [0x02, 0x0a]) []- case encodeMessage (MsgInitVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgInit encoded of- Right (MsgInitVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- , testCase "encode/decode init with networks TLV" $ do- let ch = unsafeChainHash (BS.replicate 32 0xab)- msg = Init "" "" [InitNetworks [ch]]- case encodeMessage (MsgInitVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgInit encoded of- Right (MsgInitVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- ]- , testGroup "Error" [- testCase "encode/decode error" $ do- let cid = unsafeChannelId (BS.replicate 32 0xff)- msg = Error cid "something went wrong"- case encodeMessage (MsgErrorVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgError encoded of- Right (MsgErrorVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- , testCase "error insufficient channel_id" $ do- case decodeMessage MsgError (BS.replicate 31 0x00) of- Left DecodeInsufficientBytes -> pure ()- other -> assertFailure $ "expected insufficient: " ++ show other- ]- , testGroup "Warning" [- testCase "encode/decode warning" $ do- let cid = unsafeChannelId (BS.replicate 32 0x00)- msg = Warning cid "be careful"- case encodeMessage (MsgWarningVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgWarning encoded of- Right (MsgWarningVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- ]- , testGroup "Ping" [- testCase "encode/decode ping" $ do- let msg = Ping 100 (BS.replicate 10 0x00)- case encodeMessage (MsgPingVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgPing encoded of- Right (MsgPingVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- , testCase "ping with zero ignored" $ do- let msg = Ping 50 ""- case encodeMessage (MsgPingVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgPing encoded of- Right (MsgPingVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- ]- , testGroup "Pong" [- testCase "encode/decode pong" $ do- let msg = Pong (BS.replicate 100 0x00)- case encodeMessage (MsgPongVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgPong encoded of- Right (MsgPongVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- ]- , testGroup "PeerStorage" [- testCase "encode/decode peer_storage" $ do- let msg = PeerStorage "encrypted blob data"- case encodeMessage (MsgPeerStorageVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgPeerStorage encoded of- Right (MsgPeerStorageVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- ]- , testGroup "PeerStorageRetrieval" [- testCase "encode/decode peer_storage_retrieval" $ do- let msg = PeerStorageRetrieval "retrieved blob"- case encodeMessage (MsgPeerStorageRetrievalVal msg) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeMessage MsgPeerStorageRet encoded of- Right (MsgPeerStorageRetrievalVal decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- ]- , testGroup "Unknown types" [- testCase "decodeMessage unknown even type" $ do- case decodeMessage (MsgUnknown 100) "payload" of- Left (DecodeUnknownEvenType 100) -> pure ()- other -> assertFailure $ "expected unknown even: " ++ show other- , testCase "decodeMessage unknown odd type" $ do- case decodeMessage (MsgUnknown 101) "payload" of- Left (DecodeUnknownOddType 101) -> pure ()- other -> assertFailure $ "expected unknown odd: " ++ show other- ]- ]---- Envelope tests ----------------------------------------------------------------envelope_tests :: TestTree-envelope_tests = testGroup "Envelope" [- testCase "encode/decode init envelope" $ do- let msg = MsgInitVal (Init "" "" [])- case encodeEnvelope msg Nothing of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeEnvelope encoded of- Right (Just decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- , testCase "encode/decode ping envelope" $ do- let msg = MsgPingVal (Ping 10 "")- case encodeEnvelope msg Nothing of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeEnvelope encoded of- Right (Just decoded, _) -> decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- , testCase "unknown even type fails" $ do- let bs = encodeU16 100 <> "payload" -- 100 is even, unknown- case decodeEnvelope bs of- Left (DecodeUnknownEvenType 100) -> pure ()- other -> assertFailure $ "expected unknown even: " ++ show other- , testCase "unknown odd type ignored" $ do- let bs = encodeU16 101 <> "payload" -- 101 is odd, unknown- case decodeEnvelope bs of- Right (Nothing, Nothing) -> pure () -- ignored- other -> assertFailure $ "expected (Nothing, Nothing): " ++ show other- , testCase "insufficient bytes for type" $ do- case decodeEnvelope (BS.pack [0x00]) of- Left DecodeInsufficientBytes -> pure ()- other -> assertFailure $ "expected insufficient: " ++ show other- , testCase "message type codes" $ do- msgTypeWord MsgInit @?= 16- msgTypeWord MsgError @?= 17- msgTypeWord MsgPing @?= 18- msgTypeWord MsgPong @?= 19- msgTypeWord MsgWarning @?= 1- msgTypeWord MsgPeerStorage @?= 7- msgTypeWord MsgPeerStorageRet @?= 9- ]---- Extension TLV tests -----------------------------------------------------------extension_tests :: TestTree-extension_tests = testGroup "Extension TLV" [- testCase "encode envelope with extension (odd type)" $ do- let msg = MsgPingVal (Ping 10 "")- ext = unsafeTlvStream [TlvRecord 101 "extension data"] -- odd type- case encodeEnvelope msg (Just ext) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> do- -- Should contain message + extension- assertBool "encoded should be longer" (BS.length encoded > 6)- , testCase "decode envelope with odd extension - skipped per BOLT#1" $ do- -- Per BOLT #1: unknown odd types are ignored (skipped)- let msg = MsgPingVal (Ping 10 "")- ext = unsafeTlvStream [TlvRecord 101 "ext"] -- odd type- case encodeEnvelope msg (Just ext) of- Left e -> assertFailure $ "encode failed: " ++ show e- Right encoded -> case decodeEnvelope encoded of- Right (Just decoded, Just stream)- | null (unTlvStream stream) -> do- -- Extension is empty because unknown odd types are skipped- decoded @?= msg- other -> assertFailure $ "unexpected: " ++ show other- , testCase "decode envelope with unknown even extension fails" $ do- -- Per BOLT #1: unknown even types must cause failure- let pingPayload = mconcat [encodeU16 10, encodeU16 0] -- numPong=10, len=0- extTlv = mconcat [encodeBigSize 100, encodeBigSize 3, "abc"] -- even!- envelope = encodeU16 18 <> pingPayload <> extTlv -- type 18 = ping- case decodeEnvelope envelope of- Left (DecodeInvalidExtension (TlvUnknownEvenType 100)) -> pure ()- other -> assertFailure $ "expected unknown even error: " ++ show other- , testCase "decode envelope with invalid extension fails" $ do- -- Ping + invalid TLV (non-strictly-increasing)- let pingPayload = mconcat [encodeU16 10, encodeU16 0]- badTlv = mconcat [- encodeBigSize 101, encodeBigSize 1, "a" -- odd types for this test- , encodeBigSize 51, encodeBigSize 1, "b" -- 51 < 101, invalid- ]- envelope = encodeU16 18 <> pingPayload <> badTlv- case decodeEnvelope envelope of- Left (DecodeInvalidExtension TlvNotStrictlyIncreasing) -> pure ()- other -> assertFailure $ "expected invalid extension: " ++ show other- , testCase "unknown even in extension fails even with odd types present" $ do- -- Mixed odd and even - should fail on the even type- let pingPayload = mconcat [encodeU16 10, encodeU16 0]- extTlv = mconcat [- encodeBigSize 101, encodeBigSize 1, "a" -- odd, would be skipped- , encodeBigSize 200, encodeBigSize 1, "b" -- even, must fail- ]- envelope = encodeU16 18 <> pingPayload <> extTlv- case decodeEnvelope envelope of- Left (DecodeInvalidExtension (TlvUnknownEvenType 200)) -> pure ()- other -> assertFailure $ "expected unknown even error: " ++ show other- ]---- Bounds checking tests ---------------------------------------------------------bounds_tests :: TestTree-bounds_tests = testGroup "Bounds checking" [- testCase "encode ping with oversized ignored fails" $ do- let msg = Ping 10 (BS.replicate 70000 0x00) -- > 65535- case encodeMessage (MsgPingVal msg) of- Left EncodeLengthOverflow -> pure ()- other -> assertFailure $ "expected overflow: " ++ show other- , testCase "encode pong with oversized ignored fails" $ do- let msg = Pong (BS.replicate 70000 0x00)- case encodeMessage (MsgPongVal msg) of- Left EncodeLengthOverflow -> pure ()- other -> assertFailure $ "expected overflow: " ++ show other- , testCase "encode error with oversized data fails" $ do- let cid = unsafeChannelId (BS.replicate 32 0x00)- msg = Error cid (BS.replicate 70000 0x00)- case encodeMessage (MsgErrorVal msg) of- Left EncodeLengthOverflow -> pure ()- other -> assertFailure $ "expected overflow: " ++ show other- , testCase "encode init with oversized features fails" $ do- let msg = Init "" (BS.replicate 70000 0x00) []- case encodeMessage (MsgInitVal msg) of- Left EncodeLengthOverflow -> pure ()- other -> assertFailure $ "expected overflow: " ++ show other- , testCase "encode peer_storage with oversized blob fails" $ do- let msg = PeerStorage (BS.replicate 70000 0x00)- case encodeMessage (MsgPeerStorageVal msg) of- Left EncodeLengthOverflow -> pure ()- other -> assertFailure $ "expected overflow: " ++ show other- , testCase "encode envelope exceeding 65535 bytes fails" $ do- -- Create a message that fits in encodeMessage but combined with- -- extension exceeds 65535 bytes total- let msg = MsgPongVal (Pong (BS.replicate 60000 0x00))- ext = unsafeTlvStream [TlvRecord 101 (BS.replicate 10000 0x00)]- case encodeEnvelope msg (Just ext) of- Left EncodeMessageTooLarge -> pure ()- other -> assertFailure $ "expected message too large: " ++ show other- ]---- Property tests ----------------------------------------------------------------property_tests :: TestTree-property_tests = testGroup "Properties" [- testProperty "BigSize roundtrip" $ \(NonNegative n) ->- case decodeBigSize (encodeBigSize n) of- Just (m, rest) -> m == n && BS.null rest- Nothing -> False- , testProperty "U16 roundtrip" $ \w ->- decodeU16 (encodeU16 w) == Just (w, "")- , testProperty "U32 roundtrip" $ \w ->- decodeU32 (encodeU32 w) == Just (w, "")- , testProperty "U64 roundtrip" $ \w ->- decodeU64 (encodeU64 w) == Just (w, "")- , testProperty "Ping roundtrip" $ \(NonNegative num) bs ->- let ignored = BS.pack (take 1000 bs) -- limit size- msg = Ping (fromIntegral (num `mod` 65536 :: Integer)) ignored- in case encodeMessage (MsgPingVal msg) of- Left _ -> False- Right encoded -> case decodeMessage MsgPing encoded of- Right (MsgPingVal decoded, rest) ->- decoded == msg && BS.null rest- _ -> False- , testProperty "Pong roundtrip" $ \bs ->- let ignored = BS.pack (take 1000 bs)- msg = Pong ignored- in case encodeMessage (MsgPongVal msg) of- Left _ -> False- Right encoded -> case decodeMessage MsgPong encoded of- Right (MsgPongVal decoded, rest) ->- decoded == msg && BS.null rest- _ -> False- , testProperty "PeerStorage roundtrip" $ \bs ->- let blob = BS.pack (take 1000 bs)- msg = PeerStorage blob- in case encodeMessage (MsgPeerStorageVal msg) of- Left _ -> False- Right encoded -> case decodeMessage MsgPeerStorage encoded of- Right (MsgPeerStorageVal decoded, rest) ->- decoded == msg && BS.null rest- _ -> False- , testProperty "Error roundtrip" $ \bs ->- let cid = unsafeChannelId (BS.replicate 32 0x00)- dat = BS.pack (take 1000 bs)- msg = Error cid dat- in case encodeMessage (MsgErrorVal msg) of- Left _ -> False- Right encoded -> case decodeMessage MsgError encoded of- Right (MsgErrorVal decoded, rest) ->- decoded == msg && BS.null rest- _ -> False- , testProperty "Envelope with odd extension (skipped per BOLT#1)" $ \bs ->- -- Unknown odd types in extensions are skipped per BOLT #1- let msg = MsgPingVal (Ping 42 "")- extData = BS.pack (take 100 bs)- ext = unsafeTlvStream [TlvRecord 101 extData] -- odd type, skipped- in case encodeEnvelope msg (Just ext) of- Left _ -> False- Right encoded -> case decodeEnvelope encoded of- -- Extension should be empty (odd types skipped)- Right (Just decoded, Just stream) ->- null (unTlvStream stream) && decoded == msg- _ -> False- ]---- Helpers ------------------------------------------------------------------------- | Construct a 'ChannelId' from a known-valid 32-byte 'BS.ByteString'.------ Uses 'error' for invalid input since all channel IDs in tests are--- known-valid compile-time constants.-unsafeChannelId :: BS.ByteString -> ChannelId-unsafeChannelId bs = case channelId bs of- Just cid -> cid- Nothing -> error $ "unsafeChannelId: invalid length: " ++ show (BS.length bs)---- | Decode hex string (test-only helper).------ Uses 'error' for invalid hex since all hex literals in tests are--- known-valid compile-time constants. This is acceptable in test code--- where the failure would indicate a bug in the test itself.-unhex :: BS.ByteString -> BS.ByteString-unhex bs = case B16.decode bs of- Just r -> r- Nothing -> error $ "unhex: invalid hex literal: " ++ show bs---- | Construct a ChainHash from a bytestring (test-only helper).------ Uses 'error' for invalid input since all chain hashes in tests are--- known-valid 32-byte constants. This is acceptable in test code where--- the failure would indicate a bug in the test itself.-unsafeChainHash :: BS.ByteString -> ChainHash-unsafeChainHash bs = case chainHash bs of- Just c -> c- Nothing -> error $ "unsafeChainHash: not 32 bytes: " ++ show (BS.length bs)+import qualified Data.ByteString.Char8 as B8+import Data.Int (Int64)+import qualified Data.List as L+import Data.Maybe (fromMaybe, isJust, isNothing)+import Data.Word (Word16, Word32, Word64)+import Lightning.Protocol.BOLT1+import qualified Lightning.Protocol.BOLT9 as BOLT9+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++main :: IO ()+main = defaultMain $ testGroup "ppad-bolt1" [+ appendix_a+ , appendix_b+ , appendix_c+ , appendix_d+ , primitives+ , tlv+ , messages+ , properties+ ]++-- helpers --------------------------------------------------------------------++-- run an assertion on decoded hex (spaces and a 0x prefix are allowed),+-- failing the test if the literal isn't valid hex+with_hex :: String -> (BS.ByteString -> Assertion) -> Assertion+with_hex s k = case B16.decode (B8.pack (strip s)) of+ Nothing -> assertFailure ("invalid hex literal: " ++ s)+ Just bs -> k bs+ where+ strip = filter (/= ' ') . drop0x+ drop0x ('0':'x':r) = r+ drop0x r = r++is_left :: Either a b -> Bool+is_left = either (const True) (const False)++empty_fv :: BOLT9.FeatureVector+empty_fv = BOLT9.parse ""++-- Appendix A: BigSize --------------------------------------------------------++appendix_a :: TestTree+appendix_a = testGroup "Appendix A (BigSize)" [+ testGroup "decoding" (map dec ok_vectors)+ , testGroup "encoding" (map enc ok_vectors)+ , testGroup "decoding failures" (map bad bad_vectors)+ ]+ where+ dec (v, h) = testCase ("decode " ++ h) $ with_hex h $ \bs ->+ decode_bigsize bs @?= Just (v, "")+ enc (v, h) = testCase ("encode " ++ show v) $ with_hex h $ \bs ->+ encode_bigsize v @?= bs+ bad h = testCase ("reject " ++ show h) $ with_hex h $ \bs ->+ decode_bigsize bs @?= Nothing++ ok_vectors :: [(Word64, String)]+ ok_vectors = [+ (0, "00"), (252, "fc"), (253, "fd00fd"), (65535, "fdffff")+ , (65536, "fe00010000"), (4294967295, "feffffffff")+ , (4294967296, "ff0000000100000000")+ , (18446744073709551615, "ffffffffffffffffff")+ ]++ -- non-canonical encodings, short reads and no reads+ bad_vectors = [+ "fd00fc", "fe0000ffff", "ff00000000ffffffff"+ , "fd00", "feffff", "ffffffffff"+ , "", "fd", "fe", "ff"+ ]++-- Appendix B: TLV ------------------------------------------------------------++-- the n1 and n2 test namespaces, decoded with bolt1's primitives++data N1 = N1Tlv1 !Word64+ | N1Tlv2 !ShortChannelId+ | N1Tlv3 !Point !Word64 !Word64+ | N1Tlv4 !Word16+ deriving (Eq, Show)++decode_n1 :: BS.ByteString -> Either String [N1]+decode_n1 bs = do+ s <- either (Left . show) Right (decode_tlv_stream (`elem` n1_types) bs)+ sequence [ one r | r <- un_tlv_stream s, tlv_type r `elem` n1_types ]+ where+ n1_types = [1, 2, 3, 254]+ one (TlvRecord 1 v) = maybe (Left "tlv1") (Right . N1Tlv1)+ (decode_tu64 v)+ one (TlvRecord 2 v) = case decode_short_channel_id v of+ Just (c, "") -> Right (N1Tlv2 c)+ _ -> Left "tlv2"+ one (TlvRecord 3 v) = maybe (Left "tlv3") Right $ do+ (p, r0) <- decode_point v+ (a, r1) <- decode_u64 r0+ (b, r2) <- decode_u64 r1+ if BS.null r2 then pure (N1Tlv3 p a b) else Nothing+ one (TlvRecord 254 v) = case decode_u16 v of+ Just (c, "") -> Right (N1Tlv4 c)+ _ -> Left "tlv4"+ one _ = Left "unreachable"++decode_n2 :: BS.ByteString -> Either String [(Word64, Word64)]+decode_n2 bs = do+ s <- either (Left . show) Right (decode_tlv_stream (`elem` [0, 11]) bs)+ sequence [ one r | r <- un_tlv_stream s, tlv_type r `elem` [0, 11] ]+ where+ one (TlvRecord 0 v) = maybe (Left "tlv1") (Right . (,) 0) (decode_tu64 v)+ one (TlvRecord 11 v) =+ maybe (Left "tlv2") (Right . (,) 11 . fromIntegral) (decode_tu32 v)+ one _ = Left "unreachable"++appendix_b :: TestTree+appendix_b = testGroup "Appendix B (TLV)" [+ testGroup "failures in any namespace" [+ testCase h $ with_hex h $ \bs -> do+ assertBool "n1" (is_left (decode_n1 bs))+ assertBool "n2" (is_left (decode_n2 bs))+ | h <- any_fail ]+ , testGroup "n1 failures" [+ testCase h $ with_hex h $ \bs -> assertBool "n1" (is_left (decode_n1 bs))+ | h <- n1_fail ]+ , testGroup "n2 failures" [+ testCase h $ with_hex h $ \bs -> assertBool "n2" (is_left (decode_n2 bs))+ | h <- n2_fail ]+ , testGroup "ignored successes" [+ testCase h $ with_hex h $ \bs -> do+ decode_n1 bs @?= Right []+ decode_n2 bs @?= Right []+ | h <- any_ok ]+ , testGroup "n1 successes" [+ testCase h $ with_hex h $ \bs -> decode_n1 bs @?= Right [v]+ | (h, v) <- n1_ok ]+ , testCase "n1 tlv3" $ with_hex n1_tlv3 $ \bs ->+ with_hex node_id $ \pk -> case point pk of+ Nothing -> assertFailure "bad node_id"+ Just p -> decode_n1 bs @?= Right [N1Tlv3 p 1 2]+ , testCase "unknown even types fail when no types are known" $+ mapM_ (\h -> with_hex h $ \bs ->+ assertBool h (is_left (decode_tlv_stream (const False) bs)))+ ["12 00", "fd0102 00", "fe01000002 00", "ff0100000000000002 00"]+ , testCase "appending a higher-numbered valid stream succeeds" $+ with_hex "01 01 01" $ \a -> with_hex "fd00fe 02 0226" $ \b ->+ decode_n1 (a <> b) @?= Right [N1Tlv1 1, N1Tlv4 550]+ , testCase "appending an invalid stream fails" $+ with_hex "01 01 01" $ \a -> mapM_ (\h -> with_hex h $ \b ->+ assertBool h (is_left (decode_n1 (a <> b)))) any_fail+ ]+ where+ any_fail = [+ "0xfd", "0xfd01", "0xfd0001 00", "0xfd0101", "0x0f fd", "0x0f fd26"+ , "0x0f fd2602", "0x0f fd0001 00", "0x0f fd0201 " ++ replicate 1024 '0'+ , "0x12 00", "0xfd0102 00", "0xfe01000002 00"+ , "0xff0100000000000002 00"+ ]+ n1_fail = [+ "0x01 09 ffffffffffffffffff", "0x01 01 00", "0x01 02 0001"+ , "0x01 03 000100", "0x01 04 00010000", "0x01 05 0001000000"+ , "0x01 06 000100000000", "0x01 07 00010000000000"+ , "0x01 08 0001000000000000", "0x02 07 01010101010101"+ , "0x02 09 010101010101010101"+ , "0x03 21 " ++ node_id+ , "0x03 29 " ++ node_id ++ "0000000000000001"+ , "0x03 30 " ++ node_id ++ "000000000000000100000000000001"+ , "0x03 31 04" ++ drop 2 node_id+ ++ "00000000000000010000000000000002"+ , "0x03 32 " ++ node_id ++ "0000000000000001000000000000000001"+ , "0xfd00fe 00", "0xfd00fe 01 01", "0xfd00fe 03 010101", "0x00 00"+ , "0x02 08 0000000000000226 01 01 2a"+ , "0x02 08 0000000000000231 02 08 0000000000000451"+ , "0x1f 00 0f 01 2a", "0x1f 00 1f 01 2a"+ ]+ n2_fail = ["0xffffffffffffffffff 00 00 00"]+ any_ok = [+ "0x", "0x21 00", "0xfd0201 00", "0xfd00fd 00", "0xfd00ff 00"+ , "0xfe02000001 00", "0xff0200000000000001 00"+ ]+ n1_ok = [+ ("0x01 00", N1Tlv1 0), ("0x01 01 01", N1Tlv1 1)+ , ("0x01 02 0100", N1Tlv1 256), ("0x01 03 010000", N1Tlv1 65536)+ , ("0x01 04 01000000", N1Tlv1 16777216)+ , ("0x01 05 0100000000", N1Tlv1 4294967296)+ , ("0x01 06 010000000000", N1Tlv1 1099511627776)+ , ("0x01 07 01000000000000", N1Tlv1 281474976710656)+ , ("0x01 08 0100000000000000", N1Tlv1 72057594037927936)+ , ("0x02 08 0000000000000226", N1Tlv2 (ShortChannelId 0x226))+ , ("0xfd00fe 02 0226", N1Tlv4 550)+ ]+ node_id =+ "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb"+ n1_tlv3 = "0x03 31 " ++ node_id ++ "00000000000000010000000000000002"++-- Appendix C: message extension ----------------------------------------------++appendix_c :: TestTree+appendix_c = testGroup "Appendix C (message extension)" [+ testCase "no extension" $ with_hex "001000000000" $ \bs ->+ case decode_message bs of+ Right (MsgInit i) -> do+ init_networks i @?= Nothing+ init_tlvs i @?= empty_tlv_stream+ other -> assertFailure (show other)+ , testCase "two unknown odd records are kept" $+ with_hex "001000000000c9012acb0104" $ \bs ->+ case decode_message bs of+ Right m@(MsgInit i) -> do+ map tlv_type (un_tlv_stream (init_tlvs i)) @?= [0xc9, 0xcb]+ encode_message m @?= Right bs+ other -> assertFailure (show other)+ , testCase "truncated extension" $ with_hex "00100000000001" $ \bs ->+ decode_message bs @?= Left (DecodeTlvError TlvTruncated)+ , testCase "unknown even record" $ with_hex "001000000000ca012a" $ \bs ->+ decode_message bs @?= Left (DecodeTlvError (TlvUnknownEvenType 0xca))+ , testCase "duplicate record" $ with_hex "001000000000c90101c90102" $ \bs ->+ decode_message bs @?= Left (DecodeTlvError TlvNotStrictlyIncreasing)+ ]++-- Appendix D: signed integers ------------------------------------------------++appendix_d :: TestTree+appendix_d = testGroup "Appendix D (signed integers)" [+ testCase (show v) $ with_hex h $ \bs -> case BS.length bs of+ 1 -> do+ encode_s8 (fromIntegral v) @?= bs+ decode_s8 bs @?= Just (fromIntegral v, "")+ 2 -> do+ encode_s16 (fromIntegral v) @?= bs+ decode_s16 bs @?= Just (fromIntegral v, "")+ 4 -> do+ encode_s32 (fromIntegral v) @?= bs+ decode_s32 bs @?= Just (fromIntegral v, "")+ 8 -> do+ encode_s64 v @?= bs+ decode_s64 bs @?= Just (v, "")+ _ -> assertFailure "bad vector width"+ | (v, h) <- vectors ]+ where+ vectors :: [(Int64, String)]+ vectors = [+ (0, "00"), (42, "2a"), (-42, "d6"), (127, "7f"), (-128, "80")+ , (128, "0080"), (-129, "ff7f"), (15000, "3a98"), (-15000, "c568")+ , (32767, "7fff"), (-32768, "8000"), (32768, "00008000")+ , (-32769, "ffff7fff"), (21000000, "01406f40")+ , (-21000000, "febf90c0"), (2147483647, "7fffffff")+ , (-2147483648, "80000000"), (2147483648, "0000000080000000")+ , (-2147483649, "ffffffff7fffffff"), (500000000000, "000000746a528800")+ , (-500000000000, "ffffff8b95ad7800")+ , (9223372036854775807, "7fffffffffffffff")+ , (-9223372036854775808, "8000000000000000")+ ]++-- primitives -----------------------------------------------------------------++primitives :: TestTree+primitives = testGroup "Primitives" [+ testCase "u16/u32/u64 known answers" $ do+ encode_u16 0x0102 @?= "\x01\x02"+ encode_u32 0x01020304 @?= "\x01\x02\x03\x04"+ encode_u64 0x0102030405060708 @?= "\x01\x02\x03\x04\x05\x06\x07\x08"+ decode_u32 "\x01\x02\x03\x04rest" @?= Just (0x01020304, "rest")+ , testCase "short reads fail" $ do+ decode_u16 "\x01" @?= Nothing+ decode_u32 "\x01\x02\x03" @?= Nothing+ decode_u64 "\x01\x02\x03\x04\x05\x06\x07" @?= Nothing+ decode_s8 "" @?= Nothing+ , testCase "truncated integers" $ do+ encode_tu64 0 @?= ""+ encode_tu64 1 @?= "\x01"+ encode_tu32 0x010000 @?= "\x01\x00\x00"+ decode_tu16 "" @?= Just 0+ decode_tu16 "\x00\x01" @?= Nothing+ decode_tu16 "\x01\x00\x00" @?= Nothing+ decode_tu32 "\x01\x00\x00\x00\x00" @?= Nothing+ decode_tu64 "\x01\x00\x00\x00\x00\x00\x00\x00\x00" @?= Nothing+ , testCase "length-prefixed bytes" $ do+ encode_u16_prefixed "abc" @?= Just "\x00\x03\&abc"+ encode_u16_prefixed (BS.replicate 65536 0) @?= Nothing+ decode_u16_prefixed "\x00\x03\&abcde" @?= Just ("abc", "de")+ decode_u16_prefixed "\x00\x03\&ab" @?= Nothing+ , testCase "fixed-size types check their lengths" $ do+ assertBool "chain_hash" (isNothing (chain_hash (BS.replicate 31 0)))+ assertBool "channel_id" (isNothing (channel_id (BS.replicate 33 0)))+ assertBool "signature" (isNothing (signature (BS.replicate 65 0)))+ assertBool "payment_hash" (isNothing (payment_hash ""))+ assertBool "preimage" (isNothing (payment_preimage ""))+ assertBool "secret" (isNothing (per_commitment_secret ""))+ , testCase "point prefix" $ do+ assertBool "02" (isJust (point (BS.cons 0x02 (BS.replicate 32 1))))+ assertBool "03" (isJust (point (BS.cons 0x03 (BS.replicate 32 1))))+ assertBool "04" (isNothing (point (BS.cons 0x04 (BS.replicate 32 1))))+ assertBool "len" (isNothing (point (BS.cons 0x02 (BS.replicate 31 1))))+ , testCase "short_channel_id" $ case short_channel_id 539268 845 1 of+ Nothing -> assertFailure "valid scid rejected"+ Just c -> do+ scid_block_height c @?= 539268+ scid_tx_index c @?= 845+ scid_output_index c @?= 1+ encode_short_channel_id c @?= "\x08\x3a\x84\x00\x03\x4d\x00\x01"+ , testCase "short_channel_id rejects 25-bit components" $ do+ short_channel_id 0x1000000 0 0 @?= Nothing+ short_channel_id 0 0x1000000 0 @?= Nothing+ , testCase "amount bounds" $ do+ fmap un_satoshi (satoshi 0x000775f05a074000) @?= Just 0x000775f05a074000+ satoshi 0x000775f05a074001 @?= Nothing+ fmap un_milli_satoshi (milli_satoshi 0x1d24b2dfac520000)+ @?= Just 0x1d24b2dfac520000+ milli_satoshi 0x1d24b2dfac520001 @?= Nothing+ sat_to_msat max_satoshi @?= max_milli_satoshi+ decode_satoshi (encode_u64 0x000775f05a074001) @?= Nothing+ decode_milli_satoshi (encode_u64 maxBound) @?= Nothing+ , testCase "checked arithmetic" $ do+ add_sat max_satoshi max_satoshi @?= Nothing+ (do a <- satoshi 1; b <- satoshi 2; sub_sat a b) @?= Nothing+ (do a <- satoshi 2; b <- satoshi 1; sub_sat a b) @?= satoshi 1+ (do a <- milli_satoshi 1; b <- milli_satoshi 2; sub_msat a b)+ @?= Nothing+ add_msat max_milli_satoshi max_milli_satoshi @?= Nothing+ , testCase "secrets don't show" $+ case payment_preimage (BS.replicate 32 7) of+ Nothing -> assertFailure "valid preimage rejected"+ Just p -> do+ show p @?= "PaymentPreimage <redacted>"+ show (Just p) @?= "Just (PaymentPreimage <redacted>)"+ , testCase "secret equality" $ do+ let a = payment_preimage (BS.replicate 32 7)+ b = payment_preimage (BS.replicate 31 7 <> "\x08")+ assertBool "equal" (a == a)+ assertBool "unequal" (a /= b)+ ]++-- TLV ------------------------------------------------------------------------++tlv :: TestTree+tlv = testGroup "TLV" [+ testCase "tlv_stream sorts" $+ fmap (map tlv_type . un_tlv_stream)+ (tlv_stream [TlvRecord 5 "", TlvRecord 1 "", TlvRecord 3 ""])+ @?= Just [1, 3, 5]+ , testCase "tlv_stream rejects duplicates" $+ tlv_stream [TlvRecord 1 "a", TlvRecord 1 "b"] @?= Nothing+ , testCase "lookup_tlv" $ do+ (lookup_tlv 3 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"])+ @?= Just "b"+ (lookup_tlv 2 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"])+ @?= Nothing+ , testCase "huge lengths are rejected, not wrapped" $+ mapM_ (\h -> with_hex h $ \bs ->+ decode_tlv_stream (const True) bs @?= Left TlvTruncated)+ [ "01ffffffffffffffffff", "01ff800000000000000067"+ , "01ffffffffffffffffff6701aa" ]+ , testCase "truncation vs non-minimal" $ do+ decode_tlv_stream (const True) "\xfd" @?= Left TlvTruncated+ decode_tlv_stream (const True) "\xfd\x00\x01\x00"+ @?= Left TlvNonMinimalBigSize+ , testCase "unknown odd records are kept" $+ fmap (map tlv_type . un_tlv_stream)+ (decode_tlv_stream (const False) "\x01\x00\x03\x01\x2a")+ @?= Right [1, 3]+ ]++-- messages -------------------------------------------------------------------++messages :: TestTree+messages = testGroup "Messages" [+ testCase "pong known answer" $+ encode_message (MsgPong (Pong "\x00\x00" empty_tlv_stream))+ @?= Right "\x00\x13\x00\x02\x00\x00"+ , testCase "ping known answer" $+ encode_message (MsgPing (Ping 4 "" empty_tlv_stream))+ @?= Right "\x00\x12\x00\x04\x00\x00"+ , testCase "error known answer" $+ encode_message (MsgError (Error all_channels "x" empty_tlv_stream))+ @?= Right ("\x00\x11" <> BS.replicate 32 0 <> "\x00\x01x")+ , testCase "init with networks and remote_addr" $+ case chain_hash (BS.replicate 32 0x6f) of+ Nothing -> assertFailure "valid chain hash rejected"+ Just ch -> do+ let addr = "\x01\x7f\x00\x00\x01\x26\x07"+ i = Init empty_fv (BOLT9.parse "\x02") (Just [ch])+ (Just addr) empty_tlv_stream+ wire = "\x00\x10\x00\x00\x00\x01\x02"+ <> "\x01\x20" <> un_chain_hash ch <> "\x03\x07" <> addr+ encode_message (MsgInit i) @?= Right wire+ decode_message wire @?= Right (MsgInit i)+ , testCase "init networks must be a multiple of 32 bytes" $+ decode_init "\x00\x00\x00\x00\x01\x01\x00"+ @?= Left (DecodeInvalidTlvValue 1)+ , testCase "init rejects tlvs clashing with typed fields" $+ case tlv_stream [TlvRecord 3 "x"] of+ Nothing -> assertFailure "valid stream rejected"+ Just s -> encode_init (Init empty_fv empty_fv Nothing (Just "y") s)+ @?= Left EncodeInvalidTlvs+ , testCase "unknown message types" $ do+ decode_message "\x00\x20" @?= Left (DecodeUnknownEvenType 32)+ decode_message "\x80\x01" @?= Left (DecodeUnknownOddType 32769)+ decode_message "\x00" @?= Left DecodeInsufficientBytes+ , testCase "truncated payloads" $ do+ decode_init "\x00" @?= Left DecodeInsufficientBytes+ decode_init "\x00\x01" @?= Left DecodeInsufficientBytes+ decode_error (BS.replicate 31 0) @?= Left DecodeInsufficientBytes+ decode_warning (BS.replicate 33 0) @?= Left DecodeInsufficientBytes+ decode_ping "\x00\x04\x00\x02\x00" @?= Left DecodeInsufficientBytes+ decode_pong "\x00\x02\x00" @?= Left DecodeInsufficientBytes+ decode_peer_storage "\x00\x01" @?= Left DecodeInsufficientBytes+ decode_peer_storage_retrieval "\x00" @?= Left DecodeInsufficientBytes+ , testCase "invalid extensions" $ do+ decode_pong "\x00\x00\x02\x00" @?= Left+ (DecodeTlvError (TlvUnknownEvenType 2))+ decode_ping "\x00\x00\x00\x00\x01" @?= Left (DecodeTlvError TlvTruncated)+ , testCase "message size limit" $ do+ encode_envelope 7 (BS.replicate 65533 0) @?=+ Right (encode_u16 7 <> BS.replicate 65533 0)+ encode_envelope 7 (BS.replicate 65534 0) @?= Left EncodeMessageTooLarge+ encode_message (MsgPeerStorage (PeerStorage (BS.replicate 65532 0)+ empty_tlv_stream)) @?= Left EncodeMessageTooLarge+ encode_pong (Pong (BS.replicate 65536 0) empty_tlv_stream)+ @?= Left EncodeLengthOverflow+ , testCase "ping_response" $ do+ ping_response (Ping 3 "ab" empty_tlv_stream)+ @?= Just (Pong "\x00\x00\x00" empty_tlv_stream)+ ping_response (Ping 65531 "" empty_tlv_stream) @?=+ Just (Pong (BS.replicate 65531 0) empty_tlv_stream)+ ping_response (Ping 65532 "" empty_tlv_stream) @?= Nothing+ ]++-- properties -----------------------------------------------------------------++newtype Bytes = Bytes BS.ByteString+ deriving Show++instance Arbitrary Bytes where+ arbitrary = Bytes . BS.pack <$> (choose (0, 64) >>= vector)++-- streams of unknown odd records+newtype OddTlvs = OddTlvs TlvStream+ deriving Show++instance Arbitrary OddTlvs where+ arbitrary = do+ ts <- L.nub . map (\t -> 2 * (t `div` 2) + 1)+ <$> listOf (arbitrary :: Gen Word64)+ vs <- vectorOf (length ts) arbitrary+ pure . OddTlvs . fromMaybe empty_tlv_stream $+ tlv_stream [ TlvRecord t v | (t, Bytes v) <- zip ts vs ]++newtype Bytes32 = Bytes32 BS.ByteString+ deriving Show++instance Arbitrary Bytes32 where+ arbitrary = Bytes32 . BS.pack <$> vector 32++newtype Msg = Msg Message+ deriving Show++instance Arbitrary Msg where+ arbitrary = Msg <$> oneof [+ do Bytes a <- arbitrary+ Bytes b <- arbitrary+ nets <- oneof [pure Nothing, Just <$> listOf chain]+ addr <- oneof [pure Nothing, (\(Bytes x) -> Just x) <$> arbitrary]+ OddTlvs s <- arbitrary+ let s' = filter_tlv_stream (\t -> t /= 1 && t /= 3) s+ pure (MsgInit (Init (BOLT9.parse a) (BOLT9.parse b) nets addr s'))+ , do c <- cid+ Bytes d <- arbitrary+ OddTlvs s <- arbitrary+ pure (MsgError (Error c d s))+ , do c <- cid+ Bytes d <- arbitrary+ OddTlvs s <- arbitrary+ pure (MsgWarning (Warning c d s))+ , do n <- arbitrary+ Bytes d <- arbitrary+ OddTlvs s <- arbitrary+ pure (MsgPing (Ping n d s))+ , do Bytes d <- arbitrary+ OddTlvs s <- arbitrary+ pure (MsgPong (Pong d s))+ , do Bytes d <- arbitrary+ OddTlvs s <- arbitrary+ pure (MsgPeerStorage (PeerStorage d s))+ , do Bytes d <- arbitrary+ OddTlvs s <- arbitrary+ pure (MsgPeerStorageRetrieval (PeerStorageRetrieval d s))+ ]+ where+ chain = do+ Bytes32 b <- arbitrary+ maybe chain pure (chain_hash b)+ cid = do+ Bytes32 b <- arbitrary+ pure (fromMaybe all_channels (channel_id b))++properties :: TestTree+properties = testGroup "Properties" [+ testProperty "bigsize round-trips" $ \w ->+ decode_bigsize (encode_bigsize w) === Just (w, "")+ , testProperty "tu16 round-trips" $ \w ->+ decode_tu16 (encode_tu16 w) === Just w+ , testProperty "tu32 round-trips" $ \w ->+ decode_tu32 (encode_tu32 w) === Just w+ , testProperty "tu64 round-trips" $ \w ->+ decode_tu64 (encode_tu64 w) === Just w+ , testProperty "u16 round-trips" $ \w ->+ decode_u16 (encode_u16 w) === Just (w, "")+ , testProperty "u32 round-trips" $ \w ->+ decode_u32 (encode_u32 w) === Just (w, "")+ , testProperty "u64 round-trips" $ \w ->+ decode_u64 (encode_u64 w) === Just (w, "")+ , testProperty "s64 round-trips" $ \w ->+ decode_s64 (encode_s64 w) === Just (w, "")+ , testProperty "scid components round-trip" $ \h t o ->+ let h' = h `mod` 0x1000000 :: Word32+ t' = t `mod` 0x1000000 :: Word32+ in fmap (\c -> (scid_block_height c, scid_tx_index c,+ scid_output_index c)) (short_channel_id h' t' o)+ === Just (h', t', o)+ , testProperty "sat -> msat -> sat is the identity" $+ forAll (choose (0, 0x000775f05a074000)) $ \w ->+ fmap (msat_to_sat . sat_to_msat) (satoshi w) === satoshi w+ , testProperty "add then sub is the identity" $+ forAll (choose (0, 0x000775f05a074000)) $ \a ->+ forAll (choose (0, 0x000775f05a074000 - a)) $ \b ->+ (do x <- satoshi a+ y <- satoshi b+ z <- add_sat x y+ sub_sat z y) === satoshi a+ , testProperty "tlv streams round-trip" $ \(OddTlvs s) ->+ decode_tlv_stream (const False) (encode_tlv_stream s) === Right s+ , testProperty "messages round-trip" $ \(Msg m) ->+ case encode_message m of+ Left e -> counterexample (show e) False+ Right bs -> decode_message bs === Right m+ , testProperty "re-encoding reproduces the wire bytes" $ \(Msg m) ->+ case encode_message m of+ Left e -> counterexample (show e) False+ Right bs -> case decode_message bs of+ Left e -> counterexample (show e) False+ Right m' -> encode_message m' === Right bs+ ]