bolt9

Lightning feature flags, per BOLT #9 (docs.ppad.tech/bolt9).
git clone git://git.ppad.tech/bolt9.git
Log | Files | Refs | README | LICENSE

Codec.hs (5697B)


      1 {-# OPTIONS_HADDOCK prune #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 
      4 -- |
      5 -- Module: Lightning.Protocol.BOLT9.Codec
      6 -- Copyright: (c) 2025 Jared Tobin
      7 -- License: MIT
      8 -- Maintainer: Jared Tobin <jared@ppad.tech>
      9 --
     10 -- Parsing and rendering for BOLT #9 feature vectors.
     11 
     12 module Lightning.Protocol.BOLT9.Codec (
     13     -- * Parsing and rendering
     14     parse
     15   , render
     16 
     17     -- * Bit operations
     18   , setBit
     19   , clearBit
     20   , testBit
     21 
     22     -- * Feature operations
     23   , setFeature
     24   , setFeatureWithDeps
     25   , hasFeature
     26   , isFeatureSet
     27   , listFeatures
     28   ) where
     29 
     30 import Data.ByteString (ByteString)
     31 import qualified Data.ByteString as BS
     32 import Data.Word (Word16)
     33 import Lightning.Protocol.BOLT9.Features
     34 import Lightning.Protocol.BOLT9.Types
     35   ( FeatureLevel(..)
     36   , FeatureVector
     37   , bitIndex
     38   , clear
     39   , fromByteString
     40   , member
     41   , set
     42   , unFeatureVector
     43   )
     44 
     45 -- Parsing and rendering ------------------------------------------------------
     46 
     47 -- | Parse a ByteString into a FeatureVector.
     48 --
     49 --   Alias for 'fromByteString'.
     50 parse :: ByteString -> FeatureVector
     51 parse = fromByteString
     52 {-# INLINE parse #-}
     53 
     54 -- | Render a FeatureVector to a ByteString, trimming leading zero bytes
     55 --   for compact encoding.
     56 render :: FeatureVector -> ByteString
     57 render = BS.dropWhile (== 0) . unFeatureVector
     58 {-# INLINE render #-}
     59 
     60 -- Bit operations -------------------------------------------------------------
     61 
     62 -- | Set a bit by raw index.
     63 --
     64 --   >>> setBit 17 empty
     65 --   FeatureVector {unFeatureVector = "\STX"}
     66 setBit :: Word16 -> FeatureVector -> FeatureVector
     67 setBit !idx = set (bitIndex idx)
     68 {-# INLINE setBit #-}
     69 
     70 -- | Clear a bit by raw index.
     71 --
     72 --   >>> clearBit 17 (setBit 17 empty)
     73 --   FeatureVector {unFeatureVector = ""}
     74 clearBit :: Word16 -> FeatureVector -> FeatureVector
     75 clearBit !idx = clear (bitIndex idx)
     76 {-# INLINE clearBit #-}
     77 
     78 -- | Test if a bit is set.
     79 --
     80 --   >>> testBit 17 (setBit 17 empty)
     81 --   True
     82 --   >>> testBit 16 (setBit 17 empty)
     83 --   False
     84 testBit :: Word16 -> FeatureVector -> Bool
     85 testBit !idx = member (bitIndex idx)
     86 {-# INLINE testBit #-}
     87 
     88 -- Feature operations ---------------------------------------------------------
     89 
     90 -- | Set a feature's bit at the given level.
     91 --
     92 --   'Required' sets the even bit, 'Optional' sets the odd bit.
     93 --
     94 --   >>> import Data.Maybe (fromJust)
     95 --   >>> let mpp = fromJust (featureByName "basic_mpp")
     96 --   >>> setFeature mpp Optional empty  -- set optional bit (17)
     97 --   FeatureVector {unFeatureVector = "\STX"}
     98 --   >>> setFeature mpp Required empty  -- set required bit (16)
     99 --   FeatureVector {unFeatureVector = "\SOH"}
    100 setFeature :: Feature -> FeatureLevel -> FeatureVector -> FeatureVector
    101 setFeature !f !level = setBit targetBit
    102   where
    103     !baseBit   = featureBaseBit f
    104     !targetBit = case level of
    105       Required -> baseBit
    106       Optional -> baseBit + 1
    107 {-# INLINE setFeature #-}
    108 
    109 -- | Set a feature and all its transitive dependencies.
    110 --
    111 --   Dependencies are set at the same level as the feature itself.
    112 --   Unknown dependencies (not in the known features table) are
    113 --   silently skipped.
    114 --
    115 --   >>> import Data.Maybe (fromJust)
    116 --   >>> let mpp = fromJust (featureByName "basic_mpp")
    117 --   >>> let fv = setFeatureWithDeps mpp Optional empty
    118 --   >>> isFeatureSet mpp fv
    119 --   True
    120 --   >>> let Just ps = featureByName "payment_secret"
    121 --   >>> isFeatureSet ps fv  -- dependency auto-set
    122 --   True
    123 setFeatureWithDeps
    124   :: Feature -> FeatureLevel -> FeatureVector -> FeatureVector
    125 setFeatureWithDeps !f !level !fv =
    126   let !fv' = setFeature f level fv
    127   in  foldr setDep fv' (featureDependencies f)
    128   where
    129     setDep !depName !acc = case featureByName depName of
    130       Nothing  -> acc
    131       Just dep -> setFeatureWithDeps dep level acc
    132 
    133 -- | Check if a feature is set in the vector.
    134 --
    135 --   Returns:
    136 --
    137 --   * @Just Required@ if the required (even) bit is set
    138 --   * @Just Optional@ if the optional (odd) bit is set (and required is not)
    139 --   * @Nothing@ if neither bit is set
    140 --
    141 --   >>> import Data.Maybe (fromJust)
    142 --   >>> let mpp = fromJust (featureByName "basic_mpp")
    143 --   >>> hasFeature mpp (setFeature mpp Optional empty)
    144 --   Just Optional
    145 --   >>> hasFeature mpp (setFeature mpp Required empty)
    146 --   Just Required
    147 --   >>> hasFeature mpp empty
    148 --   Nothing
    149 hasFeature :: Feature -> FeatureVector -> Maybe FeatureLevel
    150 hasFeature !f !fv
    151   | testBit baseBit fv       = Just Required
    152   | testBit (baseBit + 1) fv = Just Optional
    153   | otherwise                = Nothing
    154   where
    155     !baseBit = featureBaseBit f
    156 {-# INLINE hasFeature #-}
    157 
    158 -- | Check if either bit of a feature is set in the vector.
    159 --
    160 --   >>> import Data.Maybe (fromJust)
    161 --   >>> let mpp = fromJust (featureByName "basic_mpp")
    162 --   >>> isFeatureSet mpp (setFeature mpp Optional empty)
    163 --   True
    164 --   >>> isFeatureSet mpp empty
    165 --   False
    166 isFeatureSet :: Feature -> FeatureVector -> Bool
    167 isFeatureSet !f !fv =
    168   let !baseBit = featureBaseBit f
    169   in  testBit baseBit fv || testBit (baseBit + 1) fv
    170 {-# INLINE isFeatureSet #-}
    171 
    172 -- | List all known features that are set in the vector.
    173 --
    174 --   Returns pairs of (Feature, FeatureLevel) indicating whether each
    175 --   feature is set as required or optional.
    176 --
    177 --   >>> import Data.Maybe (fromJust)
    178 --   >>> let mpp = fromJust (featureByName "basic_mpp")
    179 --   >>> let ps = fromJust (featureByName "payment_secret")
    180 --   >>> let fv = setFeature mpp Optional (setFeature ps Required empty)
    181 --   >>> map (\(f, l) -> (featureName f, l)) (listFeatures fv)
    182 --   [("payment_secret",Required),("basic_mpp",Optional)]
    183 listFeatures :: FeatureVector -> [(Feature, FeatureLevel)]
    184 listFeatures !fv = foldr check [] knownFeatures
    185   where
    186     check !f !acc = case hasFeature f fv of
    187       Just level -> (f, level) : acc
    188       Nothing    -> acc