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 . Net . Sockets ;
7+ using System . Numerics ;
8+
9+ namespace System . Net ;
10+
11+ /// <summary>
12+ /// the parse methods.
13+ /// </summary>
14+ public partial class IPNetwork2
15+ {
16+ /// <summary>
17+ /// 192.168.1.45 - 192.168.1.65
18+ ///
19+ /// ```
20+ /// 192.168.1.45/32 (covers: 192.168.1.45)
21+ /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
22+ /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
23+ /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
24+ /// ```
25+ ///
26+ /// </summary>
27+ /// <param name="range">A string containing an ip range to convert (192.168.1.45 - 192.168.1.65).</param>
28+ /// <param name="ipnetworks">An IPNetwork List equivalent to the network contained in the range</param>
29+ /// <returns>true if parse was successful, false if the parse failed.</returns>
30+ public static bool TryParseRange ( string range , out IEnumerable < IPNetwork2 > ipnetworks )
31+ {
32+ return InternalParseRange ( true , range , out ipnetworks ) ;
33+ }
34+
35+ /// <summary>
36+ /// 192.168.1.45 - 192.168.1.65
37+ ///
38+ /// ```
39+ /// 192.168.1.45/32 (covers: 192.168.1.45)
40+ /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
41+ /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
42+ /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
43+ /// ```
44+ ///
45+ /// </summary>
46+ /// <param name="start">A string containing an ip range start (**192.168.1.45** - 192.168.1.65).</param>
47+ /// <param name="end">A string containing an ip range end (192.168.1.45 - **192.168.1.65**).</param>
48+ /// <param name="ipnetworks">An IPNetwork List equivalent to the network contained in the range</param>
49+ /// <returns>true if parse was successful, false if the parse failed.</returns>
50+ public static bool TryParseRange ( string start , string end , out IEnumerable < IPNetwork2 > ipnetworks )
51+ {
52+ return InternalParseRange ( true , start , end , out ipnetworks ) ;
53+ }
54+
55+ /// <summary>
56+ /// 192.168.1.45 - 192.168.1.65
57+ ///
58+ /// ```
59+ /// 192.168.1.45/32 (covers: 192.168.1.45)
60+ /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
61+ /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
62+ /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
63+ /// ```
64+ ///
65+ /// </summary>
66+ /// <param name="range">A string containing an ip range to convert (192.168.1.45 - 192.168.1.65).</param>
67+ /// <returns>An IPNetwork List equivalent to the network contained in the range.</returns>
68+ public static IEnumerable < IPNetwork2 > ParseRange ( string range )
69+ {
70+ InternalParseRange ( false , range , out IEnumerable < IPNetwork2 > ipnetworks ) ;
71+ return ipnetworks ;
72+ }
73+
74+ /// <summary>
75+ /// 192.168.1.45, 192.168.1.65
76+ ///
77+ /// ```
78+ /// 192.168.1.45/32 (covers: 192.168.1.45)
79+ /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
80+ /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
81+ /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
82+ /// ```
83+ /// </summary>
84+ /// <param name="start">A string containing a start range ip address.</param>
85+ /// <param name="end">A string containing a end range ip address.</param>
86+ /// <returns>An IPNetwork List equivalent to the network contained in the range.</returns>
87+ public static IEnumerable < IPNetwork2 > ParseRange ( string start , string end )
88+ {
89+ InternalParseRange ( false , start , end , out IEnumerable < IPNetwork2 > ipnetworks ) ;
90+ return ipnetworks ;
91+ }
92+
93+ /// <summary>
94+ /// 192.168.1.45 - 192.168.1.65
95+ ///
96+ /// ```
97+ /// 192.168.1.45/32 (covers: 192.168.1.45)
98+ /// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
99+ /// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
100+ /// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
101+ /// ```
102+ /// </summary>
103+ /// <param name="tryParse">Whether to throw exception or not during conversion.</param>
104+ /// <param name="start">A string containing a start range ip address.</param>
105+ /// <param name="end">A string containing a end range ip address.</param>
106+ /// <param name="ipnetworks">The resulting IPNetworks.</param>
107+ internal static bool InternalParseRange ( bool tryParse , string start , string end , out IEnumerable < IPNetwork2 > ipnetworks )
108+ {
109+ bool startParsed = IPAddress . TryParse ( start , out IPAddress startIp ) ;
110+ if ( ! startParsed )
111+ {
112+ if ( ! tryParse )
113+ {
114+ throw new ArgumentException ( "Invalid start IPAddress" , nameof ( start ) ) ;
115+ }
116+
117+ ipnetworks = null ;
118+ return false ;
119+ }
120+
121+ bool endParsed = IPAddress . TryParse ( end , out IPAddress endIp ) ;
122+ if ( ! endParsed )
123+ {
124+ if ( ! tryParse )
125+ {
126+ throw new ArgumentException ( "Invalid end IPAddress" , nameof ( end ) ) ;
127+ }
128+
129+ ipnetworks = null ;
130+ return false ;
131+ }
132+
133+ bool parsed = InternalParseRange ( tryParse , startIp , endIp , out ipnetworks ) ;
134+ return parsed ;
135+ }
136+
137+ /// <summary>
138+ /// Internal parse an IPNetwork2.
139+ /// </summary>
140+ /// <param name="tryParse">Prevent exception.</param>
141+ /// <param name="range">The network range parse.</param>
142+ /// <param name="ipnetworks">The resulting IPNetworks.</param>
143+ /// <exception cref="ArgumentNullException">When network is null.</exception>
144+ /// <exception cref="ArgumentException">When network is not valid.</exception>
145+ /// <returns>true if parsed, otherwise false</returns>
146+ internal static bool InternalParseRange ( bool tryParse , string range , out IEnumerable < IPNetwork2 > ipnetworks )
147+ {
148+ if ( string . IsNullOrEmpty ( range ) )
149+ {
150+ if ( ! tryParse )
151+ {
152+ throw new ArgumentNullException ( nameof ( range ) ) ;
153+ }
154+
155+ ipnetworks = null ;
156+ return false ;
157+ }
158+
159+ string [ ] args = range . Split ( [ ' ' , '-' ] , StringSplitOptions . RemoveEmptyEntries ) ;
160+ if ( args . Length == 2 )
161+ {
162+ bool parsed3 = InternalParseRange ( tryParse , args [ 0 ] , args [ 1 ] , out ipnetworks ) ;
163+ return parsed3 ;
164+ }
165+
166+ if ( ! tryParse )
167+ {
168+ throw new ArgumentOutOfRangeException ( nameof ( range ) ) ;
169+ }
170+ ipnetworks = null ;
171+ return false ;
172+ }
173+
174+ /// <summary>
175+ /// 192.168.168.100 255.255.255.0
176+ ///
177+ /// Network : 192.168.168.0
178+ /// Netmask : 255.255.255.0
179+ /// Cidr : 24
180+ /// Start : 192.168.168.1
181+ /// End : 192.168.168.254
182+ /// Broadcast : 192.168.168.255.
183+ /// </summary>
184+ /// <param name="tryParse">Whether to throw exception or not during conversion.</param>
185+ /// <param name="start">A start range ip address.</param>
186+ /// <param name="end">An end range ip address.</param>
187+ /// <param name="ipnetworks">The resulting IPNetworks.</param>
188+ internal static bool InternalParseRange ( bool tryParse , IPAddress start , IPAddress end , out IEnumerable < IPNetwork2 > ipnetworks )
189+ {
190+ if ( start == null )
191+ {
192+ if ( ! tryParse )
193+ {
194+ throw new ArgumentNullException ( nameof ( start ) ) ;
195+ }
196+
197+ ipnetworks = null ;
198+ return false ;
199+ }
200+
201+ if ( end == null )
202+ {
203+ if ( ! tryParse )
204+ {
205+ throw new ArgumentNullException ( nameof ( end ) ) ;
206+ }
207+ ipnetworks = null ;
208+ return false ;
209+ }
210+
211+ if ( end . AddressFamily != start . AddressFamily )
212+ {
213+ if ( ! tryParse )
214+ {
215+ throw new ArgumentException ( nameof ( AddressFamily ) ) ;
216+ }
217+ ipnetworks = null ;
218+ return false ;
219+ }
220+
221+ var result = new List < IPNetwork2 > ( ) ;
222+
223+ var startValue = ToBigInteger ( start ) ;
224+ var endValue = ToBigInteger ( end ) ;
225+
226+ if ( startValue > endValue )
227+ {
228+ throw new ArgumentException ( "Start IP must be less than or equal to end IP" , nameof ( end ) ) ;
229+ }
230+
231+ var addressFamily = start . AddressFamily ;
232+ byte addressBits = addressFamily == AddressFamily . InterNetworkV6 ? ( byte ) 128 : ( byte ) 32 ;
233+
234+ var current = startValue ;
235+ while ( current <= endValue )
236+ {
237+ // Find the largest CIDR block that starts at current and doesn't exceed endValue
238+ byte prefixLength = FindOptimalPrefixLength ( current , endValue , addressBits ) ;
239+
240+ var network = new IPNetwork2 ( current , addressFamily , prefixLength ) ;
241+ result . Add ( network ) ;
242+
243+ // Move to the next IP after this block
244+ uint blockSize = ( uint ) ( 1 << ( addressBits - prefixLength ) ) ;
245+ current += blockSize ;
246+ }
247+
248+ ipnetworks = result ;
249+ return true ;
250+ }
251+
252+ private static byte FindOptimalPrefixLength ( BigInteger startIp , BigInteger endIp , int addressBits )
253+ {
254+ BigInteger remainingIps = endIp - startIp + 1 ;
255+
256+ // Find the number of trailing zeros in startIp (alignment)
257+ int alignment = startIp . IsZero ? addressBits : CountTrailingZeros ( startIp ) ;
258+
259+ // Find the largest power of 2 that fits in the remaining range
260+ int maxBlockSizeBits = remainingIps . IsZero ? 0 : GetHighestBitPosition ( remainingIps ) ;
261+
262+ // Take the minimum of alignment and what fits in range
263+ int blockSizeBits = Math . Min ( alignment , maxBlockSizeBits ) ;
264+
265+ // Convert to prefix length
266+ return ( byte ) ( addressBits - blockSizeBits ) ;
267+ }
268+
269+ private static int CountTrailingZeros ( BigInteger value )
270+ {
271+ if ( value . IsZero ) return 0 ;
272+
273+ int count = 0 ;
274+ while ( ( value & BigInteger . One ) == 0 )
275+ {
276+ value >>= 1 ;
277+ count ++ ;
278+ }
279+ return count ;
280+ }
281+
282+ private static int GetHighestBitPosition ( BigInteger value )
283+ {
284+ if ( value . IsZero ) return 0 ;
285+
286+ int position = 0 ;
287+ while ( value > 1 )
288+ {
289+ value >>= 1 ;
290+ position ++ ;
291+ }
292+ return position ;
293+ }
294+ }
0 commit comments