Larger major change updates - #75
Open
rheone wants to merge 70 commits into
Open
Conversation
## Breaking Changes ### SubnetUtilities static fields are now readonly `SubnetUtilities.PrivateIPAddressRangesList` and `LinkLocalIPAddressRangesList` are now declared `readonly`. Any code that reassigned these field references (e.g. `SubnetUtilities.PrivateIPAddressRangesList = myList`) will no longer compile. The `IReadOnlyList<Subnet>` type already prevented content mutation; `readonly` now also prevents reference replacement. ## Bug Fixes ### AbstractIPAddressRange.Overlaps — symmetry restored `Overlaps(IIPAddressRange)` previously returned `false` when the argument range was wholly contained inside `this`. Added `addressRange.Contains(this)` to the check so the method is symmetric: `A.Overlaps(B) == B.Overlaps(A)` in all cases. Bidirectional and Subnet-typed symmetry regression tests added. ### AbstractIPAddressRange — ContainsAnyPrivateAddresses / ContainsAllPublicAddresses All four `ContainsAny/AllPrivate/PublicAddresses` methods previously used an endpoint heuristic that produced wrong results when both endpoints of a range were public but the interior spanned a private subnet (e.g. 11.0.0.0-173.0.0.0 spans 172.16.0.0/12). All four methods now independently use range-overlap detection against `PrivateIPAddressRangesList`. Regression tests for the spanning-block case and cross-method inverse-assertion tests added. ### IPAddressRange.TryExcludeAll — boundary guards at family min/max All four unguarded `Increment`/`Increment(-1)` call sites are now guarded with `IsAtMax`/`IsAtMin`. When an exclusion ends at the family maximum (e.g. 255.255.255.255) the method returns `(true, leading segment)` or `(true, [])` instead of throwing `InvalidOperationException`. When an exclusion starts at the family minimum the trailing-segment path is guarded symmetrically. Boundary behavior is documented in XML remarks. Regression tests for IPv4 and IPv6 family-max/min exclusion cases added. ### MacAddress.IsUnusable — implementation corrected `IsUnusable` previously used `Any(b != 0)` and returned `true` for almost every real MAC address — the logical inverse of the documented contract. Fixed to `All(b == 0)`: returns `true` only when all three OUI bytes are 0x00. All four existing test cases have their expected values corrected with comments noting the intentional behavioral fix. ## Additional Coverage - `Overlaps(Subnet)` typed override: explicit bidirectional symmetry test - `Overlaps(IIPAddressRange)` called from a Subnet context: symmetry test - `TryExcludeAll`: IPv4 and IPv6 family-max and family-min boundary tests - Private/public spanning-block regression + ContainsAnyPrivate <-> ContainsAllPublic inverse assertions
Addressing targeting issues with specific lang version change Adds RFC notes Cleans up ToBase85String Dropped (most) obsolete items; including MAC Address type Cleaned up ToDottedQuadString method Using modern collection conventions
fixing bad exceptions - adding tests too using matching and switch where possible Touches serialization Addresses warnings
Adds smoke test for verifying other targeted consumers
H2: Unify GetHashCode at AbstractIPAddressRange (Head+Tail hash) H3: Add enumeration warning remarks to IIPAddressRange H4: Fix Deconstruct doc (non IPv6 -> IPv6) M9: Fix XML doc grammar in IIPAddressRange/AbstractIPAddressRange M15: Simplify ContainsAnyPublicAddresses implementation + add examples L1: Simplify Overlaps, fix Touches Equals call L6: Add typed Contains/Overlaps overloads to IPAddressRange L10: Fix typos (Ovelap->Overlap, metods->methods, Goldilocks comment) L11: Remove bare TODO (superseded by H3) L12: Make AddressTuple private protected I1: Fix IEnumerable buffer-reuse comment
Add enumeration safety cap via MaxEnumerationExponent; refactor TryIPv6FromPartial Add MaxEnumerationExponent (default 12, cap 4096 addresses) to AbstractIPAddressRange, propagated through Subnet constructors, SubnetUtilities.FewestConsecutiveSubnetsFor, and TryIPv4FromPartial/ TryIPv6FromPartial. Enumeration now throws InvalidOperationException when the limit is exceeded, preventing accidental iteration over enormous ranges. Introduce ToIPAddresses() as the primary enumeration method on IIPAddressRange; the inherited GetEnumerator() is marked [Obsolete] and delegates to it. Decompose TryIPv6FromPartial into 7 focused private helpers (NormalizeIPv6PartialInput, IsPotentiallyValidIPv6Partial, ContainsOnlyHexChars, TryParseExactCidr, TryParseBareHextet, TryParseAmbiguousHextets, ExpandHextetPermutations) with thorough XML documentation. Reverse permutation output order so most-specific subnets appear first. Add serialization versioning to Subnet (version 2, stores MaxEnumerationExponent) with backward-compatible fallback for previously serialized instances. Add Subnet.GetHashCode() and AddressTuple.GetHashCode() (moved from base class). Apply expression-bodied members and switch expressions throughout.
added 29 commits
June 28, 2026 13:14
…CodeQL noise - Centralize all NuGet package versions in `src/Directory.Packages.props` and strip `Version` attributes from individual `.csproj` files (Arcus, Arcus.Tests, Arcus.Benchmarks). Regenerate lock files to v2 format with CPM-aware dependency resolution and win-x86 runtime sections. - Add 7 new test methods across BigEndianBitWrapper (TryAdd with long.MinValue delta, DebuggerDisplay for IPv4/IPv6), IPAddressRange (self-reference and unrelated-type Equals), and SubnetUtilities (range at IPv4 max, null mid-sequence). - Add 11 inline CodeQL suppression comments for intentional test patterns: self-comparison, unrelated-type Equals, null extension invocation, upcast for overload resolution, and loop-local StringBuilder. - Add `src/.runsettings` with xUnit CPU-parallelization and XPlat Code Coverage configuration (excluding test fw and benchmark assemblies). - Update README badges: replace GitHub Release/Tag/old-license entries with NuGet, OpenSSF Scorecard, refreshed build, and underscore-separated targets badge for Shields.io compatibility. - Add `allow-unknown-licenses: true` in dependency-review workflow with explanatory comments for false-positive license warnings.
…ssemblies, and migrate suppression comments - Add `paths-ignore` to CodeQL config to exclude test and benchmark projects, reducing noise from intentional test patterns - Add `Microsoft.NETFramework.ReferenceAssemblies` to central package management in `Directory.Packages.props` and reference it conditionally for `net48` targets in both `.csproj` files - Prune `win-x86` runtime-specific sections from `packages.lock.json` that are no longer needed after CPM adoption - Migrate suppression comments from `// CodeQL [...]` syntax to `// lgtm[...]` across 6 test files for consistent tooling
- X1: Add <Nullable>annotations</Nullable> to Arcus.csproj - X3: Update Directory.Build.props metadata (Sandia/Apache-2.0/sandialabs) - X4: Remove explicit LangVersion from Arcus.csproj (inherits from Directory.Build.props) - X7: Replace #if NETSTANDARD2_0 with #if !NET7_0_OR_GREATER for GeneratedRegex - X8: Add XML doc comment to InternalsVisibleTo items - .gitignore: Add /AGENTS.md and /CLAUDE.md
- B1: Replace Debug.Assert with ArgumentException in ToBytes(Span<byte>) - B2: Guard MaxValueForWidth/MaxHiLoForWidth for ByteWidth=0 - B3: ByteWidth validation in & and | - B4: Mask & and | results to ByteWidth (consistent with ~) - B5: Implement ^ (XOR) operator with ByteWidth validation + masking - B6: ByteWidth validation in Subtract - X9: Document unchecked arithmetic dependency in class XML doc - Add Debug.Assert at entry of Subtract, CompareTo, operators - Add 13 new tests in Safety.cs and Operators.cs
- A2: Clamp MaxEnumerationExponent to address family bit width (32 for IPv4, 128 for IPv6) to prevent invalid cap values - A3: Add null guards to AddressTuple.Equals and GetHashCode to prevent NRE on default(struct) instances - A4: Add recursion warning XML doc to Overlaps method - A5: Use long counter in EnumerateCore when maxCount fits in long, avoiding BigInteger heap allocation per yielded address - A6: Cache BigInteger.One << MaxEnumerationExponent in _maxCount field to avoid recomputation on each ToIPAddresses call - A7: Remove dead IPAddressMath.Min clamping in EnumerateCore (Tail can never exceed address family max) - A10: Document private subnet containment assumption on ContainsAnyPublicAddresses
S1 - Parse whitespace: Trim() in Parse/TryParse S2 - Contains delegation: delegate to base.Contains S5 - Negative prefix: reject negative routing prefixes S6 - Typo fix: 'an' -> 'a' in exception message S7 - Operators: fix double-evaluation in <=/>= (now !(>)/!(<)) S8 - Overlaps doc: add CIDR containment property doc S9 - IPv6 netmask skip: bypass NormalizeAndCreateNetMask for IPv6 S10 - Single-IP exponent: add Subnet.FromIPAddress factory S11 - Usables doc: (documentation-only, no code change) 14 new tests, 9398/9398 passing on net8.0/net10.0, 0 warnings
- GetObjectData writes NetworkPrefixAddress (Head) instead of BroadcastAddress (Tail) - DeserializeSubnet validates SerVersion == 3 (throws SerializationException otherwise) - MaxEnumerationExponent read directly (no try/catch fallback) - Add SerializationFormatVersion constant (3) - Add test-only DeserializeFromInfo internal helper - Add #if NET48 tests for field name, version, round-trip S3 (MEDIUM). X2 (TFM gate expansion) deferred - net48-only on Linux, BinaryFormatter deprecated on net8.0+.
- Add Equals(object) + GetHashCode() to AbstractIPAddressRange (Head+Tail) - Subnet.Equals(Subnet) uses Head+Tail; GetHashCode delegates to base - IPAddressRange.GetHashCode delegates to base - Remove DefaultIIPAddressRangeComparer dependency from Subnet equality - Add unification test (Subnet vs IPAddressRange hash match) Breaking: Subnet.GetHashCode changes (Head+RoutingPrefix -> Head+Tail). R1 (MEDIUM).
- C3: remove dead null-coalescing in DefaultIPAddressComparer ctor - C4: NetmaskToCidrRoutePrefix throws ArgumentException (was InvalidOperationException) - C5: ParseFromHexString handles all-zero input (preserve a single 0) - C6: document Increment delta is 64-bit (no BigInteger overload) - C7: IsGreaterThanOrEqualTo/IsLessThanOrEqualTo consistent null handling (single-null false; both-null true via ReferenceEquals) - C8: Debug.Assert non-null Head in DefaultIIPAddressRangeComparer - C9: PrivateIPAddressRangesList/LinkLocalIPAddressRangesList lazily initialized (Lazy-backed properties) - C10: ValidAddressFamilies uses array (was List.AsReadOnly) - C11: ToDottedQuadString graceful fallback on TryWriteBytes short write 12 new tests; 9414/9414 passing on net8.0/net10.0; 0 warnings. C4 breaking: exception type changes for invalid netmask (LOW).
- C2: FewestConsecutiveSubnetsFor FilledSubnets converted from recursive Concat chain to yield-return iterator (removes O(N) Concat wrapper depth, O(N^2) enumeration pressure). 2 sentinel tests guard count + contiguous coverage. - S4: NormalizeAndCreateNetMask(IPAddress, IPAddress) reuses fetched head bytes; avoids redundant GetAddressBytes allocation on the IPv4 hot path. - A9: document Length is computed once and cached (audit-only). - B8: document ToBytes() allocation and recommend Span overload on net8+ (audit-only). C1 (Span-based BigEndianBitWrapper.FromBytes + TryWriteBytes in comparer/math) deferred - meaningful API addition with cross-TFM considerations; tracked separately. 9416/9416 passing on net8.0/net10.0; 0 warnings.
- A1: document why AbstractIPAddressRange is abstract (enforces concrete subtype) - S11: document /31 usable hosts follows RFC 950 (RFC 3021 note) - B7: document FromBytes(byte[], int) empty array -> all-zero wrapper - B9: document non-generic IComparable intentionally not implemented (internal type) - A8: document [Obsolete] design decision on GetEnumerator (prefer ToIPAddresses()) - X5: generated regex types in Arcus.xml accepted (source-generator limitation) - X6: net9/net10 API audit documented (SearchValues, OrderedDictionary) - no code change Doc-only batch. 9416/9416 passing on net8.0/net10.0; 0 warnings.
- Gate `[SkipLocalsInit]` on `NET9_0_OR_GREATER` (not NET8_0) to match actual runtime support in `BigEndianBitWrapper` - Add null-check, address-family, and prefix-range validation in `Subnet.DeserializeSubnet`; suppress S5766 downstream - Change `ValidAddressFamilies` from `IReadOnlyCollection` to `AddressFamily[]` for collection-expression syntax - Suppress S1313 on intentional private/link-local IP literals - Bump SonarAnalyzer 10.27 → 10.28 - Pin `LangVersion` to `14.0` (was `latest`) - Convert `FewestConsecutiveSubnetsFor` theory data from `object[]` to `TheoryDataRow`; extract inline arrays into named static fields; replace `[^1]` with `.Count - 1` for netstandard2.0 compat - Fix `Count` → `Length` assertion on array in IPAddressUtilitiesTests
removed AsyncFixer (there is no async code)
The separate coverage-report job could not post PR comments from fork PRs because pull_request events from forks don't receive a write token. Moving the coverage report generation and comment posting into the build job (which already runs on ubuntu-latest with correct permissions) avoids the cross-workflow artifact and token issues entirely.
The coverage comment failed on fork PRs because pull_request events from forks only get a read-only GITHUB_TOKEN. Inlining the comment into the build job didn't help since it still runs in the pull_request context. Fix by splitting into a pull_request_target workflow that runs in the base repo context with full write permissions. The new workflow: - Finds the build run for the PR's head SHA via the API - Downloads the coverage artifact from that run - Generates the report and posts the sticky comment The build.yml is restored to its original form (without coverage comment steps or the old coverage-report job).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Work
Added
net10.0) target frameworkIIPAddressRange.ToIPAddresses()-- safe enumeration method capped atMaxEnumerationExponentMaxEnumerationExponentproperty on all range types (default 12 = 4096 addresses)SubnetUtilities.FewestConsecutiveSubnetsForoverload withmaxEnumerationExponentparameterSubnet,IPAddressRange,AbstractIPAddressRange,BigEndianBitWrapper)Changed
SubnetUtilities.PrivateIPAddressRangesListandLinkLocalIPAddressRangesListare nowreadonly(lazily initialized)AbstractIPAddressRange.Overlaps-- symmetry restored (both directions now returntrue)IPAddressRange.TryExcludeAll-- boundary guards at family min/max (no longer throws)Subnetparsing andIPAddressUtilitiesparsing use[GeneratedRegex]where availableDirectory.Packages.propsInvalidOperationExceptiontoArgumentExceptionwhere appropriate)Removed
MacAddresstype (was[Obsolete]in v3.x) - replacement coming soon-ish in a new packageDefaultIPAddressRangeComparer(useDefaultIIPAddressRangeComparer)Fixed
AbstractIPAddressRange.Overlapsasymmetry (inner.Overlaps(outer) returnedfalse)ContainsAnyPrivateAddresses/ContainsAllPublicAddressesendpoint heuristic producing incorrect results for ranges spanning private subnetsIPAddressRange.TryExcludeAllthrowingInvalidOperationExceptionat family max addressDeprecated (will be removed in future major version)
GetEnumerator()on any range type -- useToIPAddresses()insteadIIPAddressRangeimplementingIEnumerable<IPAddress>-- useToIPAddresses()explicitlySubnet.TryIPv6FromPartial(string, out IEnumerable<Subnet>)-- to be replaced / improved by more explicit methodsTesting
Subnettests split into partial files: factory,IComparable,IEquatable,ISerializable, operatorsIPAddressRangetests split into partial files: factory,IComparable,IEquatable,ISerializable, operatorsAbstractIPAddressRangetests split into:IEnumerable,IFormattable,IIPAddressRangesmoketests/) validating the packed NuGet package on real runtimesInfrastructure
src/Arcus.Benchmarks/) with BenchmarkDotNet for all major types.slnxsolution format (replaces legacy.sln)CONTRIBUTING.mdwith build, test, and PR guidance