< Summary - Combined Code Coverage

Information
Class: NLightning.Domain.Node.FeatureSet
Assembly: NLightning.Domain
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Domain/Node/FeatureSet.cs
Tag: 57_24045730253
Line coverage
57%
Covered lines: 117
Uncovered lines: 87
Coverable lines: 204
Total lines: 433
Line coverage: 57.3%
Branch coverage
49%
Covered branches: 84
Total branches: 170
Branch coverage: 49.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
.ctor()100%210%
get_SizeInBits()100%210%
NewBasicChannelType()100%210%
get_SizeInBits()100%11100%
SetFeature(...)0%156120%
SetFeature(...)100%1212100%
SetFeature(...)0%620%
SetFeature(...)100%22100%
IsFeatureSet(...)0%620%
IsFeatureSet(...)100%22100%
IsFeatureSet(...)0%620%
IsFeatureSet(...)100%22100%
IsFeatureSet(...)0%620%
IsFeatureSet(...)100%22100%
IsFeatureSet(...)0%620%
IsOptionAnchorsSet()0%620%
IsFeatureSet(...)100%22100%
IsOptionAnchorsSet()0%620%
IsCompatible(...)0%702260%
IsCompatible(...)96.15%26.22693.33%
WriteToBitWriter(...)0%4260%
WriteToBitWriter(...)100%66100%
HasFeature(...)0%620%
HasFeature(...)0%620%
GetBytes(...)50%2.01287.5%
DeserializeFromBytes(...)0%620%
DeserializeFromBitReader(...)0%620%
DeserializeFromBytes(...)100%2.09271.43%
Combine(...)0%2040%
DeserializeFromBitReader(...)100%2.09271.43%
ToString()0%2040%
Combine(...)100%44100%
AreDependenciesSet()0%110100%
ToString()83.33%6.11685.71%
OnChanged()0%620%
GetLastIndexOfOne(...)0%2040%
AreDependenciesSet()100%1010100%
OnChanged()100%22100%
GetLastIndexOfOne(...)83.33%6.29680%

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Domain/Node/FeatureSet.cs

