• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lduchosal / ipnetwork / 22803352265

07 Mar 2026 05:07PM UTC coverage: 91.848% (-1.3%) from 93.118%
22803352265

push

travis-pro

web-flow
Merge pull request #383 from lduchosal/feat/v4-nullable

feat/v4-nullable

651 of 721 branches covered (90.29%)

Branch coverage included in aggregate %.

111 of 147 new or added lines in 26 files covered. (75.51%)

5 existing lines in 3 files now uncovered.

1929 of 2088 relevant lines covered (92.39%)

574342.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.83
/src/System.Net.IPNetwork/IPNetwork2ParseRange.cs
1
// <copyright file="IPNetwork2Parse.cs" company="IPNetwork">
2
// Copyright (c) IPNetwork. All rights reserved.
3
// </copyright>
4

5
using System.Collections.Generic;
6
using System.Diagnostics.CodeAnalysis;
7
using System.Net.Sockets;
8
using System.Numerics;
9

10
namespace System.Net;
11

12
/// <summary>
13
/// the parse methods.
14
/// </summary>
15
public partial class IPNetwork2
16
{
17
    /// <summary>
18
    /// 192.168.1.45 - 192.168.1.65
19
    /// 
20
    /// ```
21
    /// 192.168.1.45/32 (covers: 192.168.1.45)
22
    /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
23
    /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
24
    /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
25
    /// ```
26
    /// 
27
    /// </summary>
28
    /// <param name="range">A string containing an ip range to convert (192.168.1.45 - 192.168.1.65).</param>
29
    /// <param name="ipnetworks">An IPNetwork List equivalent to the network contained in the range</param>
30
    /// <returns>true if parse was successful, false if the parse failed.</returns>
31
    public static bool TryParseRange(string range, [NotNullWhen(true)] out IEnumerable<IPNetwork2>? ipnetworks)
32
    {
22✔
33
        return InternalParseRange(true, range, out ipnetworks);
22✔
34
    }
22✔
35
    
36
    /// <summary>
37
    /// 192.168.1.45 - 192.168.1.65
38
    /// 
39
    /// ```
40
    /// 192.168.1.45/32 (covers: 192.168.1.45)
41
    /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
42
    /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
43
    /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
44
    /// ```
45
    /// 
46
    /// </summary>
47
    /// <param name="start">A string containing an ip range start (**192.168.1.45** - 192.168.1.65).</param>
48
    /// <param name="end">A string containing an ip range end (192.168.1.45 - **192.168.1.65**).</param>
49
    /// <param name="ipnetworks">An IPNetwork List equivalent to the network contained in the range</param>
50
    /// <returns>true if parse was successful, false if the parse failed.</returns>
51
    public static bool TryParseRange(string start, string end, [NotNullWhen(true)] out IEnumerable<IPNetwork2>? ipnetworks)
52
    {
22✔
53
        return InternalParseRange(true, start, end, out ipnetworks);
22✔
54
    }
22✔
55

56
    /// <summary>
57
    /// 192.168.1.45 - 192.168.1.65
58
    ///
59
    /// ```
60
    /// 192.168.1.45/32 (covers: 192.168.1.45)
61
    /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
62
    /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
63
    /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
64
    /// ```
65
    ///
66
    /// </summary>
67
    /// <param name="range">A string containing an ip range to convert (192.168.1.45 - 192.168.1.65).</param>
68
    /// <returns>An IPNetwork List equivalent to the network contained in the range.</returns>
69
    public static IEnumerable<IPNetwork2> ParseRange(string range)
70
    {
29✔
71
        if (!InternalParseRange(false, range, out IEnumerable<IPNetwork2>? ipnetworks))
29!
NEW
72
        {
×
NEW
73
            throw new ArgumentException("Failed to parse range.", nameof(range));
×
74
        }
75

76
        return ipnetworks;
22✔
77
    }
22✔
78

79
    /// <summary>
80
    /// 192.168.1.45, 192.168.1.65
81
    ///
82
    /// ```
83
    /// 192.168.1.45/32 (covers: 192.168.1.45)
84
    /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
85
    /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
86
    /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
87
    /// ```
88
    /// </summary>
89
    /// <param name="start">A string containing a start range ip address.</param>
90
    /// <param name="end">A string containing an end range ip address.</param>
91
    /// <returns>An IPNetwork List equivalent to the network contained in the range.</returns>
92
    public static IEnumerable<IPNetwork2> ParseRange(string start, string end)
93
    {
22✔
94
        if (!InternalParseRange(false, start, end, out IEnumerable<IPNetwork2>? ipnetworks))
22!
NEW
95
        {
×
NEW
96
            throw new ArgumentException("Failed to parse range.", nameof(start));
×
97
        }
98

99
        return ipnetworks;
22✔
100
    }
22✔
101
    
102
    /// <summary>
103
    /// 192.168.1.45 - 192.168.1.65
104
    ///
105
    /// ```
106
    /// 192.168.1.45/32 (covers: 192.168.1.45)
107
    /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
108
    /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
109
    /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
110
    /// ```
111
    /// </summary>
112
    /// <param name="tryParse">Whether to throw exception or not during conversion.</param>
113
    /// <param name="start">A string containing a start range ip address.</param>
114
    /// <param name="end">A string containing an end range ip address.</param>
115
    /// <param name="ipnetworks">The resulting IPNetworks.</param>
116
    internal static bool InternalParseRange(bool tryParse, string start, string end, [NotNullWhen(true)] out IEnumerable<IPNetwork2>? ipnetworks)
117
    {
106✔
118
        if (!IPAddress.TryParse(start, out IPAddress? startIp))
106✔
119
        {
5✔
120
            if (!tryParse)
5✔
121
            {
3✔
122
                throw new ArgumentException("Invalid start IPAddress", nameof(start));
3✔
123
            }
124

125
            ipnetworks = null;
2✔
126
            return false;
2✔
127
        }
128

129
        if (!IPAddress.TryParse(end, out IPAddress? endIp))
101✔
130
        {
4✔
131
            if (!tryParse)
4✔
132
            {
2✔
133
                throw new ArgumentException("Invalid end IPAddress", nameof(end));
2✔
134
            }
135

136
            ipnetworks = null;
2✔
137
            return false;
2✔
138
        }
139

140
        bool parsed = InternalParseRange(tryParse, startIp, endIp, out ipnetworks);
97✔
141
        return parsed;
93✔
142
    }
97✔
143

144
    /// <summary>
145
    /// Internal parse an IPNetwork2.
146
    /// </summary>
147
    /// <param name="tryParse">Prevent exception.</param>
148
    /// <param name="range">The network range parse.</param>
149
    /// <param name="ipnetworks">The resulting IPNetworks.</param>
150
    /// <exception cref="ArgumentNullException">When network is null.</exception>
151
    /// <exception cref="ArgumentException">When network is not valid.</exception>
152
    /// <returns>true if parsed, otherwise false</returns>
153
    internal static bool InternalParseRange(bool tryParse, string range, [NotNullWhen(true)] out IEnumerable<IPNetwork2>? ipnetworks)
154
    {
67✔
155
        if (string.IsNullOrEmpty(range))
67✔
156
        {
6✔
157
            if (!tryParse)
6✔
158
            {
5✔
159
                throw new ArgumentNullException(nameof(range));
5✔
160
            }
161

162
            ipnetworks = null;
1✔
163
            return false;
1✔
164
        }
165

166
        string[] args = range.Split([' ', '-'], StringSplitOptions.RemoveEmptyEntries);
61✔
167
        if (args.Length == 2)
61✔
168
        {
54✔
169
            bool parsed3 = InternalParseRange(tryParse, args[0], args[1], out ipnetworks);
54✔
170
            return parsed3;
48✔
171
        }
172
        
173
        if (!tryParse)
7✔
174
        {
5✔
175
            throw new ArgumentOutOfRangeException(nameof(range));
5✔
176
        }
177
        ipnetworks = null;
2✔
178
        return false;
2✔
179
    }
51✔
180

181
    /// <summary>
182
    /// 192.168.168.100 255.255.255.0
183
    ///
184
    /// Network   : 192.168.168.0
185
    /// Netmask   : 255.255.255.0
186
    /// Cidr      : 24
187
    /// Start     : 192.168.168.1
188
    /// End       : 192.168.168.254
189
    /// Broadcast : 192.168.168.255.
190
    /// </summary>
191
    /// <param name="tryParse">Whether to throw exception or not during conversion.</param>
192
    /// <param name="start">A start range ip address.</param>
193
    /// <param name="end">An end range ip address.</param>
194
    /// <param name="ipnetworks">The resulting IPNetworks.</param>
195
    internal static bool InternalParseRange(bool tryParse, IPAddress start, IPAddress end, [NotNullWhen(true)] out IEnumerable<IPNetwork2>? ipnetworks)
196
    {
124✔
197
        // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
198
        if (start == null)
124✔
199
        {
2✔
200
            if (!tryParse)
2✔
201
            {
1✔
202
                throw new ArgumentNullException(nameof(start));
1✔
203
            }
204

205
            ipnetworks = null;
1✔
206
            return false;
1✔
207
        }
208

209
        if (end == null)
122✔
210
        {
2✔
211
            if (!tryParse)
2✔
212
            {
1✔
213
                throw new ArgumentNullException(nameof(end));
1✔
214
            }
215
            ipnetworks = null;
1✔
216
            return false;
1✔
217
        }
218

219
        if (end.AddressFamily != start.AddressFamily)
120✔
220
        {
8✔
221
            if (!tryParse)
8✔
222
            {
4✔
223
                throw new ArgumentException("Start and end addresses must have the same address family.", nameof(end));
4✔
224
            }
225
            ipnetworks = null;
4✔
226
            return false;
4✔
227
        }
228

229
        var result = new List<IPNetwork2>();
112✔
230
    
231
        var startValue = ToBigInteger(start);
112✔
232
        var endValue = ToBigInteger(end);
112✔
233

234
        if (startValue > endValue)
112✔
235
        {
1✔
236
            throw new ArgumentException("Start IP must be less than or equal to end IP", nameof(end));
1✔
237
        }
238

239
        var addressFamily = start.AddressFamily;
111✔
240
        byte addressBits = addressFamily == AddressFamily.InterNetworkV6 ? (byte)128 : (byte)32;
111✔
241

242
        var current = startValue;
111✔
243
        while (current <= endValue)
245✔
244
        {
134✔
245
            // Find the largest CIDR block that starts at current and doesn't exceed endValue
246
            byte prefixLength = FindOptimalPrefixLength(current, endValue, addressBits);
134✔
247

248
            var network = new IPNetwork2(current, addressFamily, prefixLength);
134✔
249
            result.Add(network);
134✔
250
        
251
            // Move to the next IP after this block
252
            uint blockSize = (uint)(1 << (addressBits - prefixLength));
134✔
253
            current += blockSize;
134✔
254
        }
134✔
255
        
256
        ipnetworks = result;
111✔
257
        return true;
111✔
258
    }
117✔
259

260
    private static byte FindOptimalPrefixLength(BigInteger startIp, BigInteger endIp, int addressBits)
261
    {
134✔
262
        BigInteger remainingIps = endIp - startIp + 1;
134✔
263
    
264
        // Find the number of trailing zeros in startIp (alignment)
265
        int alignment = startIp.IsZero ? addressBits : CountTrailingZeros(startIp);
134✔
266
    
267
        // Find the largest power of 2 that fits in the remaining range
268
        int maxBlockSizeBits = remainingIps.IsZero ? 0 : GetHighestBitPosition(remainingIps);
134!
269
    
270
        // Take the minimum of alignment and what fits in range
271
        int blockSizeBits = Math.Min(alignment, maxBlockSizeBits);
134✔
272
    
273
        // Convert to prefix length
274
        return (byte)(addressBits - blockSizeBits);
134✔
275
    }
134✔
276

277
    private static int CountTrailingZeros(BigInteger value)
278
    {
121✔
279
        if (value.IsZero) return 0;
121!
280
    
281
        int count = 0;
121✔
282
        while ((value & BigInteger.One) == 0)
2,059✔
283
        {
1,938✔
284
            value >>= 1;
1,938✔
285
            count++;
1,938✔
286
        }
1,938✔
287
        return count;
121✔
288
    }
121✔
289

290
    private static int GetHighestBitPosition(BigInteger value)
291
    {
134✔
292
        if (value.IsZero) return 0;
134!
293
    
294
        int position = 0;
134✔
295
        while (value > 1)
1,151✔
296
        {
1,017✔
297
            value >>= 1;
1,017✔
298
            position++;
1,017✔
299
        }
1,017✔
300
        return position;
134✔
301
    }
134✔
302
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc