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

lduchosal / ipnetwork / 809

17 Aug 2025 08:25AM UTC coverage: 93.223% (-1.0%) from 94.226%
809

push

appveyor

web-flow
Chore: cleanup, breaking changes, enum, tryparse, exception, static ListIPAddress (#363)

* Chore: huge cleanup, enum, tryparse, exception, static ListIPAddress, important changes : IPNetwork comparison and sort order have change to reflect expected behavoir
* Fix: obsolete enums
* Fix: network sorting and member comparison
* Chore: upgrade version number 3.3

1802 of 1933 relevant lines covered (93.22%)

726934.37 hits per line

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

95.83
/src/System.Net.IPNetwork/CidrNetworkAware.cs
1
using System.Net.Sockets;
2

3
namespace System.Net;
4

5
/// <summary>
6
/// If your CidrGuess is “network-aware” based only on what humans usually encode in the textual address, a good heuristic is:
7
/// 
8
/// IPv4 (dotted-quad)
9
///    • Treat trailing 0s as “network bits” and trailing 255s as a “wildcard” hint for the same boundary.
10
///    • Otherwise, fall back to /32 (no safe aggregation from the string alone).
11
///    • Special case: 0.0.0.0 (or 255.255.255.255) → /0.
12
///
13
/// Rule of thumb
14
///  Ends with .0 → /24
15
///  Ends with .0.0 or .255.255 → /16
16
///  Ends with .0.0.0 or .255.255.255 → /8
17
///  Else → /32
18
///
19
/// Matches your examples
20
///  Parse("192.0.43.8") → /32
21
///  Parse("192.0.43.0") → /24
22
///  Parse("192.43.0.0") → /16
23
///  Parse("192.0.43.255") → /24 (wildcard hint)
24
///  Parse("192.43.255.255") → /16 (wildcard hint)
25
///
26
/// So: in a network-aware context like this, you generally don’t emit /25, /26, etc.,
27
/// because there’s no reliable visual cue for those in dotted-quad—stick to /32, /24, /16, /8, /0.
28
///
29
/// IPv6 (colon-hex)
30
///  IPv6 is grouped by hextets (16-bit chunks), so mirror the idea at 16-bit boundaries.
31
///  Use trailing :0000 hextets as “network bits”.
32
///  Otherwise, fall back to /128.
33
///  Note: operationally, /64 is the standard host subnet size, but you should still infer from the string, not assumptions.
34
///
35
/// Rule of thumb
36
///  Ends with :0000 → /112
37
///  Ends with :0000:0000 → /96
38
///  Ends with three trailing :0000 → /80
39
///  …
40
///  Ends with four trailing :0000 → /64
41
///  Else → /128
42
///
43
/// Examples
44
///  2001:db8:1:2:3:4:5:6 → /128
45
///  2001:db8:1:2:3:4:5:0000 → /112
46
///  2001:db8:1:2:3:4:0000:0000 → /96
47
///  2001:db8:1:2:3:0000:0000:0000 → /80
48
///  2001:db8:1:2:0000:0000:0000:0000 → /64
49
///
50
/// TL;DR
51
///  IPv4: stick to /32, /24, /16, /8, /0 based on trailing .0/.255; otherwise /32.
52
///  IPv6: infer /128, /112, /96, /80, /64, … based on trailing :0000 groups; otherwise /128.
53
/// </summary>
54
public sealed class CidrNetworkAware : ICidrGuess
55
{
56
    /// <summary>
57
    /// Tries to guess a network-aware CIDR prefix length from a textual IP address.
58
    /// IPv4: honors trailing 0s (network) and trailing 255s (wildcard hint) at octet boundaries.
59
    /// IPv6: honors trailing :0000 at hextet (16-bit) boundaries. Optional trailing :ffff wildcard heuristic is off by default.
60
    /// </summary>
61
    /// <param name="ip">IP address as string (no slash). Example: "192.0.43.0" or "2001:db8::".</param>
62
    /// <param name="cidr">Guessed CIDR (0..32 for IPv4, 0..128 for IPv6).</param>
63
    /// <returns>true if parsed and guessed; false if input is not a valid IP address.</returns>
64
    public bool TryGuessCidr(string ip, out byte cidr)
65
    {
34✔
66
        cidr = 0;
34✔
67
        if (string.IsNullOrWhiteSpace(ip))
34✔
68
            return false;
1✔
69

70
        // Reject if user passed a slash - this API expects a plain address.
71
        // (You can relax this if you want to honor an explicitly supplied prefix.)
72
        if (ip.Contains("/"))
33✔
73
            return false;
×
74

75
        if (!IPAddress.TryParse(ip.Trim(), out var ipAddress))
33✔
76
            return false;
3✔
77

78
        switch (ipAddress.AddressFamily)
30✔
79
        {
80
            case AddressFamily.InterNetwork:
81
                cidr = GuessIpv4(ipAddress);
16✔
82
                return true;
16✔
83
            case AddressFamily.InterNetworkV6:
84
                cidr = GuessIpv6(ipAddress);
14✔
85
                return true;
14✔
86
            default:
87
                return false;
×
88
        }
89
    }
34✔
90

91
    private static byte GuessIpv4(IPAddress ip)
92
    {
16✔
93
        byte[] b = ip.GetAddressBytes(); // length 4
16✔
94

95
        // /0 if all 0s or all 255s
96
        bool allZero = b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0;
16✔
97
        bool allFf   = b[0] == 255 && b[1] == 255 && b[2] == 255 && b[3] == 255;
16✔
98
        if (allZero || allFf) return 0;
18✔
99

100
        // Network-aware boundaries via trailing zeros (network) OR trailing 255 (wildcard hint)
101
        bool last3Zero = b[1] == 0   && b[2] == 0   && b[3] == 0;
14✔
102
        bool last2Zero =              b[2] == 0     && b[3] == 0;
14✔
103
        bool last1Zero =                               b[3] == 0;
14✔
104

105
        bool last3Ff   = b[1] == 255 && b[2] == 255 && b[3] == 255;
14✔
106
        bool last2Ff   =              b[2] == 255   && b[3] == 255;
14✔
107
        bool last1Ff   =                               b[3] == 255;
14✔
108

109
        if (last3Zero || last3Ff) return 8;
21✔
110
        if (last2Zero || last2Ff) return 16;
11✔
111
        if (last1Zero || last1Ff) return 24;
5✔
112

113
        // Otherwise host address
114
        return 32;
1✔
115
    }
16✔
116

117
    private static byte GuessIpv6(IPAddress ip)
118
    {
14✔
119
        byte[] b = ip.GetAddressBytes(); // length 16
14✔
120

121
        // Count trailing zero hextets (pairs of bytes == 0x0000)
122
        int trailingZeroHextets = CountTrailingHextets(b, 0x0000);
14✔
123
        if (trailingZeroHextets == 8) return 0; // all zero address '::'
16✔
124
        if (trailingZeroHextets > 0)  return (byte)(128 - 16 * trailingZeroHextets);
22✔
125

126
        // Otherwise host address
127
        return 128;
2✔
128
    }
14✔
129

130
    private static int CountTrailingHextets(byte[] bytes, ushort value)
131
    {
14✔
132
        // bytes.Length must be 16 for IPv6
133
        int count = 0;
14✔
134
        for (int i = bytes.Length - 2; i >= 0; i -= 2)
148✔
135
        {
72✔
136
            ushort hextet = (ushort)((bytes[i] << 8) | bytes[i + 1]);
72✔
137
            if (hextet == value) count++;
132✔
138
            else break;
12✔
139
        }
60✔
140
        return count;
14✔
141
    }
14✔
142
}
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