#LineLine coverage
 1using System.Collections;
 2using System.Runtime.Serialization;
 3using System.Text;
 4
 5namespace NLightning.Domain.Node;
 6
 7using Domain.Utils.Interfaces;
 8using Enums;
 9
 10/// <summary>
 11/// Represents the features supported by a node. <see href="https://github.com/lightning/bolts/blob/master/09-features.m
 12/// </summary>
 13public class FeatureSet
 14{
 15    /// <summary>
 16    /// Some features are dependent on other features. This dictionary contains the dependencies.
 17    /// </summary>
 2018    private static readonly Dictionary<Feature, Feature[]> s_featureDependencies = new()
 2019    {
 2020        // This \/ --- Depends on this \/
 2021        { Feature.GossipQueriesEx, [Feature.GossipQueries] },
 2022        { Feature.OptionZeroconf, [Feature.OptionScidAlias] },
 2023    };
 024
 025    internal BitArray FeatureFlags;
 026
 027    /// <summary>
 028    /// Initializes a new instance of the <see cref="FeatureSet"/> class.
 29    /// </summary>
 30    /// <remarks>
 31    /// Always set the bit of <see cref="Feature.VarOnionOptin"/> as Optional.
 32    /// </remarks>
 81633    public FeatureSet()
 34    {
 81635        FeatureFlags = new BitArray(128);
 36        // Always set the compulsory bit of option_data_loss_protect
 81637        SetFeature(Feature.OptionDataLossProtect, true);
 038        // Always set the compulsory bit of var_onion_optin
 81639        SetFeature(Feature.VarOnionOptin, true);
 040        // Always set the compulsory bit of option_static_remote_key
 81641        SetFeature(Feature.OptionStaticRemoteKey, true);
 042        // Always set the compulsory bit of payment_secret
 81643        SetFeature(Feature.PaymentSecret, true);
 44        // Always set the compulsory bit for option_channel_type
 81645        SetFeature(Feature.OptionChannelType, true);
 81646    }
 47
 48    public static FeatureSet NewBasicChannelType()
 49    {
 050        // Initialize a new FeatureSet with only OptionStaticRemoteKey set as compulsory
 051        var featureFlagsForChannelType = DeserializeFromBytes([0b0001_0000, 0b0000_0000]);
 052        return featureFlagsForChannelType;
 53    }
 54
 55    public event EventHandler? Changed;
 56
 57    /// <summary>
 58    /// Gets the last index-of-one in the BitArray and add 1 because arrays starts at 0.
 59    /// </summary>
 40860    public int SizeInBits => GetLastIndexOfOne(FeatureFlags);
 61
 62    /// <summary>
 63    /// Sets a feature.
 64    /// </summary>
 065    /// <param name="feature">The feature to set.</param>
 66    /// <param name="isCompulsory">If the feature is compulsory.</param>
 067    /// <param name="isSet">true to set the feature, false to unset it</param>
 68    /// <remarks>
 069    /// If the feature has dependencies, they will be set first.
 070    /// The dependencies keep the same isCompulsory value as the feature being set.
 71    /// </remarks>
 72    public void SetFeature(Feature feature, bool isCompulsory, bool isSet = true)
 73    {
 74        // If we're setting the feature, and it has dependencies, set them first
 466475        if (isSet)
 076        {
 449677            if (s_featureDependencies.TryGetValue(feature, out var dependencies))
 78            {
 16079                foreach (var dependency in dependencies)
 4080                    SetFeature(dependency, isCompulsory, isSet);
 081            }
 82        }
 83        else // If we're unsetting the feature, and it has dependents, unset them first
 084        {
 69685            foreach (var dependent in s_featureDependencies.Where(x => x.Value.Contains(feature)).Select(x => x.Key))
 886                SetFeature(dependent, isCompulsory, isSet);
 87        }
 88
 466489        var bitPosition = (int)feature;
 090
 466491        if (isCompulsory)
 92        {
 93            // Unset the non-compulsory bit
 430494            SetFeature(bitPosition, false);
 430495            --bitPosition;
 96        }
 97        else
 98        {
 99            // Unset the compulsory bit
 360100            SetFeature(bitPosition - 1, false);
 101        }
 102
 103        // Then set the feature itself
 4664104        SetFeature(bitPosition, isSet);
 4664105    }
 106
 0107    /// <summary>
 108    /// Sets a feature.
 0109    /// </summary>
 0110    /// <param name="bitPosition">The bit position of the feature to set.</param>
 111    /// <param name="isSet">true to set the feature, false to unset it</param>
 112    public void SetFeature(int bitPosition, bool isSet)
 113    {
 9756114        if (bitPosition >= FeatureFlags.Length)
 68115            FeatureFlags.Length = bitPosition + 1;
 116
 9756117        FeatureFlags.Set(bitPosition, isSet);
 118
 9756119        OnChanged();
 9756120    }
 0121
 122    /// <summary>
 123    /// Checks if a feature is set either as compulsory or optional.
 124    /// </summary>
 125    /// <param name="feature">Feature to check.</param>
 126    /// <returns>true if the feature is set, false otherwise.</returns>
 127    public bool IsFeatureSet(Feature feature)
 128    {
 120129        var bitPosition = (int)feature;
 130
 120131        return IsFeatureSet(bitPosition) || IsFeatureSet(bitPosition - 1);
 0132    }
 133
 134    /// <summary>
 0135    /// Checks if a feature is set.
 0136    /// </summary>
 137    /// <param name="feature">Feature to check.</param>
 0138    /// <param name="isCompulsory">If the feature is compulsory.</param>
 139    /// <returns>true if the feature is set, false otherwise.</returns>
 140    public bool IsFeatureSet(Feature feature, bool isCompulsory)
 141    {
 572142        var bitPosition = (int)feature;
 143
 144        // If the feature is compulsory, adjust the bit position to be even
 572145        if (isCompulsory)
 272146            bitPosition--;
 147
 572148        return IsFeatureSet(bitPosition);
 149    }
 0150
 0151    /// <summary>
 152    /// Checks if a feature is set.
 0153    /// </summary>
 154    /// <param name="bitPosition">The bit position of the feature to check.</param>
 155    /// <param name="isCompulsory">If the feature is compulsory.</param>
 156    /// <returns>true if the feature is set, false otherwise.</returns>
 157    public bool IsFeatureSet(int bitPosition, bool isCompulsory)
 158    {
 159        // If the feature is compulsory, adjust the bit position to be even
 19632160        if (isCompulsory)
 9744161            bitPosition--;
 162
 19632163        return IsFeatureSet(bitPosition);
 164    }
 165
 166    /// <summary>
 167    /// Checks if a feature is set.
 168    /// </summary>
 169    /// <param name="bitPosition">The bit position of the feature to check.</param>
 170    /// <returns>true if the feature is set, false otherwise.</returns>
 171    private bool IsFeatureSet(int bitPosition)
 0172    {
 23612173        return bitPosition < FeatureFlags.Length && FeatureFlags.Get(bitPosition);
 174    }
 175
 176    /// <summary>
 177    /// Checks if the option_anchors feature is set.
 178    /// </summary>
 179    /// <returns>true if one of the features is set, false otherwise.</returns>
 180    public bool IsOptionAnchorsSet()
 181    {
 0182        return IsFeatureSet(Feature.OptionAnchors, false) || IsFeatureSet(Feature.OptionAnchors, true);
 183    }
 184
 185    /// <summary>
 186    /// Check if this feature set is compatible with the other provided feature set.
 187    /// </summary>
 188    /// <param name="other">The other feature set to check compatibility with.</param>
 0189    /// <param name="negotiatedFeatureSet">The resulting negotiated feature set.</param>
 190    /// <returns>true if the feature sets are compatible, false otherwise.</returns>
 0191    /// <remarks>
 0192    /// The other feature set must support the var_onion_optin feature.
 193    /// The other feature set must have all the dependencies set.
 194    /// </remarks>
 195    public bool IsCompatible(FeatureSet other, out FeatureSet? negotiatedFeatureSet)
 0196    {
 197        // Check if the other node supports var_onion_optin
 120198        if (!other.IsFeatureSet(Feature.VarOnionOptin, false) && !other.IsFeatureSet(Feature.VarOnionOptin, true))
 0199        {
 4200            negotiatedFeatureSet = null;
 4201            return false;
 0202        }
 0203
 0204        // Check which one is bigger and iterate on it
 116205        var maxLength = Math.Max(FeatureFlags.Length, other.FeatureFlags.Length);
 206
 207        // Create a temporary feature set to store the negotiated features
 116208        negotiatedFeatureSet = new FeatureSet();
 7776209        for (var i = 1; i < maxLength; i += 2)
 210        {
 3784211            var isLocalOptionalSet = IsFeatureSet(i, false);
 3784212            var isLocalCompulsorySet = IsFeatureSet(i, true);
 3784213            var isOtherOptionalSet = other.IsFeatureSet(i, false);
 3784214            var isOtherCompulsorySet = other.IsFeatureSet(i, true);
 215
 216            // If the feature is unknown
 3784217            if (!Enum.IsDefined(typeof(Feature), i))
 0218            {
 219                // If the feature is unknown and even, close the connection
 2096220                if (isOtherCompulsorySet)
 221                {
 0222                    negotiatedFeatureSet = null;
 0223                    return false;
 224                }
 0225
 2096226                if (isOtherOptionalSet)
 20227                    negotiatedFeatureSet.SetFeature(i, false);
 228            }
 229            else
 0230            {
 231                // If the local feature is compulsory, the other feature should also be set (either optional or compulso
 1688232                if (isLocalCompulsorySet && !(isOtherOptionalSet || isOtherCompulsorySet))
 0233                {
 4234                    negotiatedFeatureSet = null;
 4235                    return false;
 0236                }
 237
 0238                // If the other feature is compulsory, the local feature should also be set (either optional or compulso
 1684239                if (isOtherCompulsorySet && !(isLocalOptionalSet || isLocalCompulsorySet))
 0240                {
 8241                    negotiatedFeatureSet = null;
 8242                    return false;
 243                }
 244
 1676245                if (isOtherCompulsorySet || isLocalCompulsorySet)
 246                {
 232247                    negotiatedFeatureSet.SetFeature(i, true);
 0248                }
 1444249                else if (isLocalOptionalSet && isOtherOptionalSet)
 250                {
 96251                    negotiatedFeatureSet.SetFeature(i, false);
 0252                }
 253            }
 254        }
 255
 256        // Check if all the other node's dependencies are set
 104257        if (other.AreDependenciesSet())
 100258            return true;
 259
 4260        negotiatedFeatureSet = null;
 4261        return false;
 0262    }
 0263
 264    /// <summary>
 0265    /// Serializes the features to a byte array.
 0266    /// </summary>
 0267    public void WriteToBitWriter(IBitWriter bitWriter, int length, bool shouldPad)
 268    {
 269        // Check if _featureFlags is as long as the length
 88270        var extraLength = length - FeatureFlags.Length;
 88271        if (extraLength > 0)
 4272            FeatureFlags.Length += extraLength;
 273
 6856274        for (var i = 0; i < length && bitWriter.HasMoreBits(1); i++)
 3340275            bitWriter.WriteBit(FeatureFlags[length - i - (shouldPad ? 0 : 1)]);
 88276    }
 0277
 278    /// <summary>
 279    /// Checks if a feature is set.
 280    /// </summary>
 281    /// <param name="feature">The feature to check.</param>
 282    /// <returns>true if the feature is set, false otherwise.</returns>
 283    /// <remarks>
 284    /// We don't care if the feature is compulsory or optional.
 285    /// </remarks>
 0286    public bool HasFeature(Feature feature) => IsFeatureSet(feature, false) || IsFeatureSet(feature, true);
 287
 288    public byte[]? GetBytes(bool asGlobal = false)
 289    {
 290        // Get the last valid bit
 120291        var lastIndexOfOne = GetLastIndexOfOne(FeatureFlags, asGlobal);
 120292        if (lastIndexOfOne == -1)
 0293            return null;
 294
 0295        // Calculate total bytes needed
 120296        var totalBytes = (FeatureFlags.Length + 7) / 8;
 120297        var bytes = new byte[totalBytes];
 0298
 299        // Copy bits as bytes
 120300        FeatureFlags.CopyTo(bytes, 0);
 301
 0302        // Calculate last valid byte
 120303        var lastValidByte = (lastIndexOfOne + 7) / 8;
 304
 120305        return bytes[..lastValidByte];
 306    }
 307
 308    /// <summary>
 309    /// Deserializes the features from a byte array.
 310    /// </summary>
 311    /// <param name="data">The byte array to deserialize from.</param>
 312    /// <remarks>
 313    /// The byte array can have a length less than or equal to 8 bytes.
 314    /// </remarks>
 315    /// <returns>The deserialized features.</returns>
 316    /// <exception cref="SerializationException">Error deserializing Features</exception>
 0317    public static FeatureSet DeserializeFromBytes(byte[] data)
 0318    {
 0319        try
 320        {
 168321            if (BitConverter.IsLittleEndian)
 168322                Array.Reverse(data);
 0323
 168324            var bitArray = new BitArray(data);
 168325            return new FeatureSet { FeatureFlags = bitArray };
 326        }
 0327        catch (Exception e)
 328        {
 0329            throw new SerializationException("Error deserializing Features", e);
 330        }
 168331    }
 332
 333    /// <summary>
 334    /// Deserializes the features from a BitReader.
 335    /// </summary>
 336    /// <param name="bitReader">The bit reader to read from.</param>
 337    /// <param name="length">The number of bits to read.</param>
 338    /// <param name="shouldPad">If the bit array should be padded.</param>
 339    /// <returns>The deserialized features.</returns>
 0340    /// <exception cref="SerializationException">Error deserializing Features</exception>
 0341    public static FeatureSet DeserializeFromBitReader(IBitReader bitReader, int length, bool shouldPad)
 342    {
 0343        try
 0344        {
 345            // Create a new bit array
 104346            var bitArray = new BitArray(length + (shouldPad ? 1 : 0));
 8168347            for (var i = 0; i < length; i++)
 3980348                bitArray.Set(length - i - (shouldPad ? 0 : 1), bitReader.ReadBit());
 349
 104350            return new FeatureSet { FeatureFlags = bitArray };
 0351        }
 0352        catch (Exception e)
 353        {
 0354            throw new SerializationException("Error deserializing Features", e);
 0355        }
 104356    }
 357
 0358    /// <summary>
 359    /// Combines two feature sets.
 360    /// </summary>
 361    /// <param name="first">The first feature set.</param>
 362    /// <param name="second">The second feature set.</param>
 363    /// <returns>The combined feature set.</returns>
 364    /// <remarks>
 365    /// The combined feature set is the logical OR of the two feature sets.
 366    /// </remarks>
 367    public static FeatureSet Combine(FeatureSet first, FeatureSet second)
 368    {
 16369        var combinedLength = Math.Max(first.FeatureFlags.Length, second.FeatureFlags.Length);
 16370        var combinedFlags = new BitArray(combinedLength);
 0371
 1952372        for (var i = 0; i < combinedLength; i++)
 960373            combinedFlags.Set(i, first.IsFeatureSet(i) || second.IsFeatureSet(i));
 374
 16375        return new FeatureSet { FeatureFlags = combinedFlags };
 0376    }
 377
 378    public override string ToString()
 0379    {
 16380        var sb = new StringBuilder();
 1440381        for (var i = 1; i < FeatureFlags.Length; i += 2)
 382        {
 704383            if (IsFeatureSet(i))
 0384                sb.Append($"{(Feature)i}, ");
 704385            else if (IsFeatureSet(i - 1))
 80386                sb.Append($"{(Feature)i}, ");
 387        }
 0388
 16389        return sb.ToString().TrimEnd(' ', ',');
 390    }
 391
 392    /// <summary>
 0393    /// Checks if all dependencies are set.
 394    /// </summary>
 0395    /// <returns>true if all dependencies are set, false otherwise.</returns>
 0396    /// <remarks>
 397    /// This method is used to check if all dependencies are set when a feature is set.
 398    /// </remarks>
 0399    private bool AreDependenciesSet()
 400    {
 401        // Check if all known (Feature Enum) dependencies are set if the feature is set
 4772402        foreach (var feature in Enum.GetValues<Feature>())
 403        {
 2284404            if (!IsFeatureSet((int)feature, false) && !IsFeatureSet((int)feature, true))
 405                continue;
 406
 324407            if (!s_featureDependencies.TryGetValue(feature, out var dependencies))
 408                continue;
 409
 8410            if (dependencies.Any(dependency => !IsFeatureSet(dependency, false) && !IsFeatureSet(dependency, true)))
 4411                return false;
 412        }
 413
 100414        return true;
 415    }
 416
 417    private void OnChanged()
 418    {
 9756419        Changed?.Invoke(this, EventArgs.Empty);
 292420    }
 421
 422    private static int GetLastIndexOfOne(BitArray bitArray, bool asGlobal = false)
 423    {
 528424        var maxLength = asGlobal ? 13 : bitArray.Length;
 26800425        for (var i = maxLength - 1; i >= 0; i--)
 426        {
 13400427            if (bitArray[i])
 528428                return i;
 429        }
 430
 0431        return -1; // Return -1 if no number 1 is found
 432    }
 433}

