{-# 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 (ChainHash -> ChainHash -> Bool
(ChainHash -> ChainHash -> Bool)
-> (ChainHash -> ChainHash -> Bool) -> Eq ChainHash
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ChainHash -> ChainHash -> Bool
== :: ChainHash -> ChainHash -> Bool
$c/= :: ChainHash -> ChainHash -> Bool
/= :: ChainHash -> ChainHash -> Bool
Eq, Int -> ChainHash -> ShowS
[ChainHash] -> ShowS
ChainHash -> String
(Int -> ChainHash -> ShowS)
-> (ChainHash -> String)
-> ([ChainHash] -> ShowS)
-> Show ChainHash
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ChainHash -> ShowS
showsPrec :: Int -> ChainHash -> ShowS
$cshow :: ChainHash -> String
show :: ChainHash -> String
$cshowList :: [ChainHash] -> ShowS
showList :: [ChainHash] -> ShowS
Show, (forall x. ChainHash -> Rep ChainHash x)
-> (forall x. Rep ChainHash x -> ChainHash) -> Generic ChainHash
forall x. Rep ChainHash x -> ChainHash
forall x. ChainHash -> Rep ChainHash x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. ChainHash -> Rep ChainHash x
from :: forall x. ChainHash -> Rep ChainHash x
$cto :: forall x. Rep ChainHash x -> ChainHash
to :: forall x. Rep ChainHash x -> ChainHash
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 :: ByteString -> Maybe ChainHash
chainHash ByteString
bs
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
32 = ChainHash -> Maybe ChainHash
forall a. a -> Maybe a
Just (ByteString -> ChainHash
ChainHash ByteString
bs)
  | Bool
otherwise = Maybe ChainHash
forall a. Maybe a
Nothing
{-# INLINE chainHash #-}

-- | Extract the raw bytes from a chain hash.
unChainHash :: ChainHash -> BS.ByteString
unChainHash :: ChainHash -> ByteString
unChainHash (ChainHash ByteString
bs) = ByteString
bs
{-# INLINE unChainHash #-}

-- Unsigned integer encoding ---------------------------------------------------

-- | Encode a 16-bit unsigned integer (big-endian).
--
-- >>> encodeU16 0x0102
-- "\SOH\STX"
encodeU16 :: Word16 -> BS.ByteString
encodeU16 :: Word16 -> ByteString
encodeU16 = LazyByteString -> ByteString
BSL.toStrict (LazyByteString -> ByteString)
-> (Word16 -> LazyByteString) -> Word16 -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
BSB.toLazyByteString (Builder -> LazyByteString)
-> (Word16 -> Builder) -> Word16 -> LazyByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word16 -> Builder
BSB.word16BE
{-# INLINE encodeU16 #-}

-- | Encode a 32-bit unsigned integer (big-endian).
--
-- >>> encodeU32 0x01020304
-- "\SOH\STX\ETX\EOT"
encodeU32 :: Word32 -> BS.ByteString
encodeU32 :: Word32 -> ByteString
encodeU32 = LazyByteString -> ByteString
BSL.toStrict (LazyByteString -> ByteString)
-> (Word32 -> LazyByteString) -> Word32 -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
BSB.toLazyByteString (Builder -> LazyByteString)
-> (Word32 -> Builder) -> Word32 -> LazyByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Builder
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 :: Word64 -> ByteString
encodeU64 = LazyByteString -> ByteString
BSL.toStrict (LazyByteString -> ByteString)
-> (Word64 -> LazyByteString) -> Word64 -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
BSB.toLazyByteString (Builder -> LazyByteString)
-> (Word64 -> Builder) -> Word64 -> LazyByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word64 -> Builder
BSB.word64BE
{-# INLINE encodeU64 #-}

-- Signed integer encoding -----------------------------------------------------

-- | Encode an 8-bit signed integer.
--
-- >>> encodeS8 42
-- "*"
-- >>> encodeS8 (-42)
-- "\214"
encodeS8 :: Int8 -> BS.ByteString
encodeS8 :: Int8 -> ByteString
encodeS8 = Word8 -> ByteString
BS.singleton (Word8 -> ByteString) -> (Int8 -> Word8) -> Int8 -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int8 -> Word8
forall a b. (Integral a, Num b) => a -> b
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 :: Int16 -> ByteString
encodeS16 = LazyByteString -> ByteString
BSL.toStrict (LazyByteString -> ByteString)
-> (Int16 -> LazyByteString) -> Int16 -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
BSB.toLazyByteString (Builder -> LazyByteString)
-> (Int16 -> Builder) -> Int16 -> LazyByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int16 -> Builder
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 :: Int32 -> ByteString
encodeS32 = LazyByteString -> ByteString
BSL.toStrict (LazyByteString -> ByteString)
-> (Int32 -> LazyByteString) -> Int32 -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
BSB.toLazyByteString (Builder -> LazyByteString)
-> (Int32 -> Builder) -> Int32 -> LazyByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int32 -> Builder
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 :: Int64 -> ByteString
encodeS64 = LazyByteString -> ByteString
BSL.toStrict (LazyByteString -> ByteString)
-> (Int64 -> LazyByteString) -> Int64 -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
BSB.toLazyByteString (Builder -> LazyByteString)
-> (Int64 -> Builder) -> Int64 -> LazyByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int64 -> Builder
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 :: Word16 -> ByteString
encodeTu16 Word16
0 = ByteString
BS.empty
encodeTu16 !Word16
x
  | Word16
x Word16 -> Word16 -> Bool
forall a. Ord a => a -> a -> Bool
< Word16
0x100 = Word8 -> ByteString
BS.singleton (Word16 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word16
x)
  | Bool
otherwise = Word16 -> ByteString
encodeU16 Word16
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 :: Word32 -> ByteString
encodeTu32 Word32
0 = ByteString
BS.empty
encodeTu32 !Word32
x
  | Word32
x Word32 -> Word32 -> Bool
forall a. Ord a => a -> a -> Bool
< Word32
0x100       = Word8 -> ByteString
BS.singleton (Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
x)
  | Word32
x Word32 -> Word32 -> Bool
forall a. Ord a => a -> a -> Bool
< Word32
0x10000     = Word16 -> ByteString
encodeU16 (Word32 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
x)
  | Word32
x Word32 -> Word32 -> Bool
forall a. Ord a => a -> a -> Bool
< Word32
0x1000000   = [Word8] -> ByteString
BS.pack [ Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32
x Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
16)
                              , Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32
x Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
8)
                              , Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
x
                              ]
  | Bool
otherwise       = Word32 -> ByteString
encodeU32 Word32
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 :: Word64 -> ByteString
encodeTu64 Word64
0 = ByteString
BS.empty
encodeTu64 !Word64
x
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x100             = Word8 -> ByteString
BS.singleton (Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x)
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x10000           = Word16 -> ByteString
encodeU16 (Word64 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x)
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x1000000         = [Word8] -> ByteString
BS.pack [ Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
16)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
8)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x
                                    ]
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x100000000       = Word32 -> ByteString
encodeU32 (Word64 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x)
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x10000000000     = [Word8] -> ByteString
BS.pack [ Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
32)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
24)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
16)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
8)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x
                                    ]
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x1000000000000   = [Word8] -> ByteString
BS.pack [ Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
40)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
32)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
24)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
16)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
8)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x
                                    ]
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x100000000000000 = [Word8] -> ByteString
BS.pack [ Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
48)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
40)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
32)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
24)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
16)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word64
x Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftR` Int
8)
                                    , Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x
                                    ]
  | Bool
otherwise             = Word64 -> ByteString
encodeU64 Word64
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 :: Int64 -> ByteString
encodeMinSigned !Int64
x
  | Int64
x Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
>= -Int64
128 Bool -> Bool -> Bool
&& Int64
x Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Int64
127 =
      -- Fits in 1 byte
      Word8 -> ByteString
BS.singleton (Int64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
x)
  | Int64
x Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
>= -Int64
32768 Bool -> Bool -> Bool
&& Int64
x Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Int64
32767 =
      -- Fits in 2 bytes
      Int16 -> ByteString
encodeS16 (Int64 -> Int16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
x)
  | Int64
x Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
>= -Int64
2147483648 Bool -> Bool -> Bool
&& Int64
x Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Int64
2147483647 =
      -- Fits in 4 bytes
      Int32 -> ByteString
encodeS32 (Int64 -> Int32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
x)
  | Bool
otherwise =
      -- Need 8 bytes
      Int64 -> ByteString
encodeS64 Int64
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 :: Word64 -> ByteString
encodeBigSize !Word64
x
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0xfd = Word8 -> ByteString
BS.singleton (Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x)
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x10000 = Word8 -> ByteString -> ByteString
BS.cons Word8
0xfd (Word16 -> ByteString
encodeU16 (Word64 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x))
  | Word64
x Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0x100000000 = Word8 -> ByteString -> ByteString
BS.cons Word8
0xfe (Word32 -> ByteString
encodeU32 (Word64 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
x))
  | Bool
otherwise = Word8 -> ByteString -> ByteString
BS.cons Word8
0xff (Word64 -> ByteString
encodeU64 Word64
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 :: ByteString -> Maybe ByteString
encodeLength !ByteString
bs
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
65535 = Maybe ByteString
forall a. Maybe a
Nothing
  | Bool
otherwise = ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (Word16 -> ByteString
encodeU16 (Int -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
BS.length ByteString
bs)))
{-# INLINE encodeLength #-}

-- Unsigned integer decoding ---------------------------------------------------

-- | Decode a 16-bit unsigned integer (big-endian).
decodeU16 :: BS.ByteString -> Maybe (Word16, BS.ByteString)
decodeU16 :: ByteString -> Maybe (Word16, ByteString)
decodeU16 !ByteString
bs
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
2 = Maybe (Word16, ByteString)
forall a. Maybe a
Nothing
  | Bool
otherwise =
      let !b0 :: Word16
b0 = Word8 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
0)
          !b1 :: Word16
b1 = Word8 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
1)
          !val :: Word16
val = (Word16
b0 Word16 -> Int -> Word16
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
8) Word16 -> Word16 -> Word16
forall a. Bits a => a -> a -> a
.|. Word16
b1
      in  (Word16, ByteString) -> Maybe (Word16, ByteString)
forall a. a -> Maybe a
Just (Word16
val, Int -> ByteString -> ByteString
BS.drop Int
2 ByteString
bs)
{-# INLINE decodeU16 #-}

-- | Decode a 32-bit unsigned integer (big-endian).
decodeU32 :: BS.ByteString -> Maybe (Word32, BS.ByteString)
decodeU32 :: ByteString -> Maybe (Word32, ByteString)
decodeU32 !ByteString
bs
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
4 = Maybe (Word32, ByteString)
forall a. Maybe a
Nothing
  | Bool
otherwise =
      let !b0 :: Word32
b0 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
0)
          !b1 :: Word32
b1 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
1)
          !b2 :: Word32
b2 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
2)
          !b3 :: Word32
b3 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
3)
          !val :: Word32
val = (Word32
b0 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
24) Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. (Word32
b1 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
16)
              Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. (Word32
b2 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
8) Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. Word32
b3
      in  (Word32, ByteString) -> Maybe (Word32, ByteString)
forall a. a -> Maybe a
Just (Word32
val, Int -> ByteString -> ByteString
BS.drop Int
4 ByteString
bs)
{-# INLINE decodeU32 #-}

-- | Decode a 64-bit unsigned integer (big-endian).
decodeU64 :: BS.ByteString -> Maybe (Word64, BS.ByteString)
decodeU64 :: ByteString -> Maybe (Word64, ByteString)
decodeU64 !ByteString
bs
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
8 = Maybe (Word64, ByteString)
forall a. Maybe a
Nothing
  | Bool
otherwise =
      let !b0 :: Word64
b0 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
0)
          !b1 :: Word64
b1 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
1)
          !b2 :: Word64
b2 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
2)
          !b3 :: Word64
b3 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
3)
          !b4 :: Word64
b4 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
4)
          !b5 :: Word64
b5 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
5)
          !b6 :: Word64
b6 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
6)
          !b7 :: Word64
b7 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
7)
          !val :: Word64
val = (Word64
b0 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
56) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b1 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
48)
              Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b2 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
40) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b3 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
32)
              Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b4 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
24) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b5 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
16)
              Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b6 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
8) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. Word64
b7
      in  (Word64, ByteString) -> Maybe (Word64, ByteString)
forall a. a -> Maybe a
Just (Word64
val, Int -> ByteString -> ByteString
BS.drop Int
8 ByteString
bs)
{-# INLINE decodeU64 #-}

-- Signed integer decoding -----------------------------------------------------

-- | Decode an 8-bit signed integer.
decodeS8 :: BS.ByteString -> Maybe (Int8, BS.ByteString)
decodeS8 :: ByteString -> Maybe (Int8, ByteString)
decodeS8 !ByteString
bs
  | ByteString -> Bool
BS.null ByteString
bs = Maybe (Int8, ByteString)
forall a. Maybe a
Nothing
  | Bool
otherwise  = (Int8, ByteString) -> Maybe (Int8, ByteString)
forall a. a -> Maybe a
Just (Word8 -> Int8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
0), Int -> ByteString -> ByteString
BS.drop Int
1 ByteString
bs)
{-# INLINE decodeS8 #-}

-- | Decode a 16-bit signed integer (big-endian two's complement).
decodeS16 :: BS.ByteString -> Maybe (Int16, BS.ByteString)
decodeS16 :: ByteString -> Maybe (Int16, ByteString)
decodeS16 !ByteString
bs = do
  (w, rest) <- ByteString -> Maybe (Word16, ByteString)
decodeU16 ByteString
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 :: ByteString -> Maybe (Int32, ByteString)
decodeS32 !ByteString
bs = do
  (w, rest) <- ByteString -> Maybe (Word32, ByteString)
decodeU32 ByteString
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 :: ByteString -> Maybe (Int64, ByteString)
decodeS64 !ByteString
bs = do
  (w, rest) <- ByteString -> Maybe (Word64, ByteString)
decodeU64 ByteString
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 :: Int -> ByteString -> Maybe (Word16, ByteString)
decodeTu16 !Int
len !ByteString
bs
  | Int
len Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 Bool -> Bool -> Bool
|| Int
len Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
2 = Maybe (Word16, ByteString)
forall a. Maybe a
Nothing
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
len = Maybe (Word16, ByteString)
forall a. Maybe a
Nothing
  | Int
len Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = (Word16, ByteString) -> Maybe (Word16, ByteString)
forall a. a -> Maybe a
Just (Word16
0, ByteString
bs)
  | Bool
otherwise =
      let !bytes :: ByteString
bytes = Int -> ByteString -> ByteString
BS.take Int
len ByteString
bs
          !rest :: ByteString
rest = Int -> ByteString -> ByteString
BS.drop Int
len ByteString
bs
      in  if HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bytes Int
0 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0
            then Maybe (Word16, ByteString)
forall a. Maybe a
Nothing  -- non-minimal: leading zero
            else (Word16, ByteString) -> Maybe (Word16, ByteString)
forall a. a -> Maybe a
Just (ByteString -> Word16
decodeBeWord16 ByteString
bytes, ByteString
rest)
  where
    decodeBeWord16 :: BS.ByteString -> Word16
    decodeBeWord16 :: ByteString -> Word16
decodeBeWord16 ByteString
b = case ByteString -> Int
BS.length ByteString
b of
      Int
1 -> Word8 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
b Int
0)
      Int
2 -> (Word8 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
b Int
0) Word16 -> Int -> Word16
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
8)
        Word16 -> Word16 -> Word16
forall a. Bits a => a -> a -> a
.|. Word8 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
b Int
1)
      Int
_ -> Word16
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 :: Int -> ByteString -> Maybe (Word32, ByteString)
decodeTu32 !Int
len !ByteString
bs
  | Int
len Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 Bool -> Bool -> Bool
|| Int
len Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
4 = Maybe (Word32, ByteString)
forall a. Maybe a
Nothing
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
len = Maybe (Word32, ByteString)
forall a. Maybe a
Nothing
  | Int
len Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = (Word32, ByteString) -> Maybe (Word32, ByteString)
forall a. a -> Maybe a
Just (Word32
0, ByteString
bs)
  | Bool
otherwise =
      let !bytes :: ByteString
bytes = Int -> ByteString -> ByteString
BS.take Int
len ByteString
bs
          !rest :: ByteString
rest = Int -> ByteString -> ByteString
BS.drop Int
len ByteString
bs
      in  if HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bytes Int
0 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0
            then Maybe (Word32, ByteString)
forall a. Maybe a
Nothing  -- non-minimal: leading zero
            else (Word32, ByteString) -> Maybe (Word32, ByteString)
forall a. a -> Maybe a
Just (Int -> ByteString -> Word32
decodeBeWord32 Int
len ByteString
bytes, ByteString
rest)
  where
    decodeBeWord32 :: Int -> BS.ByteString -> Word32
    decodeBeWord32 :: Int -> ByteString -> Word32
decodeBeWord32 Int
n ByteString
b = Word32 -> Int -> Word32
forall {t}. (Bits t, Num t) => t -> Int -> t
go Word32
0 Int
0
      where
        go :: t -> Int -> t
go !t
acc !Int
i
          | Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
n    = t
acc
          | Bool
otherwise = t -> Int -> t
go ((t
acc t -> Int -> t
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
8)
                           t -> t -> t
forall a. Bits a => a -> a -> a
.|. Word8 -> t
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
b Int
i)) (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
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 :: Int -> ByteString -> Maybe (Word64, ByteString)
decodeTu64 !Int
len !ByteString
bs
  | Int
len Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 Bool -> Bool -> Bool
|| Int
len Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
8 = Maybe (Word64, ByteString)
forall a. Maybe a
Nothing
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
len = Maybe (Word64, ByteString)
forall a. Maybe a
Nothing
  | Int
len Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = (Word64, ByteString) -> Maybe (Word64, ByteString)
forall a. a -> Maybe a
Just (Word64
0, ByteString
bs)
  | Bool
otherwise =
      let !bytes :: ByteString
bytes = Int -> ByteString -> ByteString
BS.take Int
len ByteString
bs
          !rest :: ByteString
rest = Int -> ByteString -> ByteString
BS.drop Int
len ByteString
bs
      in  if HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bytes Int
0 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0
            then Maybe (Word64, ByteString)
forall a. Maybe a
Nothing  -- non-minimal: leading zero
            else (Word64, ByteString) -> Maybe (Word64, ByteString)
forall a. a -> Maybe a
Just (Int -> ByteString -> Word64
decodeBeWord64 Int
len ByteString
bytes, ByteString
rest)
  where
    decodeBeWord64 :: Int -> BS.ByteString -> Word64
    decodeBeWord64 :: Int -> ByteString -> Word64
decodeBeWord64 Int
n ByteString
b = Word64 -> Int -> Word64
forall {t}. (Bits t, Num t) => t -> Int -> t
go Word64
0 Int
0
      where
        go :: t -> Int -> t
go !t
acc !Int
i
          | Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
n    = t
acc
          | Bool
otherwise = t -> Int -> t
go ((t
acc t -> Int -> t
forall a. Bits a => a -> Int -> a
`unsafeShiftL` Int
8)
                           t -> t -> t
forall a. Bits a => a -> a -> a
.|. Word8 -> t
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
b Int
i)) (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
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 :: Int -> ByteString -> Maybe (Int64, ByteString)
decodeMinSigned !Int
len !ByteString
bs
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
len = Maybe (Int64, ByteString)
forall a. Maybe a
Nothing
  | Bool
otherwise = case Int
len of
      Int
1 -> do
        (v, rest) <- ByteString -> Maybe (Int8, ByteString)
decodeS8 ByteString
bs
        Just (fromIntegral v, rest)
      Int
2 -> do
        (v, rest) <- ByteString -> Maybe (Int16, ByteString)
decodeS16 ByteString
bs
        -- Must not fit in 1 byte
        if v >= -128 && v <= 127
          then Nothing
          else Just (fromIntegral v, rest)
      Int
4 -> do
        (v, rest) <- ByteString -> Maybe (Int32, ByteString)
decodeS32 ByteString
bs
        -- Must not fit in 2 bytes
        if v >= -32768 && v <= 32767
          then Nothing
          else Just (fromIntegral v, rest)
      Int
8 -> do
        (v, rest) <- ByteString -> Maybe (Int64, ByteString)
decodeS64 ByteString
bs
        -- Must not fit in 4 bytes
        if v >= -2147483648 && v <= 2147483647
          then Nothing
          else Just (v, rest)
      Int
_ -> Maybe (Int64, ByteString)
forall a. Maybe a
Nothing
{-# INLINE decodeMinSigned #-}

-- BigSize decoding ------------------------------------------------------------

-- | Decode a BigSize value with minimality check.
decodeBigSize :: BS.ByteString -> Maybe (Word64, BS.ByteString)
decodeBigSize :: ByteString -> Maybe (Word64, ByteString)
decodeBigSize !ByteString
bs
  | ByteString -> Bool
BS.null ByteString
bs = Maybe (Word64, ByteString)
forall a. Maybe a
Nothing
  | Bool
otherwise = case HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
0 of
      Word8
0xff -> do
        (val, rest) <- ByteString -> Maybe (Word64, ByteString)
decodeU64 (Int -> ByteString -> ByteString
BS.drop Int
1 ByteString
bs)
        -- Must be >= 0x100000000 for minimal encoding
        if val >= 0x100000000
          then Just (val, rest)
          else Nothing
      Word8
0xfe -> do
        (val, rest) <- ByteString -> Maybe (Word32, ByteString)
decodeU32 (Int -> ByteString -> ByteString
BS.drop Int
1 ByteString
bs)
        -- Must be >= 0x10000 for minimal encoding
        if val >= 0x10000
          then Just (fromIntegral val, rest)
          else Nothing
      Word8
0xfd -> do
        (val, rest) <- ByteString -> Maybe (Word16, ByteString)
decodeU16 (Int -> ByteString -> ByteString
BS.drop Int
1 ByteString
bs)
        -- Must be >= 0xfd for minimal encoding
        if val >= 0xfd
          then Just (fromIntegral val, rest)
          else Nothing
      Word8
b -> (Word64, ByteString) -> Maybe (Word64, ByteString)
forall a. a -> Maybe a
Just (Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
b, Int -> ByteString -> ByteString
BS.drop Int
1 ByteString
bs)