@@ -56,6 +56,112 @@ await Assert.That(result)
5656 . EqualTo ( expected ) ;
5757 }
5858
59+ [ Test ]
60+ public async Task Mask_AllZerosMask_ShouldReturnAllZerosAddress ( )
61+ {
62+ var ip = IPAddress . Parse ( "192.168.1.100" ) ;
63+ var mask = IPAddress . Parse ( "0.0.0.0" ) ;
64+ var expected = IPAddress . Parse ( "0.0.0.0" ) ;
65+
66+ var result = ip . Mask ( mask ) ;
67+
68+ await Assert . That ( result )
69+ . EqualTo ( expected ) ;
70+ }
71+
72+ [ Test ]
73+ public async Task Mask_AllOnesMask_ShouldReturnOriginalAddress ( )
74+ {
75+ var ip = IPAddress . Parse ( "192.168.1.100" ) ;
76+ var mask = IPAddress . Parse ( "255.255.255.255" ) ;
77+
78+ var result = ip . Mask ( mask ) ;
79+
80+ await Assert . That ( result )
81+ . EqualTo ( ip ) ;
82+ }
83+
84+ [ Test ]
85+ [ Arguments ( "192.168.1.200" , "255.255.255.128" , "192.168.1.128" ) ]
86+ [ Arguments ( "192.168.1.50" , "255.255.255.128" , "192.168.1.0" ) ]
87+ [ Arguments ( "10.10.10.10" , "255.255.255.192" , "10.10.10.0" ) ]
88+ [ Arguments ( "10.10.10.200" , "255.255.255.192" , "10.10.10.192" ) ]
89+ public async Task Mask_NonOctetAlignedMask_ShouldMaskCorrectly ( string ipAddress , string maskAddress , string expected )
90+ {
91+ var ip = IPAddress . Parse ( ipAddress ) ;
92+ var mask = IPAddress . Parse ( maskAddress ) ;
93+ var expectedResult = IPAddress . Parse ( expected ) ;
94+
95+ var result = ip . Mask ( mask ) ;
96+
97+ await Assert . That ( result )
98+ . EqualTo ( expectedResult ) ;
99+ }
100+
101+ [ Test ]
102+ public async Task Mask_BroadcastAddress_ShouldReturnNetworkAddress ( )
103+ {
104+ var ip = IPAddress . Parse ( "255.255.255.255" ) ;
105+ var mask = IPAddress . Parse ( "255.255.255.0" ) ;
106+ var expected = IPAddress . Parse ( "255.255.255.0" ) ;
107+
108+ var result = ip . Mask ( mask ) ;
109+
110+ await Assert . That ( result )
111+ . EqualTo ( expected ) ;
112+ }
113+
114+ [ Test ]
115+ public async Task Mask_ZeroAddress_ShouldReturnZeroAddress ( )
116+ {
117+ var ip = IPAddress . Parse ( "0.0.0.0" ) ;
118+ var mask = IPAddress . Parse ( "255.255.255.0" ) ;
119+ var expected = IPAddress . Parse ( "0.0.0.0" ) ;
120+
121+ var result = ip . Mask ( mask ) ;
122+
123+ await Assert . That ( result )
124+ . EqualTo ( expected ) ;
125+ }
126+
127+ [ Test ]
128+ public async Task Mask_IPv6_AllZerosMask_ShouldReturnAllZeros ( )
129+ {
130+ var ip = IPAddress . Parse ( "fe80::d503:4ee:3882:c586" ) ;
131+ var mask = IPAddress . Parse ( "::" ) ;
132+ var expected = IPAddress . Parse ( "::" ) ;
133+
134+ var result = ip . Mask ( mask ) ;
135+
136+ await Assert . That ( result )
137+ . EqualTo ( expected ) ;
138+ }
139+
140+ [ Test ]
141+ public async Task Mask_IPv6_AllOnesMask_ShouldReturnOriginalAddress ( )
142+ {
143+ var ip = IPAddress . Parse ( "fe80::d503:4ee:3882:c586" ) ;
144+ var mask = IPAddress . Parse ( "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" ) ;
145+
146+ var result = ip . Mask ( mask ) ;
147+
148+ await Assert . That ( result )
149+ . EqualTo ( ip ) ;
150+ }
151+
152+ [ Test ]
153+ public async Task Mask_IPv6_NonAlignedPrefixMask_ShouldMaskCorrectly ( )
154+ {
155+ var ip = IPAddress . Parse ( "2001:0db8:85a3:0000:0000:8a2e:0370:7334" ) ;
156+ var mask = IPAddress . Parse ( "ffff:ffff:ffff:ff00::" ) ;
157+ var expected = IPAddress . Parse ( "2001:0db8:85a3::" ) ;
158+
159+ var result = ip . Mask ( mask ) ;
160+
161+ await Assert . That ( result )
162+ . EqualTo ( expected ) ;
163+ }
164+
59165 [ Test ]
60166 [ Arguments ( "192.168.1.100" , "192.168.1.1" , "255.255.255.0" , true ) ]
61167 [ Arguments ( "192.168.2.100" , "192.168.1.1" , "255.255.255.0" , false ) ]
@@ -125,10 +231,156 @@ await Assert.That(result)
125231 }
126232 else
127233 {
128- Action act = ( ) => targetIPAddress . GetLocalAddressOnTheSameNetwork ( interfaces ) ;
129- await Assert . That ( act )
234+ await Assert . That ( ( ) => targetIPAddress . GetLocalAddressOnTheSameNetwork ( interfaces ) )
130235 . ThrowsExactly < InvalidOperationException > ( )
131236 . WithMessage ( "Failed to find local address" ) ;
132237 }
133238 }
239+
240+ // ===== New GetLocalAddressOnTheSameNetwork tests =====
241+
242+ private static Mock < UnicastIPAddressInformation > CreateIPv4UnicastMock ( string address , string mask )
243+ {
244+ var mock = new Mock < UnicastIPAddressInformation > ( ) ;
245+ mock . Setup ( m => m . Address ) . Returns ( IPAddress . Parse ( address ) ) ;
246+ mock . Setup ( m => m . IPv4Mask ) . Returns ( IPAddress . Parse ( mask ) ) ;
247+ return mock ;
248+ }
249+
250+ private static Mock < UnicastIPAddressInformation > CreateIPv6UnicastMock ( string address , int prefixLength )
251+ {
252+ var mock = new Mock < UnicastIPAddressInformation > ( ) ;
253+ mock . Setup ( m => m . Address ) . Returns ( IPAddress . Parse ( address ) ) ;
254+ mock . Setup ( m => m . PrefixLength ) . Returns ( prefixLength ) ;
255+ return mock ;
256+ }
257+
258+ private static IPInterfaceProperties CreateMockInterface ( params Mock < UnicastIPAddressInformation > [ ] unicastMocks )
259+ {
260+ var unicastList = unicastMocks . Select ( m => m . Object ) . ToList ( ) ;
261+ var unicastCollection = new Mock < UnicastIPAddressInformationCollection > ( ) ;
262+ unicastCollection . Setup ( m => m . GetEnumerator ( ) ) . Returns ( unicastList . GetEnumerator ) ;
263+ unicastCollection . Setup ( m => m . Count ) . Returns ( unicastList . Count ) ;
264+
265+ var mockProps = new Mock < IPInterfaceProperties > ( ) ;
266+ mockProps . Setup ( m => m . UnicastAddresses ) . Returns ( unicastCollection . Object ) ;
267+ return mockProps . Object ;
268+ }
269+
270+ [ Test ]
271+ public async Task GetLocalAddressOnTheSameNetwork_EmptyInterfaces_ShouldThrow ( )
272+ {
273+ var target = IPAddress . Parse ( "192.168.1.1" ) ;
274+ var interfaces = Array . Empty < IPInterfaceProperties > ( ) ;
275+
276+ await Assert . That ( ( ) => target . GetLocalAddressOnTheSameNetwork ( interfaces ) )
277+ . ThrowsExactly < InvalidOperationException > ( )
278+ . WithMessage ( "Failed to find local address" ) ;
279+ }
280+
281+ [ Test ]
282+ public async Task GetLocalAddressOnTheSameNetwork_InterfaceWithNoUnicastAddresses_ShouldThrow ( )
283+ {
284+ var target = IPAddress . Parse ( "192.168.1.1" ) ;
285+
286+ var unicastCollection = new Mock < UnicastIPAddressInformationCollection > ( ) ;
287+ var emptyList = new List < UnicastIPAddressInformation > ( ) ;
288+ unicastCollection . Setup ( m => m . GetEnumerator ( ) ) . Returns ( emptyList . GetEnumerator ) ;
289+ unicastCollection . Setup ( m => m . Count ) . Returns ( 0 ) ;
290+
291+ var mockProps = new Mock < IPInterfaceProperties > ( ) ;
292+ mockProps . Setup ( m => m . UnicastAddresses ) . Returns ( unicastCollection . Object ) ;
293+
294+ await Assert . That ( ( ) => target . GetLocalAddressOnTheSameNetwork ( [ mockProps . Object ] ) )
295+ . ThrowsExactly < InvalidOperationException > ( )
296+ . WithMessage ( "Failed to find local address" ) ;
297+ }
298+
299+ [ Test ]
300+ public async Task GetLocalAddressOnTheSameNetwork_MultipleIPv4Interfaces_ShouldFindMatchOnSecond ( )
301+ {
302+ var target = IPAddress . Parse ( "10.0.0.50" ) ;
303+ var iface1 = CreateMockInterface ( CreateIPv4UnicastMock ( "192.168.1.100" , "255.255.255.0" ) ) ;
304+ var iface2 = CreateMockInterface ( CreateIPv4UnicastMock ( "10.0.0.1" , "255.255.255.0" ) ) ;
305+
306+ var result = target . GetLocalAddressOnTheSameNetwork ( [ iface1 , iface2 ] ) ;
307+
308+ await Assert . That ( result )
309+ . EqualTo ( IPAddress . Parse ( "10.0.0.1" ) ) ;
310+ }
311+
312+ [ Test ]
313+ public async Task GetLocalAddressOnTheSameNetwork_MultipleUnicastAddresses_ShouldFindMatchOnSecond ( )
314+ {
315+ var target = IPAddress . Parse ( "10.0.0.50" ) ;
316+ var iface = CreateMockInterface (
317+ CreateIPv4UnicastMock ( "192.168.1.100" , "255.255.255.0" ) ,
318+ CreateIPv4UnicastMock ( "10.0.0.1" , "255.255.255.0" )
319+ ) ;
320+
321+ var result = target . GetLocalAddressOnTheSameNetwork ( [ iface ] ) ;
322+
323+ await Assert . That ( result )
324+ . EqualTo ( IPAddress . Parse ( "10.0.0.1" ) ) ;
325+ }
326+
327+ [ Test ]
328+ public async Task GetLocalIPv6AddressOnTheSameNetwork_NonByteAlignedPrefix_ShouldMatch ( )
329+ {
330+ // Prefix /10: fullBytes=1, remainingBits=2
331+ // Both: byte[0]=0x20 (match), remaining mask=0xC0: byte[1] 0x01 & 0xC0 = 0x00 for both → match
332+ var target = IPAddress . Parse ( "2001:db8::1" ) ;
333+ var iface = CreateMockInterface ( CreateIPv6UnicastMock ( "2001:db8::2" , 10 ) ) ;
334+
335+ var result = target . GetLocalAddressOnTheSameNetwork ( [ iface ] ) ;
336+
337+ await Assert . That ( result )
338+ . EqualTo ( IPAddress . Parse ( "2001:db8::2" ) ) ;
339+ }
340+
341+ [ Test ]
342+ public async Task GetLocalIPv6AddressOnTheSameNetwork_NonByteAlignedPrefix_FullBytesMismatch_ShouldThrow ( )
343+ {
344+ // Prefix /10: fullBytes=1, remainingBits=2
345+ // byte[0]: 0x20 vs 0xfe → full byte mismatch → isMatch=false
346+ var target = IPAddress . Parse ( "2001:db8::1" ) ;
347+ var iface = CreateMockInterface ( CreateIPv6UnicastMock ( "fe80::1" , 10 ) ) ;
348+
349+ await Assert . That ( ( ) => target . GetLocalAddressOnTheSameNetwork ( [ iface ] ) )
350+ . ThrowsExactly < InvalidOperationException > ( )
351+ . WithMessage ( "Failed to find local address" ) ;
352+ }
353+
354+ [ Test ]
355+ public async Task GetLocalAddressOnTheSameNetwork_IPv4TargetWithIPv6Interface_ShouldThrow ( )
356+ {
357+ // IPv4 target: first if (InterNetwork && InterNetwork) → false (interface is IPv6)
358+ // else if (InterNetworkV6) → false (self is IPv4)
359+ // → no match
360+ var target = IPAddress . Parse ( "192.168.1.1" ) ;
361+ var iface = CreateMockInterface ( CreateIPv6UnicastMock ( "fe80::1" , 64 ) ) ;
362+
363+ await Assert . That ( ( ) => target . GetLocalAddressOnTheSameNetwork ( [ iface ] ) )
364+ . ThrowsExactly < InvalidOperationException > ( )
365+ . WithMessage ( "Failed to find local address" ) ;
366+ }
367+
368+ [ Test ]
369+ public async Task GetLocalAddressOnTheSameNetwork_IPv6TargetWithIPv4Interface_ShouldThrow ( )
370+ {
371+ // IPv6 target with IPv4 interface: enters else-if (self is IPv6),
372+ // compares IPv4 bytes (4) with IPv6 bytes (16) → mismatch on full bytes
373+ var target = IPAddress . Parse ( "fe80::1" ) ;
374+
375+ var mockUnicast = new Mock < UnicastIPAddressInformation > ( ) ;
376+ mockUnicast . Setup ( m => m . Address ) . Returns ( IPAddress . Parse ( "192.168.1.100" ) ) ;
377+ mockUnicast . Setup ( m => m . IPv4Mask ) . Returns ( IPAddress . Parse ( "255.255.255.0" ) ) ;
378+ mockUnicast . Setup ( m => m . PrefixLength ) . Returns ( 24 ) ;
379+
380+ var iface = CreateMockInterface ( mockUnicast ) ;
381+
382+ await Assert . That ( ( ) => target . GetLocalAddressOnTheSameNetwork ( [ iface ] ) )
383+ . ThrowsExactly < InvalidOperationException > ( )
384+ . WithMessage ( "Failed to find local address" ) ;
385+ }
134386}
0 commit comments