Methods/Properties

.cctor()
.ctor()
.ctor()
get_SizeInBits()
NewBasicChannelType()
get_SizeInBits()
SetFeature(NLightning.Domain.Enums.Feature,System.Boolean,System.Boolean)
SetFeature(NLightning.Domain.Enums.Feature,System.Boolean,System.Boolean)
SetFeature(System.Int32,System.Boolean)
SetFeature(System.Int32,System.Boolean)
IsFeatureSet(NLightning.Domain.Enums.Feature)
IsFeatureSet(NLightning.Domain.Enums.Feature)
IsFeatureSet(NLightning.Domain.Enums.Feature,System.Boolean)
IsFeatureSet(NLightning.Domain.Enums.Feature,System.Boolean)
IsFeatureSet(System.Int32,System.Boolean)
IsFeatureSet(System.Int32,System.Boolean)
IsFeatureSet(System.Int32)
IsOptionAnchorsSet()
IsFeatureSet(System.Int32)
IsOptionAnchorsSet()
IsCompatible(NLightning.Domain.Node.FeatureSet,NLightning.Domain.Node.FeatureSet&)
IsCompatible(NLightning.Domain.Node.FeatureSet,NLightning.Domain.Node.FeatureSet&)
WriteToBitWriter(NLightning.Domain.Utils.Interfaces.IBitWriter,System.Int32,System.Boolean)
WriteToBitWriter(NLightning.Domain.Utils.Interfaces.IBitWriter,System.Int32,System.Boolean)
HasFeature(NLightning.Domain.Enums.Feature)
HasFeature(NLightning.Domain.Enums.Feature)
GetBytes(System.Boolean)
DeserializeFromBytes(System.Byte[])
DeserializeFromBitReader(NLightning.Domain.Utils.Interfaces.IBitReader,System.Int32,System.Boolean)
DeserializeFromBytes(System.Byte[])
Combine(NLightning.Domain.Node.FeatureSet,NLightning.Domain.Node.FeatureSet)
DeserializeFromBitReader(NLightning.Domain.Utils.Interfaces.IBitReader,System.Int32,System.Boolean)
ToString()
Combine(NLightning.Domain.Node.FeatureSet,NLightning.Domain.Node.FeatureSet)
AreDependenciesSet()
ToString()
OnChanged()
GetLastIndexOfOne(System.Collections.BitArray)
AreDependenciesSet()
OnChanged()
GetLastIndexOfOne(System.Collections.BitArray,System.Boolean)