diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..641bc19 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "0.30.6", + "commands": [ + "dotnet-csharpier" + ], + "rollForward": false + }, + "dotnet-outdated-tool": { + "version": "4.6.7", + "commands": [ + "dotnet-outdated" + ], + "rollForward": false + }, + "husky": { + "version": "0.7.2", + "commands": [ + "husky" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..34176d3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,37 @@ +# Root .editorconfig as of 2025-02-03 + +root = true + +# All files +[*] +charset = utf-8-bom +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +spelling_languages = en-us +spelling_checkable_types = strings,identifiers,comments +spelling_error_severity = information +spelling_exclusion_path = .\dictionary.dic + +# json +[*.json] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Bash Files +[*.sh] +end_of_line = lf + +# Batch Files +[*.{cmd,bat,ps1}] +end_of_line = crlf + +# XML +[*.xml] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes index 1e10ade..d6ad5f9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -23,5 +23,4 @@ # intervention with every merge. To do so, just uncomment the entries below ############################################################################### *.sln merge=binary -*.csproj merge=binary -#*.vbproj merge=binary \ No newline at end of file +*.csproj merge=binary \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dbcaff8..7218e65 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,41 +1,26 @@ --- -name: Bug report -about: Create a bug report to help us improve +name: Bug Report +about: Create a report to help us improve title: "[BUG]" labels: bug assignees: '' --- -# Describe the bug +# Bug Report -A clear and concise description of what the bug is. -If available include the exception that is thrown, including the type and the included message and stack trace. +## Describe the bug -# Expected behavior +A clear and concise description of what the bug is. -A clear and concise description of what you expected to happen. - -# Example of how to Reproduce - -Provide the steps to reproduce the behavior by including a code example either inline or via a [Gist](https://gist.github.com/) link. -If a code example is not possible, or too complex for the context of the issue describe in detail - -# Code Information +## How to Reproduce - - Runtime Information [e.g. netcore2.0, netstandard2.1] - - SDK / Runtime Version [e.g. Core 2.2 SDK 2.2.402] - - Language Version [e.g. C# 7.3, F# 4.6] - - Operating System [e.g. Linux, Windows 10, Mac] +Steps to reproduce the behavior. -# Proposed solutions +## Expected Behavior -Do you have a solution that may fix this issue? - -# Can you help? - -Let us know if you are interested in helping, and in what capacity. e.g.: I want to write code and create a pull request, I can provide test cases, I have a suggestion on how it can be done, etc. +A clear and concise description of what you expected to happen. -# Additional context +## Additional context Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/documentation-needed.md b/.github/ISSUE_TEMPLATE/documentation-needed.md deleted file mode 100644 index a5c4436..0000000 --- a/.github/ISSUE_TEMPLATE/documentation-needed.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: Documentation Needed -about: Proposal for additional documentation or examples of code usage -title: "[Doc]" -labels: documentation -assignees: '' - ---- - -# Description - -A clear and concise description of what is missing in documentation, or how documentation may be improved. -What failings have occurred due to this lack of information? - -# Impact - -Description of the current or possible impact of this addition. - -# Proposed solutions - -Do you have a solution that may fix this issue? - -# Can you help? - -Let us know if you are interested in helping, and in what capacity. e.g.: I want to write code and create a pull request, I can provide test cases, I have a suggestion on how it can be done, etc. - - -# Additional context - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 87aafc6..50bf04e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,33 +1,30 @@ --- -name: Feature request +name: Feature Request about: Suggest an idea for this project -title: "[Feature Request]" +title: "[REQUEST]" labels: enhancement assignees: '' --- -# Description +# Feature Request -A clear and concise description of what the desired feature is. +## Is your feature request related to missing applicable functionality? -Is your feature request related to an existing problem? Please describe it and/or provide an issue number. +A clear and concise description of what the missing functionality. Please include RFCs or references as appropriate. -# Proposed solutions +## Proposed Solutions -Do you have a solution, what would you recommend to fix it? -If you don't have a solution what would you like the end result to be? +A clear and concise description of what you would like to see as a result. -# Can you help? - -Let us know if you are interested in helping, and in what capacity. e.g.: I want to write code and create a pull request, I can provide test cases, I have a suggestion on how it can be done, etc. +## Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. -# Describe alternatives you've considered +# Can you help? -Did you consider any alternatives solutions or features? -Did you have a workaround that met your needs? +Let us know if you are interested in helping, and in what capacity. e.g.: I want to write code and create a pull request, I can provide test cases, I have a suggestion on how it can be done, etc. -# Additional context +## Additional context -Add any other context about the problem here. +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/technical-debt.md b/.github/ISSUE_TEMPLATE/technical-debt.md deleted file mode 100644 index a0bc513..0000000 --- a/.github/ISSUE_TEMPLATE/technical-debt.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Technical Debt -about: A better solution for an already solved problem -title: "[TechDebt]" -labels: TechDebt -assignees: '' - ---- - -# Description - -A clear and concise description of what the technical debt is, and the reason it should be improved. - -# Impact - -Description of the current or possible impact of this tech debt. Include both the positive and negative. - -## Observations - -How would this change make things better? - -# Proposed solutions - -Do you have a solution that may fix this issue? - -# Can you help? - -Let us know if you are interested in helping, and in what capacity. e.g.: I want to write code and create a pull request, I can provide test cases, I have a suggestion on how it can be done, etc. - -## Related files / Classes - - - [files](url) - -# Additional context - -Add any other context about the problem here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c29e02b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +# Dependabot Configuration for .NET Project + +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/src" + schedule: + interval: "weekly" + day: "sunday" + allow: + - dependency-type: direct + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-patch" + commit-message: + prefix: "Dependabot package update" + open-pull-requests-limit: 5 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 934e835..6576b0f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,19 +1,26 @@ -Hello, and welcome! Thanks you for your contribution to **Arcus**. +# Arcus Pull Request + +Thank you for your contribution to **Arcus**. Please answer the following questions regarding your pull request: + +--- 1. Please let us know about the contribution you are making - [ ] Does this contribution update documentation? - [ ] Does this contribution update code? - [ ] Does it fix a bug? - - [ ] Does it add new functionality? + - [ ] Does it add new functionality? -2. Assuming there is a code change in this contribution +1. Assuming there is a code change in this contribution - [ ] Have you included unit test(s) to verify the change? - [ ] Do all pre-existing unit tests pass as expected? - [ ] Does the change break existing functionality, if so have you explained why doing so is necessary? - - [ ] Have you listed the possible side-effects or negative impacts of the code change. + - [ ] Have you listed the possible side-effects or negative impacts of code changes. + +1. Put `closes #XXXX` in your comment, where `XXXX` is the the issue that your PR addresses. + + - If and issue does not yet exist, please create one before continuing -3. Put `closes #XXXX` in your comment, where XXXX is the the issue that your PR fixes. -4. Provide any other information that pertinent to the PR you're making. -5. Click "Create pull request". +1. Provide any other information that pertinent to the PR you're making. +1. Submit your Pull Request Thanks again, we'll review your PR and and act accordingly. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..06d4fe4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +# .NET Build and Test Workflow + +name: Build and Test + +permissions: + contents: read + pull-requests: write + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v4 + with: + # .net 4.8.x is included by default in windows-latest + dotnet-version: | + 8.0.x + 9.0.x + + - name: Restore dotnet tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore ./src + + - name: Build + run: dotnet build ./src --no-restore -p:VersionFromCI="0.0.0-cibuild" + + - name: Test + run: dotnet test ./src --no-build --verbosity normal diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..8981c84 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,53 @@ +# .NET Publish semver tag Workflow + +name: Publish .NET Package + +permissions: + contents: read + packages: write + pull-requests: write + +on: + push: + tags: + - "v*.*.*" + - "v*.*.*-*" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Extract version + id: extract_version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV + + - name: Restore dotnet tools + run: dotnet tool restore + + - name: Restore dependencies + run: dotnet restore ./src + + - name: Build + run: dotnet build ./src --no-restore --configuration Release /p:VersionFromCI=${{ env.VERSION }} + + - name: Pack + run: dotnet pack ./src/Gulliver/Gulliver.csproj --configuration Release --no-build --output ./packages /p:VersionFromCI=${{ env.VERSION }} + + - name: Publish to GitHub Packages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: dotnet nuget push ./packages/*.nupkg --source "https://nuget.pkg.github.com/sandialabs/index.json" --api-key $GITHUB_TOKEN --skip-duplicate + + - name: Publish to nuget.org + env: + NUGET_ORG_APIKEY: ${{ secrets.NUGET_ORG_APIKEY }} + run: dotnet nuget push ./packages/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key $NUGET_ORG_APIKEY --skip-duplicate diff --git a/.gitignore b/.gitignore index 0b9ce33..7e2820d 100644 --- a/.gitignore +++ b/.gitignore @@ -196,5 +196,9 @@ ehthumbs_vista.db # Windows shortcuts *.lnk +# Coderush +**/.cr/* + ### Read the Docs ### -docs/_build \ No newline at end of file +docs/_build + diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..818853f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +dotnet husky run diff --git a/.husky/task-runner.json b/.husky/task-runner.json new file mode 100644 index 0000000..7e79946 --- /dev/null +++ b/.husky/task-runner.json @@ -0,0 +1,25 @@ +{ + "tasks": [ + { + "name": "style", + "group": "pre-commit", + "command": "dotnet", + "args": ["format", "src", "style", "--no-restore", "--include", "${staged}"], + "include": ["src/**/*.cs"] + }, + { + "name": "analyzers", + "group": "pre-commit", + "command": "dotnet", + "args": ["format", "src", "analyzers", "--no-restore", "--include", "${staged}"], + "include": ["src/**/*.cs"] + }, + { + "name": "csharpier", + "group": "pre-commit", + "command": "dotnet", + "args": ["csharpier", "--config-path", ".\\src\\.csharpierrc.json", "${staged}"], + "include": ["src/**/*.cs"] + } + ] +} diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..38f540f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,23 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +formats: + - pdf + - epub + +python: + install: + - requirements: docs/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index e6b1a3b..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,19 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -# Optionally build your docs in additional formats such as PDF and ePub -formats: all - -# Optionally set the version of Python and requirements required to build your docs -python: - version: 3.7 - install: - - requirements: docs/requirements.txt diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index eb242d7..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - // See https://github.com/psake/psake - // for more information on psake.ps1 - "version": "2.0.0", - "tasks": [{ - "label": "Clean", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' Clean", - "group": "build" - }, - { - "label": "Default", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' Default", - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "label": "Test", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' Test", - "group": { - "kind": "test", - "isDefault": true - } - }, - { - "label": "Build Debug", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' build_debug", - "group": "build" - }, - { - "label": "Build Release", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' build_release", - "group": "build" - }, - - { - "label": "Build Docs", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' build_docs", - "group": "build" - }, - { - "label": "Clean Docs", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' clean_docs", - "group": "build" - }, - - - { - "label": "Pack Debug", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' pack_debug", - "group": "build" - }, - { - "label": "Pack Release", - "type": "shell", - "command": "psake.cmd '.\\psake.ps1' pack_release", - "group": "build" - } - ], - -} \ No newline at end of file diff --git a/HUMANS.md b/HUMANS.md index bb7ed62..bcde8c3 100644 --- a/HUMANS.md +++ b/HUMANS.md @@ -1,6 +1,8 @@ +# Arcus + ```asciiart - /\ - / \ _ __ ___ _ _ ___ + /\ + / \ _ __ ___ _ _ ___ / /\ \ | '__/ __| | | / __| / ____ \| | | (__| |_| \__ \ /_/ \_\_| \___|\__,_|___/ @@ -9,10 +11,11 @@ Let us not forget that software is made for humans (for computers) by humans (on computers). Arcus is no different. Thank you all that have helped, be it by spreading the work of this library, sharing the viral ideas of opensource, provided comments, creating pull requests or even just adding to our usage count. ## Some of the humans involved include -* **Robert H. Engelhardt** - *Primary Developer, Source of Ideas Good and Bad* - [@rheone](https://twitter.com/rheone) -* **Andrew Steele** - *Review and Suggestions* - [@ahsteele](https://twitter.com/ahsteele) -* **Nick Bachicha** - *Git Wrangler and DevOps Extraordinaire* - [@nicksterx](https://twitter.com/nicksterx) + +- **Robert H. Engelhardt** - *Primary Developer, Source of Ideas Good and Bad* - [rheone](https://github.com/rheone) +- **Andrew Steele** - *Review and Suggestions* - [ahsteele](https://github.com/ahsteele) +- **Nick Bachicha** - *Git Wrangler and DevOps Extraordinaire* - [nicksterx](https://github.com/nicksterx) If you'd like to make a significant contribution, we'd appreciate your support and would be happy to add you to our list of involved humans. If you're not human, we'll find a nice heading for you too. -Arcus exists to be consumed, and all of us involved hoped it has in some way made your day a bit better. \ No newline at end of file +Arcus exists to be consumed, and all of us involved hoped it has in some way made your day a bit better. diff --git a/LICENSE b/LICENSE index 17161f2..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,3 @@ -Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/README.md b/README.md index 6191e83..1f55ed2 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,43 @@ # ![Arcus](src/Arcus/icon.png) Arcus -[![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Arcus?logo=nuget)](https://www.nuget.org/packages/Arcus/) -[![Documentation on ReadTheDocs](https://img.shields.io/badge/Read%20the%20Docs-Arcus-lightgrey?logo=read%20the%20docs)](https://arcus.readthedocs.io) -[![Apache 2.0 license](https://img.shields.io/github/license/sandialabs/arcus)](https://github.com/sandialabs/Arcus/blob/master/LICENSE) -[![.NetStandard 1.3](https://img.shields.io/badge/targets-.NETStandard%201.3-blueviolet)](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) -[![Join the chat at https://gitter.im/sandialabs/Arcus](https://badges.gitter.im/sandialabs/Arcus.svg)](https://gitter.im/sandialabs/Arcus?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/sandialabs/Arcus/build.yml?branch=main) +[![nuget Version](https://img.shields.io/nuget/v/Arcus)](https://www.nuget.org/packages/Arcus) +[![GitHub Release](https://img.shields.io/github/v/release/sandialabs/Arcus)](https://github.com/sandialabs/Arcus/releases) +[![GitHub Tag](https://img.shields.io/github/v/tag/sandialabs/Arcus)](https://github.com/sandialabs/Arcus/tags) +![Targets](https://img.shields.io/badge/.NET%20Standard%202.0%20|%20.NET%208.0%20|%20.NET%209.0-blue?logo=.net) +[![Apache 2.0 License](https://img.shields.io/github/license/sandialabs/Arcus?logo=apache)](https://github.com/sandialabs/Arcus/blob/main/LICENSE) -[![Build Status](https://dev.azure.com/sandianationallabs/Arcus/_apis/build/status/sandialabs.Arcus?branchName=master)](https://dev.azure.com/sandianationallabs/Arcus/_build/latest?definitionId=2&branchName=master) +## About the Project Arcus is a C# manipulation library for calculating, parsing, formatting, converting, and comparing both IPv4 and IPv6 addresses and subnets. It accounts for 128-bit numbers on 32-bit platforms. +## ❗Breaking Changes in Version 3+ + +### IP Address Parsing based on .NET Targets + +In .NET versions up to and including .NET 4.8 (which corresponds to .NET Standard 2.0), stricter parsing rules were enforced for `IPAddress` according to the IPv6 specification. Specifically, the presence of a terminal '%' character without a valid zone index is considered invalid in these versions. As a result, the input `abcd::%` fails to parse, leading to a null or failed address parsing depending on `Parse`/`TryParse`. This behavior represents a breaking change from Arcus's previous target of .NET Standard 1.3. and may provide confusion for .NET 4.8 / .NET Standard 2.0 versions. + +In contrast, in newer versions of .NET, including .NET 8 and .NET 9, the parsing rules have been relaxed. The trailing '%' character is now ignored during parsing, allowing for inputs that would have previously failed. + +It is important to note that this scenario appears to be an extreme edge case, and developers should ensure that their applications handle `IPAddress` parsing appropriately across different target frameworks as expected. + +If in doubt it is suggested that IP Address based user input should be sanitized to meet your development needs. + ## Getting Started The latest stable release of Arcus is [available on NuGet](https://www.nuget.org/packages/Arcus/). The latest [Arcus documentation](https://arcus.readthedocs.io/en/latest/) may be found on [ReadTheDocs](https://arcus.readthedocs.io/en/latest/). -## Usage +### Usage At its heart Arcus is split amongst five separate interdependent units. _Types_, _Comparers_, _Converters_, _Math_, and _Utilities_. These units each work across Arcus's `IPAddressRange`, `Subnet` and .NET's `System.Net.IPAddress`. Arcus adds extra desired functionality where the standard C# libraries left off. -### Types +#### Types -#### `Subnet` +##### `Subnet` An IPv4 or IPv6 subnetwork representation - the work horse and original reason for the Arcus library. Outside the concept of the `Subnet` object, most everything else in Arcus is auxiliary and exists only in support of the `Subnet` object. That’s not to say that the remaining pieces of the Arcus library aren’t useful, on the contrary their utility can benefit a developer greatly. @@ -43,35 +56,35 @@ Subnet subnet; var success = Subnet.TryParse("192.168.1.0/16", out subnet) ``` -#### `IPAddressRange` +##### `IPAddressRange` `IPAddressRange` is a basic implementation of `IIPAddressRange` it is used to represent an inclusive range of arbitrary IP Addresses of the same address family. Unlike `Subnet`, `IPAddressRange` is not restricted to a power of two length, nor a valid broadcast address head. -### Comparers +#### Comparers The _Comparers_ package contains useful Comparer objects for comparing properties of IP Addresses and IP Address composite objects. -- `DefaultAddressFamilyComparer` - A comparer that compares address families. Most frequently `Internetwork` (IPv4) and `InternetworkV6` (IPv6) -- `DefaultIPAddressComparer` - A comparer for `IPAddress` objects -- `DefaultIPAddressRangeComparer` - A comparer for `IIPAddressRange`. Compares such that lower order ranges are less that higher order ranges accounting for size at matching range starts +- `DefaultAddressFamilyComparer` - A comparer that compares address families. Most frequently `Internetwork` (IPv4) and `InternetworkV6` (IPv6) +- `DefaultIPAddressComparer` - A comparer for `IPAddress` objects +- `DefaultIPAddressRangeComparer` - A comparer for `IIPAddressRange`. Compares such that lower order ranges are less that higher order ranges accounting for size at matching range starts -### Converters +#### Converters The _Converters_ package is a package of static utility classes for converting one type into another type. -#### `IPAddressConverters` +##### `IPAddressConverters` Static utility class containing conversion methods for converting `IPAddress` objects into something else. -### Math +#### Math The _Math_ package is a package of static utility classes for doing computational mathematics on objects. -#### `IPAddressMath` +##### `IPAddressMath` In some cases the C# `IPAddress` object doesn't go far enough with what you can do with it mathematically, this static utility class containing mathematical methods to fill in the gaps. -##### Incrementing and Decrementing +###### Incrementing and Decrementing Incrementing and Decrementing an `IPAddress` is easy. @@ -91,35 +104,35 @@ var result = address.Increment(-2); // result is 192.168.0.0 _Overflow_ and _Underflow_ conditions will result in an `InvalidOperationException`. -##### Equality +###### Equality Equality may also be tested via a host of equality extension methods: -- `bool IsEqualTo(this IPAddress alpha, IPAddress beta)` -- `bool IsGreaterThan(this IPAddress alpha, IPAddress beta)` -- `bool IsGreaterThanOrEqualTo(this IPAddress alpha, IPAddress beta)` -- `bool IsLessThan(this IPAddress alpha, IPAddress beta)` -- `bool IsLessThanOrEqualTo(this IPAddress alpha, IPAddress beta)` +- `bool IsEqualTo(this IPAddress alpha, IPAddress beta)` +- `bool IsGreaterThan(this IPAddress alpha, IPAddress beta)` +- `bool IsGreaterThanOrEqualTo(this IPAddress alpha, IPAddress beta)` +- `bool IsLessThan(this IPAddress alpha, IPAddress beta)` +- `bool IsLessThanOrEqualTo(this IPAddress alpha, IPAddress beta)` -### Utilities +#### Utilities The _Utilities_ package contains static classes for miscellaneous operations on specific types. -#### `IPAddressUtilities` +##### `IPAddressUtilities` Static utility class containing miscellaneous operations for `IPAddress` objects -##### Address Family Detection +###### Address Family Detection A couple of extension methods were created to quickly determine the address family of an IP Address. To determine if an address is IPv4 use `bool IsIPv4(this IPAddress ipAddress)`, likewise `bool IsIPv6(this IPAddress ipAddress)` can be used to test for IPv6. -##### Parsing +###### Parsing It is possible to parse an `IPAddress` from a hexadecimal string into either an IPv4 of IPv6 address using the `IPAddress ParseFromHexString(string input, AddressFamily addressFamily)` method. Likewise it can be done safely with `bool TryParseFromHexString(string input, AddressFamily addressFamily, out IPAddress address)`. Similarly, conversion may be done from an octal string by using `bool TryParseIgnoreOctalInIPv4(string input, out IPAddress address)` or even a `BigInteger` by way of `bool TryParse(BigInteger input, AddressFamily addressFamily, out IPAddress address)`. -#### `SubnetUtilities` +##### `SubnetUtilities` Static utility class containing miscellaneous operations for `Subnet` objects that didn't make sense to put on the object itself. @@ -131,39 +144,71 @@ SubnetUtilities.FewestConsecutiveSubnetsFor(IPAddress.Parse("128.64.20.3"), IPAd would return an `Enumerable` containing the subnets `128.64.20.3/32`, `128.64.20.4/30`, `128.64.20.8/30`, `128.64.20.12/32`. +### Developer Notes + ## Built With -- [Gulliver](https://github.com/sandialabs/gulliver) - A self created library that helped us keep our bits and bytes in order -- [NuGet](https://www.nuget.org/) - Dependency Management -- [JetBrains.Annotations](https://www.jetbrains.com/help/resharper/10.0/Code_Analysis__Code_Annotations.html) - Used to keep developers honest -- [moq](https://github.com/moq/moq4/wiki) - Fake it until you make it! -- [Stackoverflow](https://stackoverflow.com/) - Because who really remembers how to code -- [xUnit.net](https://xunit.net/) - Testing, testing, 1, 2, 3... +This project was built with the aid of: + +- [CSharpier](https://csharpier.com/) +- [dotnet-outdated](https://github.com/dotnet-outdated/dotnet-outdated) +- [Gulliver](https://github.com/sandialabs/gulliver) - A self created library that helped us keep our bits and bytes in order +- [Husky.Net](https://alirezanet.github.io/Husky.Net/) +- [Roslynator](https://josefpihrt.github.io/docs/roslynator/) +- [SonarAnalyzer](https://www.sonarsource.com/products/sonarlint/features/visual-studio/) +- [StyleCop.Analyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) +- [xUnit.net](https://xunit.net/) + +### Versioning + +This project uses [Semantic Versioning](https://semver.org/) + +### Targeting + +The project targets [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0), [.NET 8](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8), and [.NET 9](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/overview). The test project similarly targets .NET 8, .NET 9, but targets [.NET Framework 4.8](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48) for the .NET Standard 2.0 tests. + +### Commit Hook + +The project itself has a configured pre-commit git hook, via [Husky.Net](https://alirezanet.github.io/Husky.Net/) that automatically lints and formats code via [dotnet format](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-format) and [csharpier](https://csharpier.com/). + +#### Disable husky in CI/CD pipelines + +Per the [Husky.Net instructions](https://alirezanet.github.io/Husky.Net/guide/automate.html#disable-husky-in-ci-cd-pipelines) + +> You can set the `HUSKY` environment variable to `0` in order to disable husky in CI/CD pipelines. + +#### Manual Linting and Formatting + +On occasion a manual run is desired it may be done so via the `src` directory and with the command + +```shell +dotnet format style; dotnet format analyzers; dotnet csharpier . +``` + +These commands may be called independently, but order may matter. + +#### Testing + +After making changes tests should be run that include all targets -## Versioning +## Acknowledgments -We use [SemVer](http://semver.org/) for versioning. +This project was built by the Production Tools Team at Sandia National Laboratories. Special thanks to all contributors and reviewers who helped shape and improve this library. -## Primary Authors and Contributors +Including, but not limited to: -- **Robert H. Engelhardt** - _Primary Developer, Source of Ideas Good and Bad_ - [@rheone](https://twitter.com/rheone) -- **Andrew Steele** - _Review and Suggestions_ - [@ahsteele](https://twitter.com/ahsteele) -- **Nick Bachicha** - _Git Wrangler and DevOps Extraordinaire_ - [@nicksterx](https://twitter.com/nicksterx) +- **Robert H. Engelhardt** - _Primary Developer, Source of Ideas Good and Bad_ - [rheone](https://github.com/rheone) +- **Andrew Steele** - _Review and Suggestions_ - [ahsteele](https://github.com/ahsteele) +- **Nick Bachicha** - _Git Wrangler and DevOps Extraordinaire_ - [nicksterx](https://github.com/nicksterx) ## Copyright -> Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. +> Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. ## License -> Licensed under the Apache License, Version 2.0 (the "License"); -> you may not use this file except in compliance with the License. -> You may obtain a copy of the License at +> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at > -> http://www.apache.org/licenses/LICENSE-2.0 +> http://www.apache.org/licenses/LICENSE-2.0 > -> Unless required by applicable law or agreed to in writing, software -> distributed under the License is distributed on an "AS IS" BASIS, -> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -> See the License for the specific language governing permissions and -> limitations under the License. +> Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/SECURITY.md b/SECURITY.md index 162b181..4050654 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,6 +4,6 @@ The Arcus team takes security issues in Arcus and our other projects seriously. **Please do not file an issue via the issue tracker**, we'd prefer to avoid publishing security issue details until after we have a fix in place. This will aid in avoiding vulnerability exploitation in the wild. -We request that if you discover a security significant issue in our library that you report it via email to us at [opensource@sandia.gov](mailto:opensource@sandia.gov). Please include the words "[ARCUS SECURITY]" in the subject line. Upon notification of a security issue we may request additional information. +We request that if you discover a security significant issue in our library that you report it via email to us at [opensource@sandia.gov](mailto:opensource@sandia.gov). Please include the words "[Arcus SECURITY]" in the subject line. Upon notification of a security issue we may request additional information. The Arcus team will do our best alleviate the issue and inform the public as appropriate. diff --git a/acknowledgements.md b/acknowledgements.md deleted file mode 100644 index 8a102d5..0000000 --- a/acknowledgements.md +++ /dev/null @@ -1,106 +0,0 @@ -Arcus and the Arcus Testing suite is reliant on several 3rd party packages - -#Gulliver -Hey, we made this too! Check out the [GitHub repository](https://github.com/sandialabs/gulliver) - -> Copyright 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. -> -> Licensed under the Apache License, Version 2.0 (the "License"); -> you may not use this file except in compliance with the License. -> You may obtain a copy of the License at -> -> http://www.apache.org/licenses/LICENSE-2.0 -> -> Unless required by applicable law or agreed to in writing, software -> distributed under the License is distributed on an "AS IS" BASIS, -> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -> See the License for the specific language governing permissions and -> limitations under the License. - -# moq -> BSD 3-Clause License -> -> Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions are met: -> -> * Redistributions of source code must retain the above copyright notice, -> this list of conditions and the following disclaimer. -> -> * Redistributions in binary form must reproduce the above copyright -> notice, this list of conditions and the following disclaimer in the -> documentation and/or other materials provided with the distribution. -> -> * Neither the names of the copyright holders nor the names of its -> contributors may be used to endorse or promote products derived from this -> software without specific prior written permission. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# xunit -> Unless otherwise noted, the source code here is covered by the following license: -> -> Copyright (c) .NET Foundation and Contributors -> All Rights Reserved -> -> Licensed under the Apache License, Version 2.0 (the "License"); -> you may not use this file except in compliance with the License. -> You may obtain a copy of the License at -> -> http://www.apache.org/licenses/LICENSE-2.0 -> -> Unless required by applicable law or agreed to in writing, software -> distributed under the License is distributed on an "AS IS" BASIS, -> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -> See the License for the specific language governing permissions and -> limitations under the License. -> -> ----------------------- -> -> The code in src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions was imported from: -> https://github.com/dotnet/core-setup/tree/v2.0.1/src/managed/Microsoft.DotNet.PlatformAbstractions -> -> The code in src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions was imported from: -> https://github.com/dotnet/core-setup/tree/v2.0.1/src/managed/Microsoft.Extensions.DependencyModel -> -> Both sets of code are covered by the following license: -> -> The MIT License (MIT) -> -> Copyright (c) 2015 .NET Foundation -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - -# JetBrains.Annotations -> Copyright (c) 2016 JetBrains http://www.jetbrains.com -> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, > sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES > OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.T OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 7b0a3b2..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,35 +0,0 @@ -trigger: -- master - -pr: -- master - -pool: - vmImage: 'windows-latest' - -steps: -- task: DotNetCoreCLI@2 - displayName: 'dotnet restore' - inputs: - command: restore - projects: '**/*.csproj' - -- task: DotNetCoreCLI@2 - displayName: 'dotnet test' - inputs: - command: test - arguments: '--collect:"Code Coverage"' - projects: '**/*Tests.csproj' - -- task: DotNetCoreCLI@2 - displayName: 'dotnet pack' - inputs: - command: pack - arguments: '--include-symbols -p:SymbolPackageFormat=snupkg' - packagesToPack: '**/Arcus.csproj;' - -- task: PublishPipelineArtifact@1 - displayName: 'Publish Pipeline Artifact' - inputs: - targetPath: '$(Build.ArtifactStagingDirectory)' - artifact: nuget \ No newline at end of file diff --git a/dictionary.dic b/dictionary.dic new file mode 100644 index 0000000..80a3f63 --- /dev/null +++ b/dictionary.dic @@ -0,0 +1,8 @@ +cidr +hextet +hextets +NTESS +parseable +Sandia +stringified +undelimited diff --git a/docs/Community.rst b/docs/Community.rst index e456dc5..bb2851b 100644 --- a/docs/Community.rst +++ b/docs/Community.rst @@ -16,11 +16,6 @@ GitHub Source Code available on `Arcus GitHub `_ -GITTER ------- - -The developers monitor the `Arcus Gitter chat `_ drop us a line. - File an Issue ------------- diff --git a/docs/MacAddress.rst b/docs/MacAddress.rst index 667ccf9..7d702df 100644 --- a/docs/MacAddress.rst +++ b/docs/MacAddress.rst @@ -61,7 +61,7 @@ A ``MacAddress`` may also be created via either the ``Parse`` or safe ``TryParse ParseAny ``string`` +++++++++++++++++++ -``ParseAny`` and the safe ``TryParseAny`` allow the parsing of an arbitrary string that may be a Mac address into a ``MacAddress``. It looks for six hexadecimal digits within the string, joins them and interprets the result as consecutive big-endian hextets. If six, and only six, hexadecimal digits are not found the parse will fail. +``ParseAny`` and the safe ``TryParseAny`` allow the parsing of an arbitrary string that may be a Mac address into a ``MacAddress``. It looks for six hexadecimal digits within the string, joins them and interprets the result as consecutive big-endian hextets. If six, and only six, hexadecimal digits are not found the parse will fail. .. code-block:: c# @@ -72,7 +72,7 @@ ParseAny ``string`` public static bool TryParseAny(string input, out MacAddress macAddress) Functionality -------------- +------------- Properties ^^^^^^^^^^ @@ -81,7 +81,7 @@ Properties :``bool`` IsLocallyAdministered: returns ``true`` if, and only if, is locally administered. :``bool`` IsMulticast: returns ``true`` if, and only if, the MAC Address is multicast. :``bool`` IsUnicast: returns ``true`` if, and only if, the MAC Address is unicast. -:``bool`` IsUnusable: returns ``true`` if, and only if, the MAC Address is "unusable", meaning all OUI bits of the MAC Address are unset. +:``bool`` IsUnusable: returns ``true`` if, and only if, the MAC Address is "unusable" [#EUI-Usable]_, meaning all OUI bits of the MAC Address are unset. :``MacAddress`` DefaultMacAddress: Provides a ``MacAddress`` that represents the default or ``null`` case MAC address. @@ -106,7 +106,7 @@ GetAddressBytes GetOuiBytes +++++++++++ -``GetOuiBytes`` returns the *Organizationally Unique Identifier (OUI)* [#Eui-Oui]_ of the ``MAcAddress``. +``GetOuiBytes`` returns the *Organizationally Unique Identifier (OUI)* [#Eui-Oui]_ of the ``MacAddress``. .. code-block:: c# @@ -115,7 +115,7 @@ GetOuiBytes GetCidBytes +++++++++++ -``GetCidBytes`` returns the *Company ID (CID)* [#Eui-Cid]_ of the ``MAcAddress``. +``GetCidBytes`` returns the *Company ID (CID)* [#Eui-Cid]_ of the ``MacAddress``. .. code-block:: c# @@ -141,7 +141,7 @@ Operators .. [#EUI-48Default] The recommended null or default value for **EUI-48** is ``FF-FF-FF-FF-FF-FF`` -.. [#IEEE-Eui] `Guidelines for Use of Extended Unique Identifier (EUI), Organizationally Unique Identifier (OUI), and Company ID (CID) `_ +.. [#IEEE-Eui] `Guidelines for Use of Extended Unique Identifier (EUI), Organizationally Unique Identifier (OUI), and Company ID (CID) `_ .. [#Eui-Oui] *Organizationally Unique Identifier (OUI)* is the first 3-bytes (24-bits) of a MAC-48 MAC Address. diff --git a/docs/conf.py b/docs/conf.py index 4b77286..6308bec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,7 @@ # -- Project information ----------------------------------------------------- project = u'Arcus' -copyright = u'Copyright 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software.' +copyright = u'Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software.' author = u'Sandia National Laboratories' # -- General configuration --------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index e10e530..08c103c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,21 +1,22 @@ Arcus ===== -.. image:: https://img.shields.io/nuget/vpre/arcus?logo=nuget - :alt: Nuget (with prereleases) +.. image:: https://img.shields.io/github/actions/workflow/status/sandialabs/Arcus/build.yml?branch=main) + :alt: GitHub Actions Workflow Status +.. image:: https://img.shields.io/nuget/v/Arcus + :alt: nuget Version :target: https://www.nuget.org/packages/Arcus/ -.. image:: https://img.shields.io/badge/GitHub-Arcus-lightgray?logo=github - :alt: Visit us on GitHub - :target: https://github.com/sandialabs/arcus -.. image:: https://img.shields.io/github/license/sandialabs/arcus?logo=apache +.. image:: https://img.shields.io/github/v/release/sandialabs/Arcus + :alt: GitHub Release + :target: https://github.com/sandialabs/Arcus/releases +.. image:: https://img.shields.io/github/v/tag/sandialabs/Arcus + :alt: GitHub Tag + :target: https://github.com/sandialabs/Arcus/tags +.. image:: https://img.shields.io/badge/.NET%20Standard%202.0%20|%20.NET%208.0%20|%20.NET%209.0-blue?logo=.net + :alt: Targets .NET Standard 2.0, .NET 8, and .NET 9 +.. image:: https://img.shields.io/github/license/sandialabs/Arcus?logo=apache :alt: Apache 2.0 License - :target: https://github.com/sandialabs/Arcus/blob/master/LICENSE -.. image:: https://img.shields.io/badge/targets-.NETStandard%201.3-5C2D91?logo=.net - :alt: .NetStandard 1.3 - :target: https://docs.microsoft.com/en-us/dotnet/standard/net-standard -.. image:: https://badges.gitter.im/sandialabs/Arcus.svg - :alt: Join the chat at https://gitter.im/sandialabs/Arcus - :target: https://gitter.im/sandialabs/Arcus?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + :target: https://github.com/sandialabs/Arcus/blob/main/LICENSE Arcus is a C# manipulation library for calculating, parsing, formatting, converting, and comparing both IPv4 and IPv6 addresses and subnets. It accounts for 128-bit numbers on 32-bit platforms. @@ -25,6 +26,17 @@ Arcus provides extension and helper methods for the pre-existing ``System.Net.IP Arcus heavily relies upon one of our other libraries `Gulliver `_, if you're interested in byte manipulation it is worth checking out. +.. warning:: + + Breaking Changes in Version 3+ + + In .NET versions up to and including .NET 4.8 (which corresponds to .NET Standard 2.0), stricter parsing rules were enforced for `IPAddress` according to the IPv6 specification.Specifically, the presence of a terminal '%' character without a valid zone index is considered invalid in these versions. As a result, the input `abcd::%` fails to parse, leading to a null or failed address parsing depending on `Parse`/`TryParse`. This behavior represents a breaking change from Arcus's previous target of .NET Standard 1.3. and may provide confusion for .NET 4.8 / .NET Standard 2.0 versions. + + In contrast, in newer versions of .NET, including .NET 8 and .NET 9, the parsing rules have been relaxed. The trailing '%' character is now ignored during parsing, allowing for inputs that would have previously failed. + + It is important to note that this scenario appears to be an extreme edge case, and developers should ensure that their applications handle `IPAddress` parsing appropriately across different target frameworks as expected. + + If in doubt it is suggested that IP Address based user input should be sanitized to meet your development needs. .. toctree:: :maxdepth: 1 @@ -46,7 +58,7 @@ Arcus heavily relies upon one of our other libraries `Gulliver tag. +dotnet_diagnostic.SA1605.severity = Warning # The or tag within the documentation header for a C# code element is missing or empty. +dotnet_diagnostic.SA1606.severity = Warning # The tag within the documentation header for a C# code element is empty. +dotnet_diagnostic.SA1607.severity = Warning # The or tag within the documentation header for a C# code element is empty. +dotnet_diagnostic.SA1608.severity = Warning # The tag within an element's Xml header documentation contains the default text generated by Visual Studio during the creation of the element. +dotnet_diagnostic.SA1609.severity = Warning # The Xml header documentation for a C# property does not contain a tag. +dotnet_diagnostic.SA1610.severity = Warning # The Xml header documentation for a C# property contains an empty tag. +dotnet_diagnostic.SA1611.severity = Warning # A C# method, constructor, delegate or indexer element is missing documentation for one or more of its parameters. +dotnet_diagnostic.SA1612.severity = Warning # The documentation describing the parameters to a C# method, constructor, delegate or indexer element does not match the actual parameters on the element. +dotnet_diagnostic.SA1613.severity = Warning # A tag within a C# element's documentation header is missing a name attribute containing the name of the parameter. +dotnet_diagnostic.SA1614.severity = Warning # A tag within a C# element's documentation header is empty. +dotnet_diagnostic.SA1615.severity = Warning # A C# element is missing documentation for its return value. +dotnet_diagnostic.SA1616.severity = Warning # The tag within a C# element's documentation header is empty. +dotnet_diagnostic.SA1617.severity = Warning # A C# code element does not contain a return value, or returns void, but the documentation header for the element contains a tag. +dotnet_diagnostic.SA1618.severity = Warning # A generic C# element is missing documentation for one or more of its generic type parameters. +dotnet_diagnostic.SA1619.severity = Warning # A generic, partial C# element is missing documentation for one or more of its generic type parameters, and the documentation for the element contains a tag. +dotnet_diagnostic.SA1620.severity = Warning # The tags within the Xml header documentation for a generic C# element do not match the generic type parameters on the element. +dotnet_diagnostic.SA1621.severity = Warning # A tag within the Xml header documentation for a generic C# element is missing a name attribute, or contains an empty name attribute. +dotnet_diagnostic.SA1622.severity = Warning # A tag within the Xml header documentation for a generic C# element is empty. +dotnet_diagnostic.SA1623.severity = Warning # The documentation text within a C# property's tag does not match the accessors within the property. +dotnet_diagnostic.SA1624.severity = Warning # The documentation text within a C# property's tag takes into account all of the accessors within the property, but one of the accessors has limited access. +dotnet_diagnostic.SA1625.severity = Warning # The Xml documentation for a C# element contains two or more identical entries, indicating that the documentation has been copied and pasted. +dotnet_diagnostic.SA1626.severity = Warning # The C# code contains a single-line comment which begins with three forward slashes in a row. +dotnet_diagnostic.SA1627.severity = Warning # The Xml header documentation for a C# code element contains an empty tag. +dotnet_diagnostic.SA1628.severity = Warning # A section of the Xml header documentation for a C# element does not begin with a capital letter. +dotnet_diagnostic.SA1629.severity = None # A section of the Xml header documentation for a C# element does not end with a period (also known as a full stop). +dotnet_diagnostic.SA1630.severity = Warning # A section of the Xml header documentation for a C# element does not contain any whitespace between words. +dotnet_diagnostic.SA1631.severity = Warning # A section of the Xml header documentation for a C# element does not contain enough alphabetic characters. +dotnet_diagnostic.SA1632.severity = None # From StyleCop 4.5 this rule is disabled by default. +dotnet_diagnostic.SA1633.severity = None # A C# code file is missing a standard file header. +dotnet_diagnostic.SA1634.severity = Warning # The file header at the top of a C# code file is missing a copyright tag. +dotnet_diagnostic.SA1635.severity = Warning # The file header at the top of a C# code file is missing copyright text. +dotnet_diagnostic.SA1636.severity = Warning # The file header at the top of a C# code file does not contain the appropriate copyright text. +dotnet_diagnostic.SA1637.severity = Warning # The file header at the top of a C# code file is missing the file name. +dotnet_diagnostic.SA1638.severity = Warning # The file tag within the file header at the top of a C# code file does not contain the name of the file. +dotnet_diagnostic.SA1639.severity = Warning # The file header at the top of a C# code file does not contain a filled-in summary tag. +dotnet_diagnostic.SA1640.severity = Warning # The file header at the top of a C# code file does not contain company name text. +dotnet_diagnostic.SA1641.severity = Warning # The file header at the top of a C# code file does not contain the appropriate company name text. +dotnet_diagnostic.SA1642.severity = Warning # The XML documentation header for a C# constructor does not contain the appropriate summary text. +dotnet_diagnostic.SA1643.severity = Warning # The Xml documentation header for a C# finalizer does not contain the appropriate summary text. +dotnet_diagnostic.SA1644.severity = Warning # A section within the Xml documentation header for a C# element contains blank lines. +dotnet_diagnostic.SA1645.severity = Warning # An included Xml documentation file does not exist. +dotnet_diagnostic.SA1646.severity = Warning # An included Xml documentation link contains an invalid path. +dotnet_diagnostic.SA1647.severity = Warning # An include tag within an Xml documentation header does not contain valid file and path attribute. +dotnet_diagnostic.SA1648.severity = Warning # has been used on an element that doesn't inherit from a base class or implement an interface. +dotnet_diagnostic.SA1649.severity = Warning # The file name of a C# code file does not match the first type declared in the file. +dotnet_diagnostic.SA1650.severity = Warning # The element documentation for the element contains one or more spelling mistakes or unrecognized words. +dotnet_diagnostic.SA1651.severity = Warning # The documentation for the element contains one or more elements. +dotnet_diagnostic.SA1652.severity = None # This rule was moved to SA0001 + +# Sonar +dotnet_diagnostic.S107.severity = Suggestion # Methods should not have too many parameters +dotnet_diagnostic.S1210.severity = Suggestion # "Equals" and the comparison operators should be overridden when implementing "IComparable" +dotnet_diagnostic.S2436.severity = Suggestion # Types and methods should not have too many generic parameters +dotnet_diagnostic.S4457.severity = Warning # Parameter validation in "async"/"await" methods should be wrapped + +# C# Autogenerated code +[{**/obj/**,**/bin/**}] +# AnalyzerId= Microsoft.CodeAnalysis.CSharp RuleNamespace= Microsoft.CodeAnalysis.CSharp +dotnet_diagnostic.CS8019.severity = none + +# C# Tests files in Tests projects +[{**Tests/**Tests.cs,**Tests.**/**Tests.cs}] +# Reset StyleCop documentation comments +dotnet_diagnostic.SA1600.severity = None # A C# code element is missing a documentation header. +dotnet_diagnostic.SA1601.severity = None # A C# partial element is missing a documentation header. +dotnet_diagnostic.SA1602.severity = None # An item within a C# enumeration is missing an Xml documentation header. + +# Sonar +dotnet_diagnostic.S4114.severity = Suggestion # Methods should not have identical implementations + +# Microsoft.VisualStudio.Threading +dotnet_diagnostic.VSTHRD200.severity = None # Use Async suffix for async methods + +# Misc +dotnet_diagnostic.CS1591.severity = None # Missing XML comment for publicly visible type or member 'Type_or_Member' +dotnet_diagnostic.SA1600.severity = None # Elements should be documented +dotnet_diagnostic.CA1707.severity = None # Identifiers should not contain underscores +dotnet_diagnostic.RCS1192.severity = Warning # RCS1192: Unnecessary usage of verbatim string literal. diff --git a/src/Arcus.DocExamples/AbstractIPAddressRangeExamples.cs b/src/Arcus.DocExamples/AbstractIPAddressRangeExamples.cs index a7b3d73..4e99c62 100644 --- a/src/Arcus.DocExamples/AbstractIPAddressRangeExamples.cs +++ b/src/Arcus.DocExamples/AbstractIPAddressRangeExamples.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Net; using Xunit; diff --git a/src/Arcus.DocExamples/Arcus.DocExamples.csproj b/src/Arcus.DocExamples/Arcus.DocExamples.csproj index 7d7e27f..6e8ef37 100644 --- a/src/Arcus.DocExamples/Arcus.DocExamples.csproj +++ b/src/Arcus.DocExamples/Arcus.DocExamples.csproj @@ -1,58 +1,68 @@ - netcoreapp3.0 - false - latest - Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. - Document exampl package for Arcus - Sandia National Laboratories + Documentation Example package for Arcus + The Production Tools Team Sandia National Laboratories + Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software + + + + net48;net8.0;net9.0 + false + true + bin\ + $(OutputPath)$(AssemblyName).xml - - - - all - runtime; build; native; contentfiles; analyzers - - + + stylecop.json + - - ..\analyzers.tests.ruleset - - - - stylecop.json - - + + + all runtime; build; native; contentfiles; analyzers - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - diff --git a/src/Arcus.DocExamples/Arcus.DocExamples.sln b/src/Arcus.DocExamples/Arcus.DocExamples.sln deleted file mode 100644 index f39afe8..0000000 --- a/src/Arcus.DocExamples/Arcus.DocExamples.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29409.12 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus.DocExamples", "Arcus.DocExamples.csproj", "{B9EF7BF8-6EFB-42BA-B4FA-811872159AC0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B9EF7BF8-6EFB-42BA-B4FA-811872159AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9EF7BF8-6EFB-42BA-B4FA-811872159AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9EF7BF8-6EFB-42BA-B4FA-811872159AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9EF7BF8-6EFB-42BA-B4FA-811872159AC0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {15CC7BEE-F708-4991-8794-E4130FA1D448} - EndGlobalSection -EndGlobal diff --git a/src/Arcus.DocExamples/DocReferenceGenerator.cs b/src/Arcus.DocExamples/DocReferenceGenerator.cs index 675d9ca..29e8c5c 100644 --- a/src/Arcus.DocExamples/DocReferenceGenerator.cs +++ b/src/Arcus.DocExamples/DocReferenceGenerator.cs @@ -12,10 +12,10 @@ public class DocReferenceGenerator public DocReferenceGenerator(ITestOutputHelper output) { - this.output = output; + this._output = output; } - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper _output; #endregion @@ -25,42 +25,41 @@ public void IPv4CirdRefGen() var sb = new StringBuilder(); sb.Append("CIDR") - .Append(',') - .Append("Network Prefix Address") - .Append(',') - .Append("Route Prefix") - .Append(',') - .Append("Netmask") - .Append(',') - .Append("Netmask (bits)") - .Append(',') - .Append("Address Count") - .Append(',') - .Append("Address Count 2^n") - .AppendLine(); + .Append(',') + .Append("Network Prefix Address") + .Append(',') + .Append("Route Prefix") + .Append(',') + .Append("Netmask") + .Append(',') + .Append("Netmask (bits)") + .Append(',') + .Append("Address Count") + .Append(',') + .Append("Address Count 2^n") + .AppendLine(); for (var i = 32; i >= 0; i--) { var subnet = new Subnet(IPAddressUtilities.IPv4MaxAddress, i); sb.Append(subnet) - .Append(',') - .Append(subnet.NetworkPrefixAddress) - .Append(',') - .Append(subnet.RoutingPrefix) - .Append(',') - .Append(subnet.Netmask) - .Append(',') - .Append(subnet.Netmask.GetAddressBytes() - .ToString("b")) - .Append(',') - .Append(subnet.Length) - .Append(',') - .Append(32 - i) - .AppendLine(); + .Append(',') + .Append(subnet.NetworkPrefixAddress) + .Append(',') + .Append(subnet.RoutingPrefix) + .Append(',') + .Append(subnet.Netmask) + .Append(',') + .Append(subnet.Netmask.GetAddressBytes().ToString("b")) + .Append(',') + .Append(subnet.Length) + .Append(',') + .Append(32 - i) + .AppendLine(); } - this.output.WriteLine(sb.ToString()); + this._output.WriteLine(sb.ToString()); } [Fact] @@ -69,33 +68,33 @@ public void IPv6CirdRefGen() var sb = new StringBuilder(); sb.Append("CIDR") - .Append(',') - .Append("Network Prefix Address") - .Append(',') - .Append("Route Prefix") - .Append(',') - .Append("Address Count") - .Append(',') - .Append("Address Count 2^n") - .AppendLine(); + .Append(',') + .Append("Network Prefix Address") + .Append(',') + .Append("Route Prefix") + .Append(',') + .Append("Address Count") + .Append(',') + .Append("Address Count 2^n") + .AppendLine(); for (var i = 128; i >= 0; i--) { var subnet = new Subnet(IPAddressUtilities.IPv6MaxAddress, i); sb.Append(subnet) - .Append(',') - .Append(subnet.NetworkPrefixAddress) - .Append(',') - .Append(subnet.RoutingPrefix) - .Append(',') - .Append(subnet.Length) - .Append(',') - .Append(128 - i) - .AppendLine(); + .Append(',') + .Append(subnet.NetworkPrefixAddress) + .Append(',') + .Append(subnet.RoutingPrefix) + .Append(',') + .Append(subnet.Length) + .Append(',') + .Append(128 - i) + .AppendLine(); } - this.output.WriteLine(sb.ToString()); + this._output.WriteLine(sb.ToString()); } } } diff --git a/src/Arcus.DocExamples/IPAddressConvertersExamples.cs b/src/Arcus.DocExamples/IPAddressConvertersExamples.cs index a24382a..6d9bdc8 100644 --- a/src/Arcus.DocExamples/IPAddressConvertersExamples.cs +++ b/src/Arcus.DocExamples/IPAddressConvertersExamples.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Linq; using System.Net; using System.Text; @@ -26,14 +26,14 @@ public IPAddressConvertersExamples(ITestOutputHelper output) public void NetmaskToCidrRoutePrefix_Example() { // equivalent byte value of 255.255.255.255 or 2^32 - var maxIPv4Bytes = Enumerable.Repeat((byte) 0xFF, 4) - .ToArray(); + var maxIPv4Bytes = Enumerable.Repeat((byte)0xFF, 4).ToArray(); // build all valid net masks - var allNetMasks = Enumerable.Range(7, 10) - .Select(i => maxIPv4Bytes.ShiftBitsLeft(32 - i)) // use Gulliver to shift bits of byte array - .Select(b => new IPAddress(b)) - .ToArray(); + var allNetMasks = Enumerable + .Range(7, 10) + .Select(i => maxIPv4Bytes.ShiftBitsLeft(32 - i)) // use Gulliver to shift bits of byte array + .Select(b => new IPAddress(b)) + .ToArray(); var sb = new StringBuilder(); @@ -42,12 +42,11 @@ public void NetmaskToCidrRoutePrefix_Example() var routePrefix = netmask.NetmaskToCidrRoutePrefix(); sb.Append(routePrefix) - .Append('\t') - .AppendFormat(CultureInfo.InvariantCulture, "{0,-15}", netmask) - .Append('\t') - .Append(netmask.GetAddressBytes() - .ToString("b")) // using Gulliver to print bytes as bits - .AppendLine(); + .Append('\t') + .AppendFormat(CultureInfo.InvariantCulture, "{0,-15}", netmask) + .Append('\t') + .Append(netmask.GetAddressBytes().ToString("b")) // using Gulliver to print bytes as bits + .AppendLine(); } this._output.WriteLine(sb.ToString()); @@ -57,15 +56,16 @@ public void NetmaskToCidrRoutePrefix_Example() public void ToBase85String_Example() { var addresses = new[] - { - "::", - "::ffff", - "1080:0:0:0:8:800:200C:417A", // specific example from RFC 1924 - "ffff::", - "ffff::0102:0304", - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" - }.Select(IPAddress.Parse) - .ToArray(); + { + "::", + "::ffff", + "1080:0:0:0:8:800:200C:417A", // specific example from RFC 1924 + "ffff::", + "ffff::0102:0304", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + } + .Select(IPAddress.Parse) + .ToArray(); var sb = new StringBuilder(); @@ -74,9 +74,9 @@ public void ToBase85String_Example() var base85String = address.ToBase85String(); sb.AppendFormat(CultureInfo.InvariantCulture, "{0,-40}", address) - .Append("\t=>\t") - .Append(base85String) - .AppendLine(); + .Append("\t=>\t") + .Append(base85String) + .AppendLine(); } this._output.WriteLine(sb.ToString()); @@ -86,15 +86,16 @@ public void ToBase85String_Example() public void ToDottedQuadString_Example() { var addresses = new[] - { - "::", - "::ffff", - "a:b:c::ff00:ff", - "ffff::", - "ffff::0102:0304", - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" - }.Select(IPAddress.Parse) - .ToArray(); + { + "::", + "::ffff", + "a:b:c::ff00:ff", + "ffff::", + "ffff::0102:0304", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + } + .Select(IPAddress.Parse) + .ToArray(); var sb = new StringBuilder(); @@ -103,9 +104,9 @@ public void ToDottedQuadString_Example() var dottedQuadString = address.ToDottedQuadString(); sb.AppendFormat(CultureInfo.InvariantCulture, "{0,-40}", address) - .Append("\t=>\t") - .Append(dottedQuadString) - .AppendLine(); + .Append("\t=>\t") + .Append(dottedQuadString) + .AppendLine(); } this._output.WriteLine(sb.ToString()); @@ -115,17 +116,18 @@ public void ToDottedQuadString_Example() public void ToHexString_Example() { var addresses = new[] - { - "::", - "::ffff", - "10.1.1.1", - "192.168.1.1", - "255.255.255.255", - "ffff::", - "ffff::0102:0304", - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" - }.Select(IPAddress.Parse) - .ToArray(); + { + "::", + "::ffff", + "10.1.1.1", + "192.168.1.1", + "255.255.255.255", + "ffff::", + "ffff::0102:0304", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + } + .Select(IPAddress.Parse) + .ToArray(); var sb = new StringBuilder(); @@ -134,9 +136,9 @@ public void ToHexString_Example() var hexString = address.ToHexString(); sb.AppendFormat(CultureInfo.InvariantCulture, "{0,-40}", address) - .Append("\t=>\t") - .Append(hexString) - .AppendLine(); + .Append("\t=>\t") + .Append(hexString) + .AppendLine(); } this._output.WriteLine(sb.ToString()); @@ -146,17 +148,18 @@ public void ToHexString_Example() public void ToNumericString_Example() { var addresses = new[] - { - "::", - "::ffff", - "10.1.1.1", - "192.168.1.1", - "255.255.255.255", - "ffff::", - "ffff::0102:0304", - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" - }.Select(IPAddress.Parse) - .ToArray(); + { + "::", + "::ffff", + "10.1.1.1", + "192.168.1.1", + "255.255.255.255", + "ffff::", + "ffff::0102:0304", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + } + .Select(IPAddress.Parse) + .ToArray(); var sb = new StringBuilder(); @@ -165,9 +168,9 @@ public void ToNumericString_Example() var numericString = address.ToNumericString(); sb.AppendFormat(CultureInfo.InvariantCulture, "{0,-40}", address) - .Append("\t=>\t") - .Append(numericString) - .AppendLine(); + .Append("\t=>\t") + .Append(numericString) + .AppendLine(); } this._output.WriteLine(sb.ToString()); @@ -176,17 +179,9 @@ public void ToNumericString_Example() [Fact] public void ToUncompressedString_Example() { - var addresses = new[] - { - "::", - "::ffff", - "10.1.1.1", - "192.168.1.1", - "255.255.255.255", - "ffff::", - "ffff::0102:0304" - }.Select(IPAddress.Parse) - .ToArray(); + var addresses = new[] { "::", "::ffff", "10.1.1.1", "192.168.1.1", "255.255.255.255", "ffff::", "ffff::0102:0304" } + .Select(IPAddress.Parse) + .ToArray(); var sb = new StringBuilder(); @@ -195,9 +190,9 @@ public void ToUncompressedString_Example() var uncompressedString = address.ToUncompressedString(); sb.AppendFormat(CultureInfo.InvariantCulture, "{0,-40}", address) - .Append("\t=>\t") - .Append(uncompressedString) - .AppendLine(); + .Append("\t=>\t") + .Append(uncompressedString) + .AppendLine(); } this._output.WriteLine(sb.ToString()); diff --git a/src/Arcus.DocExamples/IPAddressRangeExamples.cs b/src/Arcus.DocExamples/IPAddressRangeExamples.cs index cad6a35..7e49756 100644 --- a/src/Arcus.DocExamples/IPAddressRangeExamples.cs +++ b/src/Arcus.DocExamples/IPAddressRangeExamples.cs @@ -12,11 +12,11 @@ public void TryCollapseAll_Example() { // Arrange var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), - new IPAddressRange(IPAddress.Parse("192.168.1.6"), IPAddress.Parse("192.168.1.7")), - new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")) - }; + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), + new IPAddressRange(IPAddress.Parse("192.168.1.6"), IPAddress.Parse("192.168.1.7")), + new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")), + }; // Act var success = IPAddressRange.TryCollapseAll(ranges, out var results); diff --git a/src/Arcus.DocExamples/SubnetExamples.cs b/src/Arcus.DocExamples/SubnetExamples.cs index 9668ebc..a9199ad 100644 --- a/src/Arcus.DocExamples/SubnetExamples.cs +++ b/src/Arcus.DocExamples/SubnetExamples.cs @@ -34,9 +34,9 @@ public void Address_RoutePrefix_Subnet_Example() public void Contains_Example() { // Arrange - var subnetA = Subnet.Parse("192.168.1.0", 8); // 192.0.0.0 - 192.255.255.255 + var subnetA = Subnet.Parse("192.168.1.0", 8); // 192.0.0.0 - 192.255.255.255 var subnetB = Subnet.Parse("192.168.0.0", 16); // 192.168.0.0 - 192.168.255.255 - var subnetC = Subnet.Parse("255.0.0.0", 8); // 255.0.0.0 - 255.255.255.255 + var subnetC = Subnet.Parse("255.0.0.0", 8); // 255.0.0.0 - 255.255.255.255 // Act // Assert diff --git a/src/Arcus.DocExamples/SubnetUtilitiesExamples.cs b/src/Arcus.DocExamples/SubnetUtilitiesExamples.cs index 4b89019..00f3c15 100644 --- a/src/Arcus.DocExamples/SubnetUtilitiesExamples.cs +++ b/src/Arcus.DocExamples/SubnetUtilitiesExamples.cs @@ -16,8 +16,7 @@ public void FewestConsecutiveSubnetsFor_Example() var right = IPAddress.Parse("192.168.1.5"); // Act - var result = SubnetUtilities.FewestConsecutiveSubnetsFor(left, right) - .ToArray(); + var result = SubnetUtilities.FewestConsecutiveSubnetsFor(left, right).ToArray(); // Assert Assert.Equal(2, result.Length); @@ -30,11 +29,11 @@ public void LargestSubnet_Example() { // Arrange var tall = Subnet.Parse("255.255.255.254/31"); // 2^1 = 2 - var grande = Subnet.Parse("192.168.1.0/24"); // 2^8 = 256 - var vente = Subnet.Parse("10.10.0.0/16"); // 2^16 = 65536 - var trenta = Subnet.Parse("16.240.0.0/12"); // 2^20 = 1048576 + var grande = Subnet.Parse("192.168.1.0/24"); // 2^8 = 256 + var vente = Subnet.Parse("10.10.0.0/16"); // 2^16 = 65536 + var trenta = Subnet.Parse("16.240.0.0/12"); // 2^20 = 1048576 - var subnets = new[] {tall, grande, vente, trenta}; + var subnets = new[] { tall, grande, vente, trenta }; // Act var result = SubnetUtilities.LargestSubnet(subnets); @@ -48,11 +47,11 @@ public void SmallestSubnet_Example() { // Arrange var tall = Subnet.Parse("255.255.255.254/31"); // 2^1 = 2 - var grande = Subnet.Parse("192.168.1.0/24"); // 2^8 = 256 - var vente = Subnet.Parse("10.10.0.0/16"); // 2^16 = 65536 - var trenta = Subnet.Parse("16.240.0.0/12"); // 2^20 = 1048576 + var grande = Subnet.Parse("192.168.1.0/24"); // 2^8 = 256 + var vente = Subnet.Parse("10.10.0.0/16"); // 2^16 = 65536 + var trenta = Subnet.Parse("16.240.0.0/12"); // 2^20 = 1048576 - var subnets = new[] {tall, grande, vente, trenta}; + var subnets = new[] { tall, grande, vente, trenta }; // Act var result = SubnetUtilities.SmallestSubnet(subnets); diff --git a/src/Arcus.DocExamples/packages.lock.json b/src/Arcus.DocExamples/packages.lock.json new file mode 100644 index 0000000..3ca9e79 --- /dev/null +++ b/src/Arcus.DocExamples/packages.lock.json @@ -0,0 +1,469 @@ +{ + "version": 1, + "dependencies": { + ".NETFramework,Version=v4.8": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.13.0, )", + "resolved": "17.13.0", + "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", + "dependencies": { + "Microsoft.CodeCoverage": "17.13.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.2, )", + "resolved": "3.0.2", + "contentHash": "oXbusR6iPq0xlqoikjdLvzh+wQDkMv9If58myz9MEzldS4nIcp442Btgs2sWbYWV+caEluMe2pQCZ0hUZgPiow==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.12.0" + } + }, + "Gulliver": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.Bcl.HashCode": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.12.0", + "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "1.5.0", + "contentHash": "EXKiDFsChZW0RjrZ4FYHu9aW6+P4MCgEDCklsVseRfhoO0F+dXeMSsMRAlVXIo06kGJ/zv+2w1a2uc2+kxxSaQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==", + "dependencies": { + "System.Collections.Immutable": "1.5.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "arcus": { + "type": "Project", + "dependencies": { + "Gulliver": "[2.0.0, )", + "Microsoft.Bcl.HashCode": "[6.0.0, )" + } + } + }, + "net8.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.13.0, )", + "resolved": "17.13.0", + "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", + "dependencies": { + "Microsoft.CodeCoverage": "17.13.0", + "Microsoft.TestPlatform.TestHost": "17.13.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.2, )", + "resolved": "3.0.2", + "contentHash": "oXbusR6iPq0xlqoikjdLvzh+wQDkMv9If58myz9MEzldS4nIcp442Btgs2sWbYWV+caEluMe2pQCZ0hUZgPiow==" + }, + "Gulliver": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.13.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "arcus": { + "type": "Project", + "dependencies": { + "Gulliver": "[2.0.0, )" + } + } + }, + "net9.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.13.0, )", + "resolved": "17.13.0", + "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", + "dependencies": { + "Microsoft.CodeCoverage": "17.13.0", + "Microsoft.TestPlatform.TestHost": "17.13.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.2, )", + "resolved": "3.0.2", + "contentHash": "oXbusR6iPq0xlqoikjdLvzh+wQDkMv9If58myz9MEzldS4nIcp442Btgs2sWbYWV+caEluMe2pQCZ0hUZgPiow==" + }, + "Gulliver": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.13.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "arcus": { + "type": "Project", + "dependencies": { + "Gulliver": "[2.0.0, )" + } + } + } + } +} \ No newline at end of file diff --git a/src/Arcus.Tests/AbstractIPAddressRangeTests.cs b/src/Arcus.Tests/AbstractIPAddressRangeTests.cs index f737737..e477e88 100644 --- a/src/Arcus.Tests/AbstractIPAddressRangeTests.cs +++ b/src/Arcus.Tests/AbstractIPAddressRangeTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -7,14 +7,11 @@ using System.Numerics; using Arcus.Math; using Arcus.Utilities; -using JetBrains.Annotations; -using Moq; +using NSubstitute; using Xunit; -using Xunit.Abstractions; namespace Arcus.Tests { - [PublicAPI] public class AbstractIPAddressRangeTests { #region Setup / Teardown @@ -40,7 +37,7 @@ public void Deconstruct_Head_Tail_Test() var tail = IPAddress.Parse(tailString); // Act - var (resultHead, resultTail) = CreateMockAbstractIPAddressRange(head, tail); + var (resultHead, resultTail) = CreateSubstituteIPAddressRange(head, tail); // Assert Assert.Equal(resultHead, head); @@ -51,10 +48,9 @@ public void Deconstruct_Head_Tail_Test() #region other members - private static AbstractIPAddressRange CreateMockAbstractIPAddressRange(IPAddress head, - IPAddress tail) + private static AbstractIPAddressRange CreateSubstituteIPAddressRange(IPAddress head, IPAddress tail) { - return new Mock(MockBehavior.Strict, head, tail).Object; + return Substitute.For(head, tail); } #endregion @@ -71,22 +67,21 @@ private static AbstractIPAddressRange CreateMockAbstractIPAddressRange(IPAddress [InlineData("255.255.255.255", "255.255.255.255")] [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] - public void GetEnumerator_Test(string headAddressString, - string tailAddressString) + public void GetEnumerator_Test(string headAddressString, string tailAddressString) { // Arrange var head = IPAddress.Parse(headAddressString); var tail = IPAddress.Parse(tailAddressString); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act var ipAddressArray = iPAddressRange.ToArray(); // Assert Assert.Equal(iPAddressRange.Count(), ipAddressArray.Length); - Assert.Equal(head, ipAddressArray.First()); - Assert.Equal(tail, ipAddressArray.Last()); + Assert.Equal(head, ipAddressArray[0]); + Assert.Equal(tail, ipAddressArray[ipAddressArray.Length - 1]); } [Theory] @@ -99,18 +94,17 @@ public void GetEnumerator_Test(string headAddressString, [InlineData("255.255.255.255", "255.255.255.255")] [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] - public void GetIEnumerableEnumerator_Test(string headAddressString, - string tailAddressString) + public void GetIEnumerableEnumerator_Test(string headAddressString, string tailAddressString) { // Arrange var head = IPAddress.Parse(headAddressString); var tail = IPAddress.Parse(tailAddressString); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act var result = new List(); - var enumerator = ((IEnumerable) iPAddressRange).GetEnumerator(); + var enumerator = ((IEnumerable)iPAddressRange).GetEnumerator(); while (enumerator.MoveNext()) { result.Add(enumerator.Current as IPAddress); @@ -118,8 +112,8 @@ public void GetIEnumerableEnumerator_Test(string headAddressString, // Assert Assert.Equal(iPAddressRange.Count(), result.Count); - Assert.Equal(head, result.First()); - Assert.Equal(tail, result.Last()); + Assert.Equal(head, result[0]); + Assert.Equal(tail, result[result.Count - 1]); } #endregion // end: IEnumerable @@ -128,17 +122,16 @@ public void GetIEnumerableEnumerator_Test(string headAddressString, public static IEnumerable IsSingleIP_Test_Values() { - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Any)}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.IPv6Any)}; + yield return new object[] { true, CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Any) }; + yield return new object[] { true, CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.IPv6Any) }; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast)}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.IPv6Loopback)}; + yield return new object[] { false, CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast) }; + yield return new object[] { false, CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.IPv6Loopback) }; } [Theory] [MemberData(nameof(IsSingleIP_Test_Values))] - public void IsSingleIP_Test(bool expected, - AbstractIPAddressRange ipAddressRange) + public void IsSingleIP_Test(bool expected, AbstractIPAddressRange ipAddressRange) { // Arrange // Act @@ -155,52 +148,62 @@ public void IsSingleIP_Test(bool expected, public static IEnumerable Length_Test_Values() { // single address - yield return new object[] {new BigInteger(1), CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Any)}; - yield return new object[] {new BigInteger(1), CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.IPv6Any)}; + yield return new object[] { new BigInteger(1), CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Any) }; + yield return new object[] + { + new BigInteger(1), + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.IPv6Any), + }; // maximum length ipv4 - yield return new object[] {BigInteger.Pow(2, 32), CreateMockAbstractIPAddressRange(IPAddress.Parse("0.0.0.0"), IPAddress.Parse("255.255.255.255"))}; + yield return new object[] + { + BigInteger.Pow(2, 32), + CreateSubstituteIPAddressRange(IPAddress.Parse("0.0.0.0"), IPAddress.Parse("255.255.255.255")), + }; // maximum length ipv6 - yield return new object[] {BigInteger.Pow(2, 128), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))}; + yield return new object[] + { + BigInteger.Pow(2, 128), + CreateSubstituteIPAddressRange( + IPAddress.Parse("::"), + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ), + }; // ipv6 length at int.MaxValue yield return new object[] - { - new BigInteger(int.MaxValue), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), - IPAddress.Parse("::") - .Increment(int.MaxValue - 1)) - }; + { + new BigInteger(int.MaxValue), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::").Increment(int.MaxValue - 1)), + }; // ipv6 length at long.MaxValue yield return new object[] - { - new BigInteger(long.MaxValue), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), - IPAddress.Parse("::") - .Increment(long.MaxValue - 1)) - }; + { + new BigInteger(long.MaxValue), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::").Increment(long.MaxValue - 1)), + }; // ipv6 length at int.MaxValue + 1 yield return new object[] - { - new BigInteger(int.MaxValue) + 1, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), - IPAddress.Parse("::") - .Increment(int.MaxValue)) - }; + { + new BigInteger(int.MaxValue) + 1, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::").Increment(int.MaxValue)), + }; // ipv6 length at long.MaxValue + 1 yield return new object[] - { - new BigInteger(long.MaxValue) + 1, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), - IPAddress.Parse("::") - .Increment(long.MaxValue)) - }; + { + new BigInteger(long.MaxValue) + 1, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::").Increment(long.MaxValue)), + }; } [Theory] [MemberData(nameof(Length_Test_Values))] - public static void Length_Test(BigInteger expected, - AbstractIPAddressRange ipAddressRange) + public static void Length_Test(BigInteger expected, AbstractIPAddressRange ipAddressRange) { // Arrange // Act @@ -213,8 +216,7 @@ public static void Length_Test(BigInteger expected, [Theory] [MemberData(nameof(Length_Test_Values))] - public static void TryGetLength_Integer_Test(BigInteger expected, - AbstractIPAddressRange ipAddressRange) + public static void TryGetLength_Integer_Test(BigInteger expected, AbstractIPAddressRange ipAddressRange) { // Arrange // Act @@ -222,16 +224,12 @@ public static void TryGetLength_Integer_Test(BigInteger expected, // Assert Assert.Equal(expected <= int.MaxValue, success); - Assert.Equal(expected <= int.MaxValue - ? (int) expected - : -1, - length); + Assert.Equal(expected <= int.MaxValue ? (int)expected : -1, length); } [Theory] [MemberData(nameof(Length_Test_Values))] - public static void TryGetLength_Long_Test(BigInteger expected, - AbstractIPAddressRange ipAddressRange) + public static void TryGetLength_Long_Test(BigInteger expected, AbstractIPAddressRange ipAddressRange) { // Arrange // Act @@ -239,10 +237,7 @@ public static void TryGetLength_Long_Test(BigInteger expected, // Assert Assert.Equal(expected <= long.MaxValue, success); - Assert.Equal(expected <= long.MaxValue - ? (long) expected - : -1, - length); + Assert.Equal(expected <= long.MaxValue ? (long)expected : -1, length); } #endregion // end: Length / TryGetLength @@ -252,29 +247,26 @@ public static void TryGetLength_Long_Test(BigInteger expected, #region Contains(IPAddress) [Theory] - [InlineData(true, "::", "::ABCD", "::1234")] // IPv6 range contains IPv6 - [InlineData(false, "::", "::ABCD", "::FFFF")] // IPv6 range doesn't contains IPv6 - [InlineData(true, "::", "::ABCD", "::")] // IPv6 range contains IPv6 head - [InlineData(true, "::", "::ABCD", "::ABCD")] // IPv6 range contains IPv6 tail - [InlineData(false, "::", "::ABCD", "192.168.1.1")] // IPv6 range doesn't contains IPv4 - [InlineData(false, "::", "::ABCD", null)] // IPv6 range doesn't contains null + [InlineData(true, "::", "::ABCD", "::1234")] // IPv6 range contains IPv6 + [InlineData(false, "::", "::ABCD", "::FFFF")] // IPv6 range doesn't contains IPv6 + [InlineData(true, "::", "::ABCD", "::")] // IPv6 range contains IPv6 head + [InlineData(true, "::", "::ABCD", "::ABCD")] // IPv6 range contains IPv6 tail + [InlineData(false, "::", "::ABCD", "192.168.1.1")] // IPv6 range doesn't contains IPv4 + [InlineData(false, "::", "::ABCD", null)] // IPv6 range doesn't contains null [InlineData(true, "192.168.0.1", "192.168.0.254", "192.168.0.128")] // IPv4 range contains IPv4 - [InlineData(false, "192.168.0.1", "192.168.0.254", "10.1.1.1")] // IPv4 range doesn't contains IPv4 - [InlineData(true, "192.168.0.1", "192.168.0.254", "192.168.0.1")] // IPv4 range contains IPv4 head + [InlineData(false, "192.168.0.1", "192.168.0.254", "10.1.1.1")] // IPv4 range doesn't contains IPv4 + [InlineData(true, "192.168.0.1", "192.168.0.254", "192.168.0.1")] // IPv4 range contains IPv4 head [InlineData(true, "192.168.0.1", "192.168.0.254", "192.168.0.254")] // IPv4 range contains IPv4 tail - [InlineData(false, "192.168.0.1", "192.168.0.254", "::")] // IPv4 range doesn't contains IPv6 - [InlineData(false, "192.168.0.1", "192.168.0.254", null)] // IPv4 range doesn't contains null - public void ContainsIPAddress_Test(bool expected, - string headString, - string tailString, - string addressString) + [InlineData(false, "192.168.0.1", "192.168.0.254", "::")] // IPv4 range doesn't contains IPv6 + [InlineData(false, "192.168.0.1", "192.168.0.254", null)] // IPv4 range doesn't contains null + public void ContainsIPAddress_Test(bool expected, string headString, string tailString, string addressString) { // Arrange var head = IPAddress.Parse(headString); var tail = IPAddress.Parse(tailString); _ = IPAddress.TryParse(addressString, out var containsAddress); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act var result = iPAddressRange.Contains(containsAddress); @@ -288,41 +280,43 @@ public void ContainsIPAddress_Test(bool expected, #region Contains(IIPAddressRange) [Theory] - [InlineData(true, "::", "::ABCD", "::", "::ABCD")] // IPv6 range contains self - [InlineData(true, "::", "::ABCD", "::1", "::1234")] // IPv6 range contains internal IPv6 range - [InlineData(false, "::", "::ABCD", "AA::FFFF", "AB::FFFF")] // IPv6 range doesn't contains external IPv6 range - [InlineData(false, "A::", "A::ABCD", "::", "A::")] // IPv6 range doesn't contains IPv6 overhang head - [InlineData(false, "::", "::ABCD", "::", "AA::FFFF")] // IPv6 range doesn't contains IPv6 overhang tail - [InlineData(false, "::", "::ABCD", "192.168.1.1", "192.168.1.10")] // IPv6 range doesn't contains IPv4 range - [InlineData(false, "::", "A::ABCD", null, null)] // IPv6 range doesn't contain null range + [InlineData(true, "::", "::ABCD", "::", "::ABCD")] // IPv6 range contains self + [InlineData(true, "::", "::ABCD", "::1", "::1234")] // IPv6 range contains internal IPv6 range + [InlineData(false, "::", "::ABCD", "AA::FFFF", "AB::FFFF")] // IPv6 range doesn't contains external IPv6 range + [InlineData(false, "A::", "A::ABCD", "::", "A::")] // IPv6 range doesn't contains IPv6 overhang head + [InlineData(false, "::", "::ABCD", "::", "AA::FFFF")] // IPv6 range doesn't contains IPv6 overhang tail + [InlineData(false, "::", "::ABCD", "192.168.1.1", "192.168.1.10")] // IPv6 range doesn't contains IPv4 range + [InlineData(false, "::", "A::ABCD", null, null)] // IPv6 range doesn't contain null range [InlineData(true, "128.0.0.1", "128.0.0.254", "128.0.0.1", "128.0.0.254")] // IPv4 range contains self [InlineData(true, "128.0.0.1", "128.0.0.254", "128.0.0.5", "128.0.0.100")] // IPv4 range contains internal IPv4 range - [InlineData(false, "128.0.0.1", "128.0.0.254", "140.0.0.0", "145.0.0.0")] // IPv4 range doesn't contains external IPv4 range - [InlineData(false, "128.0.0.1", "128.0.0.254", "", "128.0.0.100")] // IPv4 range doesn't contains null head IPv4 range - [InlineData(false, "128.0.0.1", "128.0.0.254", "128.0.0.5", "")] // IPv4 range doesn't contains null tail IPv4 range - [InlineData(false, "128.0.0.1", "128.0.0.254", "0.0.0.0", "128.0.0.100")] // IPv4 range doesn't contains IPv4 overhang head - [InlineData(false, "128.0.0.1", "128.0.0.254", "128.0.0.5", "145.0.0.0")] // IPv4 range doesn't contains IPv4 overhang tail - [InlineData(false, "128.0.0.1", "128.0.0.254", "::ABCD", "42::ABCD")] // IPv4 range doesn't contains IPv6 range - [InlineData(false, "128.0.0.1", "128.0.0.254", null, null)] // IPv4 range doesn't contain null range - public void ContainsIPAddressRange_Test(bool expected, - string headString, - string tailString, - string containsHeadString, - string containsTailString) + [InlineData(false, "128.0.0.1", "128.0.0.254", "140.0.0.0", "145.0.0.0")] // IPv4 range doesn't contains external IPv4 range + [InlineData(false, "128.0.0.1", "128.0.0.254", "", "128.0.0.100")] // IPv4 range doesn't contains null head IPv4 range + [InlineData(false, "128.0.0.1", "128.0.0.254", "128.0.0.5", "")] // IPv4 range doesn't contains null tail IPv4 range + [InlineData(false, "128.0.0.1", "128.0.0.254", "0.0.0.0", "128.0.0.100")] // IPv4 range doesn't contains IPv4 overhang head + [InlineData(false, "128.0.0.1", "128.0.0.254", "128.0.0.5", "145.0.0.0")] // IPv4 range doesn't contains IPv4 overhang tail + [InlineData(false, "128.0.0.1", "128.0.0.254", "::ABCD", "42::ABCD")] // IPv4 range doesn't contains IPv6 range + [InlineData(false, "128.0.0.1", "128.0.0.254", null, null)] // IPv4 range doesn't contain null range + public void ContainsIPAddressRange_Test( + bool expected, + string headString, + string tailString, + string containsHeadString, + string containsTailString + ) { // Arrange var head = IPAddress.Parse(headString); var tail = IPAddress.Parse(tailString); - var mockIPAddressSubRange = IPAddress.TryParse(containsHeadString, out var subhead) - && IPAddress.TryParse(containsTailString, out var subtail) - ? new Mock(subhead, subtail) - : null; + var substituteIPAddressSubRange = + IPAddress.TryParse(containsHeadString, out var subhead) + && IPAddress.TryParse(containsTailString, out var subtail) + ? Substitute.For(subhead, subtail) + : null; - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - var containedIPAddressRange = mockIPAddressSubRange?.Object; + var containedIPAddressRange = substituteIPAddressSubRange; // Act var result = iPAddressRange.Contains(containedIPAddressRange); @@ -374,14 +368,14 @@ public void Enumerable_IPv6_ContainsExpected_Test() var head = IPAddress.Parse(headString); var tail = IPAddress.Parse(tailString); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act var result = iPAddressRange.ToList(); // Assert Assert.Equal(3, result.Count); - Assert.Equal(new[] {head, IPAddress.Parse("::b"), tail}, result); + Assert.Equal(new[] { head, IPAddress.Parse("::b"), tail }, result); } [Fact] @@ -391,15 +385,14 @@ public void Enumerable_IPv6_TakePastEnd_Test() var head = IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0"); var tail = IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act - var result = iPAddressRange.Take(100) - .ToList(); + var result = iPAddressRange.Take(100).ToList(); // Assert Assert.Equal(16, result.Count); - Assert.Equal(tail, result.Last()); + Assert.Equal(tail, result[result.Count - 1]); } [Fact] @@ -409,15 +402,14 @@ public void Enumerable_IPv4_TakePastEnd_Test() var head = IPAddress.Parse("255.255.255.240"); var tail = IPAddress.Parse("255.255.255.255"); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act - var result = iPAddressRange.Take(100) - .ToList(); + var result = iPAddressRange.Take(100).ToList(); // Assert Assert.Equal(16, result.Count); - Assert.Equal(tail, result.Last()); + Assert.Equal(tail, result[result.Count - 1]); } [Fact] // Test that the expected addresses appear in the given IPv4 range @@ -429,14 +421,14 @@ public void Enumerable_IPv4_ContainsExpected_Test() var head = IPAddress.Parse(headString); var tail = IPAddress.Parse(tailString); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act var result = iPAddressRange.ToList(); // Assert Assert.Equal(3, result.Count); - Assert.Equal(new[] {head, IPAddress.Parse("192.168.1.2"), tail}, result); + Assert.Equal(new[] { head, IPAddress.Parse("192.168.1.2"), tail }, result); } [Fact] // Test that a range of one returns a single address when Addresses is called @@ -445,7 +437,7 @@ public void Enumerable_SameHead_And_SameTail_ReturnsSingle_Test() // Arrange var ipAddress = IPAddress.Parse("192.168.1.1"); - var iPAddressRange = CreateMockAbstractIPAddressRange(ipAddress, ipAddress); + var iPAddressRange = CreateSubstituteIPAddressRange(ipAddress, ipAddress); // Act var addresses = iPAddressRange.ToArray(); @@ -460,12 +452,13 @@ public void Enumerable_ReasonableIteration_Test() { // Arrange - var iPAddressRange = CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + var iPAddressRange = CreateSubstituteIPAddressRange( + IPAddress.Parse("::"), + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ); // Act - var addresses = iPAddressRange.Skip(10) - .Take(10) - .ToArray(); + var addresses = iPAddressRange.Skip(10).Take(10).ToArray(); // Assert Assert.NotNull(addresses); // if this test completed it likely means that we didn't iterate through 2^128 ip addresses to skip 10 and take 10 @@ -478,15 +471,14 @@ public void Enumerable_ReasonableIteration_Test() [Theory] [InlineData("192.168.1.1", "192.168.1.5")] [InlineData("::beef", "::dead")] - public void Ctor_HappyPath_Test(string headString, - string tailString) + public void Ctor_HappyPath_Test(string headString, string tailString) { // Arrange var head = IPAddress.Parse(headString); var tail = IPAddress.Parse(tailString); // Act - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Assert Assert.Equal(head, iPAddressRange.Head); @@ -497,29 +489,23 @@ public void Ctor_HappyPath_Test(string headString, [InlineData("192.168.1.1", null)] [InlineData(null, "192.168.1.5")] [InlineData(null, null)] - public void Ctor_Null_Input_Throws_ArgumentNullException_Test(string headString, - string tailString) + public void Ctor_Null_Input_Throws_ArgumentNullException_Test(string headString, string tailString) { // Arrange - var head = headString != null - ? IPAddress.Parse(headString) - : null; + var head = headString != null ? IPAddress.Parse(headString) : null; - var tail = tailString != null - ? IPAddress.Parse(tailString) - : null; + var tail = tailString != null ? IPAddress.Parse(tailString) : null; // Act // Assert - var exception = Assert.ThrowsAny(() => CreateMockAbstractIPAddressRange(head, tail)); + var exception = Assert.ThrowsAny(() => CreateSubstituteIPAddressRange(head, tail)); Assert.IsAssignableFrom(exception.InnerException); } [Theory] [InlineData("192.168.1.1", "::beef")] [InlineData("::beef", "192.168.1.5")] - public void Ctor_MismatchAddressFamilies_Throws_InvalidOperationException_Test(string headString, - string tailString) + public void Ctor_MismatchAddressFamilies_Throws_InvalidOperationException_Test(string headString, string tailString) { // Arrange var head = IPAddress.Parse(headString); @@ -527,15 +513,14 @@ public void Ctor_MismatchAddressFamilies_Throws_InvalidOperationException_Test(s // Act // Assert - var exception = Assert.ThrowsAny(() => CreateMockAbstractIPAddressRange(head, tail)); + var exception = Assert.ThrowsAny(() => CreateSubstituteIPAddressRange(head, tail)); Assert.IsAssignableFrom(exception.InnerException); } [Theory] [InlineData("192.168.1.5", "192.168.1.1")] [InlineData("::dead", "::beef")] - public void Ctor_BadAddressSequencing_Throws_InvalidOperationException_Test(string headString, - string tailString) + public void Ctor_BadAddressSequencing_Throws_InvalidOperationException_Test(string headString, string tailString) { // Arrange var head = IPAddress.Parse(headString); @@ -543,7 +528,7 @@ public void Ctor_BadAddressSequencing_Throws_InvalidOperationException_Test(stri // Act // Assert - var exception = Assert.ThrowsAny(() => CreateMockAbstractIPAddressRange(head, tail)); + var exception = Assert.ThrowsAny(() => CreateSubstituteIPAddressRange(head, tail)); Assert.IsAssignableFrom(exception.InnerException); } @@ -561,7 +546,7 @@ public void AddressFamily_IPv4_Test() var head = IPAddress.Parse(headString); var tail = IPAddress.Parse(tailString); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act // Assert @@ -580,7 +565,7 @@ public void AddressFamily_IPv6_Test() var head = IPAddress.Parse(headString); var tail = IPAddress.Parse(tailString); - var iPAddressRange = CreateMockAbstractIPAddressRange(head, tail); + var iPAddressRange = CreateSubstituteIPAddressRange(head, tail); // Act // Assert @@ -599,51 +584,112 @@ public void AddressFamily_IPv6_Test() public static IEnumerable Contains_IIPAddressRange_Test_Values() { - var ipv4Range = CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast); - var ipv6Range = CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + var ipv4Range = CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast); + var ipv6Range = CreateSubstituteIPAddressRange( + IPAddress.IPv6Any, + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ); // null overlap checking - yield return new object[] {false, ipv4Range, null}; - yield return new object[] {false, ipv6Range, null}; + yield return new object[] { false, ipv4Range, null }; + yield return new object[] { false, ipv6Range, null }; // same overlap checking - yield return new object[] {true, ipv4Range, ipv4Range}; - yield return new object[] {true, ipv6Range, ipv6Range}; + yield return new object[] { true, ipv4Range, ipv4Range }; + yield return new object[] { true, ipv6Range, ipv6Range }; // equal overlap checking - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast), CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast)}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + }; // differing address families - yield return new object[] {false, ipv4Range, ipv6Range}; - yield return new object[] {false, ipv6Range, ipv4Range}; + yield return new object[] { false, ipv4Range, ipv6Range }; + yield return new object[] { false, ipv6Range, ipv4Range }; // head only overlapped - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + }; // full head and tail overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00")), + }; // tail only overlapped - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + }; // not touching - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f")), CreateMockAbstractIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f")), + CreateSubstituteIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f")), + }; // disparate ranges - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), + }; } [Theory] [MemberData(nameof(Contains_IIPAddressRange_Test_Values))] - public void Contains_IIPAddressRange_Test(bool expected, - IIPAddressRange left, - IIPAddressRange right) + public void Contains_IIPAddressRange_Test(bool expected, IIPAddressRange left, IIPAddressRange right) { // Arrange // Act @@ -659,52 +705,76 @@ public void Contains_IIPAddressRange_Test(bool expected, public static IEnumerable Contains_Test_Values() { - var ipv4Range = CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast); - var ipv6Range = CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + var ipv4Range = CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast); + var ipv6Range = CreateSubstituteIPAddressRange( + IPAddress.IPv6Any, + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ); // does not contain null - yield return new object[] {false, ipv4Range, null}; - yield return new object[] {false, ipv6Range, null}; + yield return new object[] { false, ipv4Range, null }; + yield return new object[] { false, ipv6Range, null }; // differing address families - yield return new object[] {false, ipv4Range, IPAddress.IPv6Any}; - yield return new object[] {false, ipv6Range, IPAddress.Any}; + yield return new object[] { false, ipv4Range, IPAddress.IPv6Any }; + yield return new object[] { false, ipv6Range, IPAddress.Any }; // contains head - yield return new object[] {true, ipv4Range, ipv4Range.Head}; - yield return new object[] {true, ipv6Range, ipv6Range.Head}; + yield return new object[] { true, ipv4Range, ipv4Range.Head }; + yield return new object[] { true, ipv6Range, ipv6Range.Head }; // contains tail - yield return new object[] {true, ipv4Range, ipv4Range.Tail}; - yield return new object[] {true, ipv6Range, ipv6Range.Tail}; + yield return new object[] { true, ipv4Range, ipv4Range.Tail }; + yield return new object[] { true, ipv6Range, ipv6Range.Tail }; // contains all inside - var ipv4InsideRange = CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")); + var ipv4InsideRange = CreateSubstituteIPAddressRange( + IPAddress.Parse("192.168.1.0"), + IPAddress.Parse("192.168.1.5") + ); foreach (var ip in ipv4InsideRange) { - yield return new object[] {true, ipv4InsideRange, ip}; + yield return new object[] { true, ipv4InsideRange, ip }; } - var ipv6InsideRange = CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("::ff0f")); + var ipv6InsideRange = CreateSubstituteIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("::ff0f")); foreach (var ip in ipv6InsideRange) { - yield return new object[] {true, ipv6InsideRange, ip}; + yield return new object[] { true, ipv6InsideRange, ip }; } // does not contain outside before - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.200")), IPAddress.Parse("192.168.1.0")}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff01"), IPAddress.Parse("::ff08")), IPAddress.Parse("::ff00")}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.200")), + IPAddress.Parse("192.168.1.0"), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ff01"), IPAddress.Parse("::ff08")), + IPAddress.Parse("::ff00"), + }; // does not contain outside after - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.200")), IPAddress.Parse("192.168.1.201")}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff01"), IPAddress.Parse("::ff08")), IPAddress.Parse("::ff09")}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.200")), + IPAddress.Parse("192.168.1.201"), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ff01"), IPAddress.Parse("::ff08")), + IPAddress.Parse("::ff09"), + }; } [Theory] [MemberData(nameof(Contains_Test_Values))] - public void Contains_Test(bool expected, - IIPAddressRange range, - IPAddress address) + public void Contains_Test(bool expected, IIPAddressRange range, IPAddress address) { // Arrange // Act @@ -724,51 +794,112 @@ public void Contains_Test(bool expected, public static IEnumerable HeadOverlappedBy_Test_Values() { - var ipv4Range = CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast); - var ipv6Range = CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + var ipv4Range = CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast); + var ipv6Range = CreateSubstituteIPAddressRange( + IPAddress.IPv6Any, + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ); // null overlap checking - yield return new object[] {false, ipv4Range, null}; - yield return new object[] {false, ipv6Range, null}; + yield return new object[] { false, ipv4Range, null }; + yield return new object[] { false, ipv6Range, null }; // same overlap checking - yield return new object[] {true, ipv4Range, ipv4Range}; - yield return new object[] {true, ipv6Range, ipv6Range}; + yield return new object[] { true, ipv4Range, ipv4Range }; + yield return new object[] { true, ipv6Range, ipv6Range }; // equal overlap checking - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast), CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast)}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + }; // differing address families - yield return new object[] {false, ipv4Range, ipv6Range}; - yield return new object[] {false, ipv6Range, ipv4Range}; + yield return new object[] { false, ipv4Range, ipv6Range }; + yield return new object[] { false, ipv6Range, ipv4Range }; // head only overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + }; // full head and tail overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), + }; // tail only overlapped - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + }; // not touching - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f")), CreateMockAbstractIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f")), + CreateSubstituteIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f")), + }; // disparate ranges - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), + }; } [Theory] [MemberData(nameof(HeadOverlappedBy_Test_Values))] - public void HeadOverlappedBy_Test(bool expected, - IIPAddressRange left, - IIPAddressRange right) + public void HeadOverlappedBy_Test(bool expected, IIPAddressRange left, IIPAddressRange right) { // Arrange // Act @@ -784,51 +915,112 @@ public void HeadOverlappedBy_Test(bool expected, public static IEnumerable TailOverlappedBy_Test_Values() { - var ipv4Range = CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast); - var ipv6Range = CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + var ipv4Range = CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast); + var ipv6Range = CreateSubstituteIPAddressRange( + IPAddress.IPv6Any, + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ); // null overlap checking - yield return new object[] {false, ipv4Range, null}; - yield return new object[] {false, ipv6Range, null}; + yield return new object[] { false, ipv4Range, null }; + yield return new object[] { false, ipv6Range, null }; // same overlap checking - yield return new object[] {true, ipv4Range, ipv4Range}; - yield return new object[] {true, ipv6Range, ipv6Range}; + yield return new object[] { true, ipv4Range, ipv4Range }; + yield return new object[] { true, ipv6Range, ipv6Range }; // equal overlap checking - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast), CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast)}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + }; // differing address families - yield return new object[] {false, ipv4Range, ipv6Range}; - yield return new object[] {false, ipv6Range, ipv4Range}; + yield return new object[] { false, ipv4Range, ipv6Range }; + yield return new object[] { false, ipv6Range, ipv4Range }; // head only overlapped - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + }; // full head and tail overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), + }; // tail only overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + }; // not touching - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f")), CreateMockAbstractIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f")), + CreateSubstituteIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f")), + }; // disparate ranges - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), + }; } [Theory] [MemberData(nameof(TailOverlappedBy_Test_Values))] - public void TailOverlappedBy_Test(bool expected, - IIPAddressRange left, - IIPAddressRange right) + public void TailOverlappedBy_Test(bool expected, IIPAddressRange left, IIPAddressRange right) { // Arrange // Act @@ -844,51 +1036,112 @@ public void TailOverlappedBy_Test(bool expected, public static IEnumerable Overlaps_Test_Values() { - var ipv4Range = CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast); - var ipv6Range = CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + var ipv4Range = CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast); + var ipv6Range = CreateSubstituteIPAddressRange( + IPAddress.IPv6Any, + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ); // null overlap checking - yield return new object[] {false, ipv4Range, null}; - yield return new object[] {false, ipv6Range, null}; + yield return new object[] { false, ipv4Range, null }; + yield return new object[] { false, ipv6Range, null }; // same overlap checking - yield return new object[] {true, ipv4Range, ipv4Range}; - yield return new object[] {true, ipv6Range, ipv6Range}; + yield return new object[] { true, ipv4Range, ipv4Range }; + yield return new object[] { true, ipv6Range, ipv6Range }; // equal overlap checking - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast), CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast)}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + }; // differing address families - yield return new object[] {false, ipv4Range, ipv6Range}; - yield return new object[] {false, ipv6Range, ipv4Range}; + yield return new object[] { false, ipv4Range, ipv6Range }; + yield return new object[] { false, ipv6Range, ipv4Range }; // head only overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + }; // full head and tail overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00")), + }; // tail only overlapped - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + }; // not touching - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f")), CreateMockAbstractIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.128")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.0"), IPAddress.Parse("10.1.1.100")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("ab::"), IPAddress.Parse("ab::f")), + CreateSubstituteIPAddressRange(IPAddress.Parse("ef::"), IPAddress.Parse("ef::f")), + }; // disparate ranges - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), + }; } [Theory] [MemberData(nameof(Overlaps_Test_Values))] - public void Overlaps_Test(bool expected, - IIPAddressRange left, - IIPAddressRange right) + public void Overlaps_Test(bool expected, IIPAddressRange left, IIPAddressRange right) { // Arrange // Act @@ -904,55 +1157,147 @@ public void Overlaps_Test(bool expected, public static IEnumerable Touches_Test_Values() { - var ipv4Range = CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast); - var ipv6Range = CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + var ipv4Range = CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast); + var ipv6Range = CreateSubstituteIPAddressRange( + IPAddress.IPv6Any, + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ); // null overlap checking - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Any, IPAddress.Broadcast), null}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), null}; + yield return new object[] { false, CreateSubstituteIPAddressRange(IPAddress.Any, IPAddress.Broadcast), null }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.IPv6Any, IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + null, + }; // differing address families - yield return new object[] {false, ipv4Range, ipv6Range}; - yield return new object[] {false, ipv6Range, ipv4Range}; + yield return new object[] { false, ipv4Range, ipv6Range }; + yield return new object[] { false, ipv6Range, ipv4Range }; // left tail touches right head - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.200"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::abcd")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::abce"), IPAddress.Parse("::ffff"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.200")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::abcd")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::abce"), IPAddress.Parse("::ffff")), + }; // left head touches right tail - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.200")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100"))}; - yield return new object[] {true, CreateMockAbstractIPAddressRange(IPAddress.Parse("::abce"), IPAddress.Parse("::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::abcd"))}; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.200")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")), + }; + yield return new object[] + { + true, + CreateSubstituteIPAddressRange(IPAddress.Parse("::abce"), IPAddress.Parse("::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::abcd")), + }; // head only overlapped - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.128")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + }; // full head and tail overlapped - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.128"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::ff00"), IPAddress.Parse("ff::ff00")), + }; // tail only overlapped - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.192"), IPAddress.Parse("192.168.1.255")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.192")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::ffff"), IPAddress.Parse("1::ffff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ffff")), + }; // disparate ranges - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.2.0")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::ff")), + CreateSubstituteIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), + }; // this tail at max - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("255.255.255.255")), CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), CreateMockAbstractIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("255.255.255.255")), + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange( + IPAddress.Parse("::"), + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ), + CreateSubstituteIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), + }; // that tail at max - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), CreateMockAbstractIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("255.255.255.255"))}; - yield return new object[] {false, CreateMockAbstractIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), CreateMockAbstractIPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))}; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("10.1.1.1"), IPAddress.Parse("10.1.5.0")), + CreateSubstituteIPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("255.255.255.255")), + }; + yield return new object[] + { + false, + CreateSubstituteIPAddressRange(IPAddress.Parse("f::"), IPAddress.Parse("f:1::")), + CreateSubstituteIPAddressRange( + IPAddress.Parse("::"), + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ), + }; } [Theory] [MemberData(nameof(Touches_Test_Values))] - public void Touches_Test(bool expected, - IIPAddressRange left, - IIPAddressRange right) + public void Touches_Test(bool expected, IIPAddressRange left, IIPAddressRange right) { // Arrange // Act @@ -976,35 +1321,58 @@ public static IEnumerable ContainsPublicPrivate_Values() foreach (var subnet in SubnetUtilities.PrivateIPAddressRangesList) { // has private - yield return new object[] {false, true, CreateMockAbstractIPAddressRange(subnet.Head, subnet.Tail)}; // on the border of private - yield return new object[] {false, true, CreateMockAbstractIPAddressRange(subnet.Head.Increment(2), subnet.Tail.Increment(-2))}; // wholly inside of private - yield return new object[] {false, true, CreateMockAbstractIPAddressRange(subnet.Head.Increment(2), subnet.Tail)}; // partially within of private - yield return new object[] {false, true, CreateMockAbstractIPAddressRange(subnet.Head, subnet.Tail.Increment(-2))}; // partially within of private + yield return new object[] { false, true, CreateSubstituteIPAddressRange(subnet.Head, subnet.Tail) }; // on the border of private + yield return new object[] + { + false, + true, + CreateSubstituteIPAddressRange(subnet.Head.Increment(2), subnet.Tail.Increment(-2)), + }; // wholly inside of private + yield return new object[] + { + false, + true, + CreateSubstituteIPAddressRange(subnet.Head.Increment(2), subnet.Tail), + }; // partially within of private + yield return new object[] + { + false, + true, + CreateSubstituteIPAddressRange(subnet.Head, subnet.Tail.Increment(-2)), + }; // partially within of private // has private and public - yield return new object[] {true, true, CreateMockAbstractIPAddressRange(subnet.Head.Increment(-2), subnet.Tail)}; // partially outside of private - yield return new object[] {true, true, CreateMockAbstractIPAddressRange(subnet.Head, subnet.Tail.Increment(2))}; // partially outside of private + yield return new object[] + { + true, + true, + CreateSubstituteIPAddressRange(subnet.Head.Increment(-2), subnet.Tail), + }; // partially outside of private + yield return new object[] { true, true, CreateSubstituteIPAddressRange(subnet.Head, subnet.Tail.Increment(2)) }; // partially outside of private } // public only var publicSpace = new (IPAddress head, IPAddress tail)[] - { - (IPAddress.Parse("128.64.32.0"), IPAddress.Parse("128.64.32.16")), - (IPAddress.Parse("FFFF:7FFF:3FFF::"), IPAddress.Parse("FFFF:7FFF:3FFF:1FFF::")) - }; + { + (IPAddress.Parse("128.64.32.0"), IPAddress.Parse("128.64.32.16")), + (IPAddress.Parse("FFFF:7FFF:3FFF::"), IPAddress.Parse("FFFF:7FFF:3FFF:1FFF::")), + }; foreach (var (head, tail) in publicSpace) { - yield return new object[] {true, false, CreateMockAbstractIPAddressRange(head, tail)}; // on the border of public - yield return new object[] {true, false, CreateMockAbstractIPAddressRange(head.Increment(2), tail.Increment(-2))}; // wholly inside public + yield return new object[] { true, false, CreateSubstituteIPAddressRange(head, tail) }; // on the border of public + yield return new object[] + { + true, + false, + CreateSubstituteIPAddressRange(head.Increment(2), tail.Increment(-2)), + }; // wholly inside public } } [Theory] [MemberData(nameof(ContainsPublicPrivate_Values))] - public void ContainsAnyPrivateAddresses_Test(bool expectedHasPublic, - bool expectedHasPrivate, - IIPAddressRange range) + public void ContainsAnyPrivateAddresses_Test(bool expectedHasPublic, bool expectedHasPrivate, IIPAddressRange range) { // Arrange // Act @@ -1018,9 +1386,7 @@ public void ContainsAnyPrivateAddresses_Test(bool expectedHasPublic, [Theory] [MemberData(nameof(ContainsPublicPrivate_Values))] - public void ContainsAllPrivateAddresses_Test(bool expectedHasPublic, - bool expectedHasPrivate, - IIPAddressRange range) + public void ContainsAllPrivateAddresses_Test(bool expectedHasPublic, bool expectedHasPrivate, IIPAddressRange range) { // Arrange // Act @@ -1034,9 +1400,7 @@ public void ContainsAllPrivateAddresses_Test(bool expectedHasPublic, [Theory] [MemberData(nameof(ContainsPublicPrivate_Values))] - public void ContainsAnyPublicAddresses_Test(bool expectedHasPublic, - bool expectedHasPrivate, - IIPAddressRange range) + public void ContainsAnyPublicAddresses_Test(bool expectedHasPublic, bool expectedHasPrivate, IIPAddressRange range) { // Arrange // Act @@ -1050,9 +1414,7 @@ public void ContainsAnyPublicAddresses_Test(bool expectedHasPublic, [Theory] [MemberData(nameof(ContainsPublicPrivate_Values))] - public void ContainsAllPublicAddresses_Test(bool expectedHasPublic, - bool expectedHasPrivate, - IIPAddressRange range) + public void ContainsAllPublicAddresses_Test(bool expectedHasPublic, bool expectedHasPrivate, IIPAddressRange range) { // Arrange // Act diff --git a/src/Arcus.Tests/Arcus.Tests.csproj b/src/Arcus.Tests/Arcus.Tests.csproj index d87a9e9..70a83db 100644 --- a/src/Arcus.Tests/Arcus.Tests.csproj +++ b/src/Arcus.Tests/Arcus.Tests.csproj @@ -1,64 +1,73 @@  - netcoreapp2.2 - false - latest - Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. Unit test package for Arcus - Sandia National Laboratories + The Production Tools Team Sandia National Laboratories + Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software + + net48;net8.0;net9.0 + false + true + bin\ + $(OutputPath)$(AssemblyName).xml + Exe + + + + + stylecop.json + + + - - - - + + + + all runtime; build; native; contentfiles; analyzers - - - - ..\analyzers.tests.ruleset - - - - - - - - bin\Arcus.Tests.xml - bin\ - - - - - - stylecop.json - - + + all - runtime; build; native; contentfiles; analyzers + runtime; build; native; contentfiles; analyzers; buildtransitive - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers diff --git a/src/Arcus.Tests/Comparers/DefaultAddressFamilyComparerTests.cs b/src/Arcus.Tests/Comparers/DefaultAddressFamilyComparerTests.cs index af7f2b4..e327314 100644 --- a/src/Arcus.Tests/Comparers/DefaultAddressFamilyComparerTests.cs +++ b/src/Arcus.Tests/Comparers/DefaultAddressFamilyComparerTests.cs @@ -24,22 +24,20 @@ public void Assignability_Test() public static IEnumerable Compare_Test_Values() { - var concernedAddressFamilies = new[] {AddressFamily.InterNetwork, AddressFamily.InterNetworkV6}; + var concernedAddressFamilies = new[] { AddressFamily.InterNetwork, AddressFamily.InterNetworkV6 }; foreach (var i in concernedAddressFamilies) { foreach (var j in concernedAddressFamilies) { - yield return new object[] {i.CompareTo(j), i, j}; + yield return new object[] { i.CompareTo(j), i, j }; } } } [Theory] [MemberData(nameof(Compare_Test_Values))] - public void Compare_Test(int expected, - AddressFamily x, - AddressFamily y) + public void Compare_Test(int expected, AddressFamily x, AddressFamily y) { // Arrange var comparer = new DefaultAddressFamilyComparer(); diff --git a/src/Arcus.Tests/Comparers/DefaultIIPAddressRangeComparerTests.cs b/src/Arcus.Tests/Comparers/DefaultIIPAddressRangeComparerTests.cs new file mode 100644 index 0000000..51a2f0f --- /dev/null +++ b/src/Arcus.Tests/Comparers/DefaultIIPAddressRangeComparerTests.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Numerics; +using Arcus.Comparers; +using NSubstitute; +using Xunit; + +namespace Arcus.Tests.Comparers +{ +#if NET6_0_OR_GREATER +#pragma warning disable IDE0062 // Make local function static (IDE0062); purposely allowing non-static functions that could be static for .net4.8 compatibility +#endif + public class DefaultIIPAddressRangeComparerTests + { + [Fact] + public void Assignability_Test() + { + // Arrange + var type = typeof(DefaultIIPAddressRangeComparer); + + // Act + var isAssignableFrom = typeof(IComparer).IsAssignableFrom(type); + + // Assert + Assert.True(isAssignableFrom); + } + + #region Ctor + + [Fact] + public void Ctor_NullInput_Throws_ArgumentNullException_Test() + { + // Arrange + // Act + // Assert + + Assert.Throws(() => new DefaultIIPAddressRangeComparer(null)); + } + + #endregion // end: Ctor + + #region Compare + + public static IEnumerable Compare_Test_Values() + { + // equal ranges + yield return new object[] { 0, CreateIIPAddressRange("192.168.1.0", 0), CreateIIPAddressRange("192.168.1.0", 0) }; + yield return new object[] { 0, CreateIIPAddressRange("a::", 0), CreateIIPAddressRange("a::", 0) }; + + // same range + var ipv4Same = CreateIIPAddressRange("192.168.1.0"); + yield return new object[] { 0, ipv4Same, ipv4Same }; + + var ipv6Same = CreateIIPAddressRange("a::"); + yield return new object[] { 0, ipv6Same, ipv6Same }; + + // null compare + yield return new object[] { 0, null, null }; + yield return new object[] { -1, null, CreateIIPAddressRange("192.168.1.0") }; + yield return new object[] { 1, CreateIIPAddressRange("192.168.1.0"), null }; + yield return new object[] { -1, null, CreateIIPAddressRange("a::") }; + yield return new object[] { 1, CreateIIPAddressRange("a::"), null }; + + // numerically equivalent, different address families + yield return new object[] { -1, CreateIIPAddressRange("0.0.0.0", 100), CreateIIPAddressRange("::", 100) }; + yield return new object[] { 1, CreateIIPAddressRange("::", 100), CreateIIPAddressRange("0.0.0.0", 100) }; + + // satisfies ordinal ordering by length + yield return new object[] { -1, CreateIIPAddressRange("192.0.0.0", 100), CreateIIPAddressRange("192.0.0.0", 500) }; + yield return new object[] { 1, CreateIIPAddressRange("192.0.0.0", 500), CreateIIPAddressRange("192.0.0.0", 100) }; + yield return new object[] { -1, CreateIIPAddressRange("ab::", 100), CreateIIPAddressRange("ab::", 500) }; + yield return new object[] { 1, CreateIIPAddressRange("ab::", 500), CreateIIPAddressRange("ab::", 100) }; + + IIPAddressRange CreateIIPAddressRange(string head, BigInteger? length = null) + { + var substitute = Substitute.For(); + + substitute.Head.Returns(IPAddress.Parse(head)); + + if (length != null) + { + substitute.Length.Returns(length.Value); + } + + return substitute; + } + } + + [Theory] + [MemberData(nameof(Compare_Test_Values))] + public void Compare_Test(int expected, IIPAddressRange x, IIPAddressRange y) + { + // Arrange + var comparer = new DefaultIIPAddressRangeComparer(); + + // Act + + var result = comparer.Compare(x, y); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void DeferToIPAddressComparerTest() + { + // Arrange + const int expectedResult = 42; + + var xHead = IPAddress.Parse("0.0.0.0"); + var yHead = IPAddress.Parse("abc::"); + + var x = CreateIIPAddressRange(xHead); + var y = CreateIIPAddressRange(yHead); + + var substituteIPAddressComparer = Substitute.For>(); + substituteIPAddressComparer.Compare(xHead, yHead).Returns(expectedResult); + + var comparer = new DefaultIIPAddressRangeComparer(substituteIPAddressComparer); + + // Act + var result = comparer.Compare(x, y); + + // Assert + Assert.Equal(expectedResult, result); + substituteIPAddressComparer.Received(1).Compare(xHead, yHead); // Verify that Compare was called once + + IIPAddressRange CreateIIPAddressRange(IPAddress head) + { + var substitute = Substitute.For(); + + substitute.Head.Returns(head); + + return substitute; + } + } + + #endregion // end: Compare + } +} diff --git a/src/Arcus.Tests/Comparers/DefaultIPAddressComparerTests.cs b/src/Arcus.Tests/Comparers/DefaultIPAddressComparerTests.cs index 20dd7c1..4a87f1f 100644 --- a/src/Arcus.Tests/Comparers/DefaultIPAddressComparerTests.cs +++ b/src/Arcus.Tests/Comparers/DefaultIPAddressComparerTests.cs @@ -3,7 +3,7 @@ using System.Net; using System.Net.Sockets; using Arcus.Comparers; -using Moq; +using NSubstitute; using Xunit; namespace Arcus.Tests.Comparers @@ -31,7 +31,7 @@ public void Ctor_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => new DefaultIPAddressComparer(null)); } @@ -42,39 +42,37 @@ public void Ctor_NullInput_Throws_ArgumentNullException_Test() public static IEnumerable Compare_Test_Values() { // equal addresses - yield return new object[] {0, IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.1")}; - yield return new object[] {0, IPAddress.Parse("dead::beef"), IPAddress.Parse("dead::beef")}; + yield return new object[] { 0, IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.1") }; + yield return new object[] { 0, IPAddress.Parse("dead::beef"), IPAddress.Parse("dead::beef") }; // same address var ipv4Same = IPAddress.Parse("192.168.1.1"); - yield return new object[] {0, ipv4Same, ipv4Same}; + yield return new object[] { 0, ipv4Same, ipv4Same }; var ipv6Same = IPAddress.Parse("dead::beef"); - yield return new object[] {0, ipv6Same, ipv6Same}; + yield return new object[] { 0, ipv6Same, ipv6Same }; // null compare - yield return new object[] {0, null, null}; - yield return new object[] { -1, null, IPAddress.Parse("192.168.1.1")}; - yield return new object[] {1, IPAddress.Parse("192.168.1.1"), null}; - yield return new object[] { -1, null, IPAddress.Parse("dead::beef")}; - yield return new object[] {1, IPAddress.Parse("dead::beef"), null}; + yield return new object[] { 0, null, null }; + yield return new object[] { -1, null, IPAddress.Parse("192.168.1.1") }; + yield return new object[] { 1, IPAddress.Parse("192.168.1.1"), null }; + yield return new object[] { -1, null, IPAddress.Parse("dead::beef") }; + yield return new object[] { 1, IPAddress.Parse("dead::beef"), null }; // numerically equivalent, different address families - yield return new object[] { -1, IPAddress.Parse("0.0.0.0"), IPAddress.Parse("::")}; - yield return new object[] {1, IPAddress.Parse("::"), IPAddress.Parse("0.0.0.0")}; + yield return new object[] { -1, IPAddress.Parse("0.0.0.0"), IPAddress.Parse("::") }; + yield return new object[] { 1, IPAddress.Parse("::"), IPAddress.Parse("0.0.0.0") }; // satisfies ordinal ordering - yield return new object[] { -1, IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.2")}; - yield return new object[] {1, IPAddress.Parse("192.168.1.2"), IPAddress.Parse("192.168.1.1")}; - yield return new object[] { -1, IPAddress.Parse("abc::123"), IPAddress.Parse("abc::124")}; - yield return new object[] {1, IPAddress.Parse("abc::124"), IPAddress.Parse("abc::123")}; + yield return new object[] { -1, IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.2") }; + yield return new object[] { 1, IPAddress.Parse("192.168.1.2"), IPAddress.Parse("192.168.1.1") }; + yield return new object[] { -1, IPAddress.Parse("abc::123"), IPAddress.Parse("abc::124") }; + yield return new object[] { 1, IPAddress.Parse("abc::124"), IPAddress.Parse("abc::123") }; } [Theory] [MemberData(nameof(Compare_Test_Values))] - public void Compare_Test(int expected, - IPAddress x, - IPAddress y) + public void Compare_Test(int expected, IPAddress x, IPAddress y) { // Arrange var comparer = new DefaultIPAddressComparer(); @@ -95,19 +93,17 @@ public void Compare_DeferToAddressFamilyComparer_Test() var x = IPAddress.Any; var y = IPAddress.IPv6Any; - var mockAddressFamilyComparer = new Mock>(); - - mockAddressFamilyComparer.Setup(c => c.Compare(x.AddressFamily, y.AddressFamily)) - .Returns(expectedResult); + var substituteAddressFamilyComparer = Substitute.For>(); + substituteAddressFamilyComparer.Compare(x.AddressFamily, y.AddressFamily).Returns(expectedResult); - var comparer = new DefaultIPAddressComparer(mockAddressFamilyComparer.Object); + var comparer = new DefaultIPAddressComparer(substituteAddressFamilyComparer); // Act var result = comparer.Compare(x, y); // Assert Assert.Equal(expectedResult, result); - mockAddressFamilyComparer.Verify(c => c.Compare(x.AddressFamily, y.AddressFamily), Times.Once); + substituteAddressFamilyComparer.Received(1).Compare(x.AddressFamily, y.AddressFamily); } #endregion // end: Compare diff --git a/src/Arcus.Tests/Comparers/DefaultIPAddressRangeComparerTests.cs b/src/Arcus.Tests/Comparers/DefaultIPAddressRangeComparerTests.cs index 46e10da..168900d 100644 --- a/src/Arcus.Tests/Comparers/DefaultIPAddressRangeComparerTests.cs +++ b/src/Arcus.Tests/Comparers/DefaultIPAddressRangeComparerTests.cs @@ -3,11 +3,14 @@ using System.Net; using System.Numerics; using Arcus.Comparers; -using Moq; +using NSubstitute; using Xunit; namespace Arcus.Tests.Comparers { +#if NET6_0_OR_GREATER +#pragma warning disable IDE0062 // Make local function static (IDE0062); purposely allowing non-static functions that could be static for .net4.8 compatibility +#endif public class DefaultIPAddressRangeComparerTests { [Fact] @@ -31,7 +34,7 @@ public void Ctor_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => new DefaultIPAddressRangeComparer(null)); } @@ -42,61 +45,51 @@ public void Ctor_NullInput_Throws_ArgumentNullException_Test() public static IEnumerable Compare_Test_Values() { // equal ranges - //yield return new object[] {0, CreateMockIPAddressRange("", "", 0), CreateMockIPAddressRange("", "", 0) }; - - yield return new object[] {0, CreateIIPAddressRange("192.168.1.0", 0), CreateIIPAddressRange("192.168.1.0", 0)}; - yield return new object[] {0, CreateIIPAddressRange("a::", 0), CreateIIPAddressRange("a::", 0)}; + yield return new object[] { 0, CreateIIPAddressRange("192.168.1.0", 0), CreateIIPAddressRange("192.168.1.0", 0) }; + yield return new object[] { 0, CreateIIPAddressRange("a::", 0), CreateIIPAddressRange("a::", 0) }; // same range var ipv4Same = CreateIIPAddressRange("192.168.1.0"); - yield return new object[] {0, ipv4Same, ipv4Same}; + yield return new object[] { 0, ipv4Same, ipv4Same }; var ipv6Same = CreateIIPAddressRange("a::"); - yield return new object[] {0, ipv6Same, ipv6Same}; + yield return new object[] { 0, ipv6Same, ipv6Same }; // null compare - yield return new object[] {0, null, null}; - yield return new object[] { -1, null, CreateIIPAddressRange("192.168.1.0")}; - yield return new object[] {1, CreateIIPAddressRange("192.168.1.0"), null}; - yield return new object[] { -1, null, CreateIIPAddressRange("a::")}; - yield return new object[] {1, CreateIIPAddressRange("a::"), null}; + yield return new object[] { 0, null, null }; + yield return new object[] { -1, null, CreateIIPAddressRange("192.168.1.0") }; + yield return new object[] { 1, CreateIIPAddressRange("192.168.1.0"), null }; + yield return new object[] { -1, null, CreateIIPAddressRange("a::") }; + yield return new object[] { 1, CreateIIPAddressRange("a::"), null }; // numerically equivalent, different address families - yield return new object[] { -1, CreateIIPAddressRange("0.0.0.0", 100), CreateIIPAddressRange("::", 100)}; - yield return new object[] {1, CreateIIPAddressRange("::", 100), CreateIIPAddressRange("0.0.0.0", 100)}; + yield return new object[] { -1, CreateIIPAddressRange("0.0.0.0", 100), CreateIIPAddressRange("::", 100) }; + yield return new object[] { 1, CreateIIPAddressRange("::", 100), CreateIIPAddressRange("0.0.0.0", 100) }; // satisfies ordinal ordering by length - yield return new object[] { -1, CreateIIPAddressRange("192.0.0.0", 100), CreateIIPAddressRange("192.0.0.0", 500)}; - yield return new object[] {1, CreateIIPAddressRange("192.0.0.0", 500), CreateIIPAddressRange("192.0.0.0", 100)}; - yield return new object[] { -1, CreateIIPAddressRange("ab::", 100), CreateIIPAddressRange("ab::", 500)}; - yield return new object[] {1, CreateIIPAddressRange("ab::", 500), CreateIIPAddressRange("ab::", 100)}; + yield return new object[] { -1, CreateIIPAddressRange("192.0.0.0", 100), CreateIIPAddressRange("192.0.0.0", 500) }; + yield return new object[] { 1, CreateIIPAddressRange("192.0.0.0", 500), CreateIIPAddressRange("192.0.0.0", 100) }; + yield return new object[] { -1, CreateIIPAddressRange("ab::", 100), CreateIIPAddressRange("ab::", 500) }; + yield return new object[] { 1, CreateIIPAddressRange("ab::", 500), CreateIIPAddressRange("ab::", 100) }; - IIPAddressRange CreateIIPAddressRange(string head, - BigInteger? length = null) + IIPAddressRange CreateIIPAddressRange(string head, BigInteger? length = null) { - var xMock = new Mock(MockBehavior.Strict); - - xMock.Setup(m => m.ToString()) - .Returns($"{head}[{length}]"); + var substitute = Substitute.For(); - xMock.Setup(m => m.Head) - .Returns(IPAddress.Parse(head)); + substitute.Head.Returns(IPAddress.Parse(head)); if (length != null) { - xMock.Setup(m => m.Length) - .Returns(length.Value); + substitute.Length.Returns(length.Value); } - return xMock.Object; + return substitute; } } [Theory] [MemberData(nameof(Compare_Test_Values))] - public void Compare_Test(int expected, - IIPAddressRange x, - IIPAddressRange y) + public void Compare_Test(int expected, IIPAddressRange x, IIPAddressRange y) { // Arrange var comparer = new DefaultIPAddressRangeComparer(); @@ -121,28 +114,25 @@ public void DeferToIPAddressComparerTest() var x = CreateIIPAddressRange(xHead); var y = CreateIIPAddressRange(yHead); - var mockIPAddressComparer = new Mock>(); - mockIPAddressComparer.Setup(c => c.Compare(xHead, yHead)) - .Returns(expectedResult); + var substituteIPAddressComparer = Substitute.For>(); + substituteIPAddressComparer.Compare(xHead, yHead).Returns(expectedResult); - var comparer = new DefaultIPAddressRangeComparer(mockIPAddressComparer.Object); + var comparer = new DefaultIPAddressRangeComparer(substituteIPAddressComparer); // Act var result = comparer.Compare(x, y); // Assert Assert.Equal(expectedResult, result); - mockIPAddressComparer.Verify(c => c.Compare(xHead, yHead), Times.Once); + substituteIPAddressComparer.Received(1).Compare(xHead, yHead); // Verify that Compare was called once IIPAddressRange CreateIIPAddressRange(IPAddress head) { - var xMock = new Mock(MockBehavior.Strict); + var substitute = Substitute.For(); - xMock.Setup(m => m.ToString()); - xMock.Setup(m => m.Head) - .Returns(head); + substitute.Head.Returns(head); - return xMock.Object; + return substitute; } } diff --git a/src/Arcus.Tests/Converters/IPAddressConvertersTests.cs b/src/Arcus.Tests/Converters/IPAddressConvertersTests.cs index 2577b1c..7f8f840 100644 --- a/src/Arcus.Tests/Converters/IPAddressConvertersTests.cs +++ b/src/Arcus.Tests/Converters/IPAddressConvertersTests.cs @@ -5,25 +5,11 @@ using Arcus.Converters; using Gulliver; using Xunit; -using Xunit.Abstractions; namespace Arcus.Tests.Converters { -#pragma warning disable SA1404 - public class IPAddressConvertersTests { - #region Setup / Teardown - - public IPAddressConvertersTests(ITestOutputHelper testOutputHelper) - { - this._testOutputHelper = testOutputHelper; - } - - private readonly ITestOutputHelper _testOutputHelper; - - #endregion - #region ToUncompressedString [Theory] @@ -41,8 +27,7 @@ public IPAddressConvertersTests(ITestOutputHelper testOutputHelper) [InlineData("0000:0000:0000:0000:0000:0000:0000:0000", "::")] [InlineData("0001:0002:ffff:0000:00ab:0000:0000:0123", "1:2:ffff:0:ab:0:0:123")] [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] - public void IPAddressToUncompressedStringConversion_Test(string expected, - string input) + public void IPAddressToUncompressedStringConversion_Test(string expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -67,8 +52,7 @@ public void IPAddressToUncompressedStringConversion_Test(string expected, [InlineData("00000000000000000000", "::")] [InlineData("000000000000000-mSjx", "::dead:beef")] [InlineData("4)+k&C#VzJ4br>0wv%Yp", "1080:0:0:0:8:800:200C:417A")] // specific example from RFC 1924 - public void ToBase85Test(string expected, - string input) + public void ToBase85Test(string expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -101,8 +85,7 @@ public void ToBase85Test(string expected, [InlineData("ffff::ffff:ffff:ffff:ffff:255.255.255.255", "ffff::ffff:ffff:ffff:ffff:ffff:ffff")] [InlineData("::0.0.0.0", "::")] [InlineData("192.168.1.1", "192.168.1.1")] - public void ToDottedQuadTestTest(string expected, - string input) + public void ToDottedQuadTestTest(string expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -130,8 +113,7 @@ public void ToDottedQuadTestTest(string expected, [InlineData("80808080", "128.128.128.128")] [InlineData("00808080", "0.128.128.128")] [InlineData("80808000", "128.128.128.0")] - public void ToHexTest(string expected, - string input) + public void ToHexTest(string expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -159,8 +141,7 @@ public void ToHexTest(string expected, [InlineData("2155905152", "128.128.128.128")] [InlineData("8421504", "0.128.128.128")] [InlineData("2155905024", "128.128.128.0")] - public void ToNumericTest(string expected, - string input) + public void ToNumericTest(string expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -180,20 +161,17 @@ public static IEnumerable NetmaskToCidrRoutePrefix_Test_Values() { for (var i = 0; i <= 32; i++) { - var netmaskBytes = Enumerable.Repeat((byte) 0xFF, 4) - .ToArray() - .ShiftBitsLeft(32 - i); + var netmaskBytes = Enumerable.Repeat((byte)0xFF, 4).ToArray().ShiftBitsLeft(32 - i); var netmask = new IPAddress(netmaskBytes); - yield return new object[] {i, netmask}; + yield return new object[] { i, netmask }; } } [Theory] [MemberData(nameof(NetmaskToCidrRoutePrefix_Test_Values))] - public void NetmaskToCidrRoutePrefix_Test(int expected, - IPAddress address) + public void NetmaskToCidrRoutePrefix_Test(int expected, IPAddress address) { // Arrange // Act @@ -223,8 +201,8 @@ public void NetmaskToCidrRoutePrefix_NullInput_Throws_ArgumentNullException_Test // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => ((IPAddress) null).NetmaskToCidrRoutePrefix()); + + Assert.Throws(() => ((IPAddress)null).NetmaskToCidrRoutePrefix()); } #endregion // end: NetmaskToCidrRoutePrefix diff --git a/src/Arcus.Tests/IPAddressCompatibilityTests.cs b/src/Arcus.Tests/IPAddressCompatibilityTests.cs index e50533c..3cf3f63 100644 --- a/src/Arcus.Tests/IPAddressCompatibilityTests.cs +++ b/src/Arcus.Tests/IPAddressCompatibilityTests.cs @@ -1,114 +1,110 @@ using System.Net; using Xunit; -using Xunit.Abstractions; namespace Arcus.Tests { // Trusting, but verifying the compatibility of IPAddress object with Microsoft System.Net.IPAddress object public class IPAddressCompatibilityTests { - #region Setup / Teardown - - public IPAddressCompatibilityTests(ITestOutputHelper testOutputHelper) - { - this._testOutputHelper = testOutputHelper; - } - - private readonly ITestOutputHelper _testOutputHelper; - - #endregion - [Theory] - // IPv4 Addresses // - min IPv4 address - [InlineData(true, "0", "0.0.0.0")] // IPv4 minimum single 0 - [InlineData(true, "0000", "0.0.0.0")] // IPv4 minimum 0000 - [InlineData(true, "0.0", "0.0.0.0")] // IPv4 minimum double 0 octets - [InlineData(true, "0.0.0", "0.0.0.0")] // IPv4 minimum triple 0 octets + [InlineData(true, "0", "0.0.0.0")] // IPv4 minimum single 0 + [InlineData(true, "0000", "0.0.0.0")] // IPv4 minimum 0000 + [InlineData(true, "0.0", "0.0.0.0")] // IPv4 minimum double 0 octets + [InlineData(true, "0.0.0", "0.0.0.0")] // IPv4 minimum triple 0 octets [InlineData(true, "0.0.0.0", "0.0.0.0")] // IPv4 minimum full 0 octets - // - max IPv4 address [InlineData(true, "255.255.255.255", "255.255.255.255")] // IPv4 max value full 255 octets - // - generic IPv4 address [InlineData(true, "192.168.1.100", "192.168.1.100")] // IPv4 generic - // - integer input - [InlineData(true, "1024", "0.0.4.0")] // integer 1024 - [InlineData(true, "2048", "0.0.8.0")] // integer 2048 - [InlineData(true, "4096", "0.0.16.0")] // integer 4096 + [InlineData(true, "1024", "0.0.4.0")] // integer 1024 + [InlineData(true, "2048", "0.0.8.0")] // integer 2048 + [InlineData(true, "4096", "0.0.16.0")] // integer 4096 [InlineData(true, "2147483647", "127.255.255.255")] // integer max int-1 (2147483647) - [InlineData(true, "2147483648", "128.0.0.0")] // integer max positive + [InlineData(true, "2147483648", "128.0.0.0")] // integer max positive [InlineData(true, "4294967295", "255.255.255.255")] // integer max uint - // - fewer than 4 octet IPv4 addresses - [InlineData(true, "192", "0.0.0.192")] // IPv4 single octet - [InlineData(true, "192.100", "192.0.0.100")] // IPv4 first and last octets - [InlineData(true, "192.1.100", "192.1.0.100")] // IPv4 three octets + [InlineData(true, "192", "0.0.0.192")] // IPv4 single octet + [InlineData(true, "192.100", "192.0.0.100")] // IPv4 first and last octets + [InlineData(true, "192.1.100", "192.1.0.100")] // IPv4 three octets [InlineData(true, "064.0100.004.017", "52.64.4.15")] // IPV4 leading 00 octal notation" - // octal notation IPv4 - [InlineData(true, "064", "0.0.0.52")] // IPv4 octal single octet + [InlineData(true, "064", "0.0.0.52")] // IPv4 octal single octet [InlineData(true, "064.0100.04.017", "52.64.4.15")] // IPv4 full octal - [InlineData(true, "064.100.4.18", "52.100.4.18")] // IPv4 mixed octal and decimal - + [InlineData(true, "064.100.4.18", "52.100.4.18")] // IPv4 mixed octal and decimal // IPv4-like invalid address string - [InlineData(false, "256.0.0.0", null)] // IPv4-like first octet too large - [InlineData(false, "0.256.0.0", null)] // IPv4-like second octet too large - [InlineData(false, "0.0.256.0", null)] // IPv4-like third octet too large - [InlineData(false, "0.0.0.256", null)] // IPv4-like fourth octet too large + [InlineData(false, "256.0.0.0", null)] // IPv4-like first octet too large + [InlineData(false, "0.256.0.0", null)] // IPv4-like second octet too large + [InlineData(false, "0.0.256.0", null)] // IPv4-like third octet too large + [InlineData(false, "0.0.0.256", null)] // IPv4-like fourth octet too large [InlineData(false, "255.255.255.255.0", null)] // IPv4-like extra 0 tail octet [InlineData(false, "0.255.255.255.255", null)] // IPv4-like extra 0 head octet - [InlineData(false, "064.0100.04.018", null)] // invalid 8 in octal notation - [InlineData(false, "192.168.1.1.", null)] // IPv4-like trailing period - [InlineData(false, "192.168.1.1/16", null)] // invalid input IPv4 basic cidr - [InlineData(false, "192.168.1.1/", null)] // invalid input IPv4 illegal tail slash - [InlineData(false, "/192.168.1.16", null)] // invalid input IPv4 basic illegal head slash - + [InlineData(false, "064.0100.04.018", null)] // invalid 8 in octal notation + [InlineData(false, "192.168.1.1.", null)] // IPv4-like trailing period + [InlineData(false, "192.168.1.1/16", null)] // invalid input IPv4 basic cidr + [InlineData(false, "192.168.1.1/", null)] // invalid input IPv4 illegal tail slash + [InlineData(false, "/192.168.1.16", null)] // invalid input IPv4 basic illegal head slash // IPv6 addresses - [InlineData(true, "::", "::")] // IPv6 minimum double colon - [InlineData(true, "0::", "::")] // IPv6 minimum double colon leading 0 - [InlineData(true, "::0", "::")] // IPv6 minimum double colon following 0 - [InlineData(true, "0::0", "::")] // IPv6 minimum double colon leading and following 0 - [InlineData(true, "::192.168.1.100", "::192.168.1.100")] // IPv6 encoded IPv4 - [InlineData(true, "ab::192.168.1.100", "ab::c0a8:164")] // IPv6 parse encoded IPv4 like - [InlineData(false, "ab::80000", null)] // IPv6 with IPv4 integer - [InlineData(false, "ab::064.0100.04.017", null)] // IPv6 with IPv4 full octal - [InlineData(true, "ab::0:192.168.1.100", "ab::c0a8:164")] // IPv6 parse encoded IPv4 like + [InlineData(true, "::", "::")] // IPv6 minimum double colon + [InlineData(true, "0::", "::")] // IPv6 minimum double colon leading 0 + [InlineData(true, "::0", "::")] // IPv6 minimum double colon following 0 + [InlineData(true, "0::0", "::")] // IPv6 minimum double colon leading and following 0 + [InlineData(true, "::192.168.1.100", "::192.168.1.100")] // IPv6 encoded IPv4 + [InlineData(true, "ab::192.168.1.100", "ab::c0a8:164")] // IPv6 parse encoded IPv4 like + [InlineData(false, "ab::80000", null)] // IPv6 with IPv4 integer + [InlineData(false, "ab::064.0100.04.017", null)] // IPv6 with IPv4 full octal + [InlineData(true, "ab::0:192.168.1.100", "ab::c0a8:164")] // IPv6 parse encoded IPv4 like [InlineData(true, "ffff:ffff:ffff:ffff:ffff:ffff:192.168.1.100", "ffff:ffff:ffff:ffff:ffff:ffff:c0a8:164")] // IPv6 parse encoded IPv4 like with leading max - [InlineData(true, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] // IPv6 maximum all ffff hextets - [InlineData(true, "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] // IPv6 maximum all FFFF hextets - [InlineData(true, "abcd::", "abcd::")] // IPv6 basic input - [InlineData(true, "[abcd::12]", "abcd::12")] // IPv6 encapsulated in brackets - + [InlineData(true, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] // IPv6 maximum all ffff hextets + [InlineData(true, "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] // IPv6 maximum all FFFF hextets + [InlineData(true, "abcd::", "abcd::")] // IPv6 basic input + [InlineData(true, "[abcd::12]", "abcd::12")] // IPv6 encapsulated in brackets // IPv6-like invalid address string - [InlineData(false, "abcd::/64", null)] // invalid input IPv6 basic cidr - [InlineData(false, "abcd::/", null)] // invalid input IPv6 illegal trailing slash - [InlineData(false, "/abcd::", null)] // invalid input IPv6 illegal prefix slash + [InlineData(false, "abcd::/64", null)] // invalid input IPv6 basic cidr + [InlineData(false, "abcd::/", null)] // invalid input IPv6 illegal trailing slash + [InlineData(false, "/abcd::", null)] // invalid input IPv6 illegal prefix slash [InlineData(false, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:", null)] // IPv6 trailing colon - [InlineData(false, "ffff::::abcd", null)] // IPv6 double double colons - [InlineData(false, "ffff::0::abcd", null)] // IPv6 double multiple collapse around hextets - [InlineData(false, "abcd::%", null)] // invalid input IPv6 trailing % - [InlineData(false, "%abcd::", null)] // invalid input IPv6 leading % - [InlineData(false, "f", null)] // single hex char - [InlineData(false, "fc", null)] // double hex char - [InlineData(false, "abcd:abc", null)] // IPv6 incomplete - + [InlineData(false, "ffff::::abcd", null)] // IPv6 double double colons + [InlineData(false, "ffff::0::abcd", null)] // IPv6 double multiple collapse around hextets + [InlineData(false, "%abcd::", null)] // invalid input IPv6 leading % + [InlineData(false, "f", null)] // single hex char + [InlineData(false, "fc", null)] // double hex char + [InlineData(false, "abcd:abc", null)] // IPv6 incomplete // invalid input - [InlineData(false, "", null)] // invalid input empty string - [InlineData(false, null, null)] // invalid input null - [InlineData(false, " ", null)] // invalid input whitespace - [InlineData(false, "\t", null)] // invalid input tab character - [InlineData(false, "-1", null)] // integer -1 + [InlineData(false, "", null)] // invalid input empty string + [InlineData(false, null, null)] // invalid input null + [InlineData(false, " ", null)] // invalid input whitespace + [InlineData(false, "\t", null)] // invalid input tab character + [InlineData(false, "-1", null)] // integer -1 [InlineData(false, "4294967296", null)] // integer unsigned max input - [InlineData(false, "8589934591", null)] - - // integer bigger than max uint - public void IPAddressTryParseSuccessTest(bool expected, - string input, - string expectedParseResult) + [InlineData(false, "8589934591", null)] // integer bigger than max uint +#if NET48 + /* + * In .NET versions up to and including .NET 4.8 (which corresponds to .NET Standard 2.0), stricter + * parsing rules were enforced for IPAddress according to the IPv6 specification.Specifically, + * the presence of a terminal '%' character without a valid zone index is considered invalid in + * these versions. As a result, the input "abcd::%" fails to parse, leading to a null or failed + * address parsing depending on Parse/TryParse.This behavior represents a breaking change from + * Arcus's previous target of .NET Standard 1.3. and may provide confusion for .NET 4.8 / .NET + * Standard 2.0 versions. + * + * In contrast, in newer versions of. NET, including .NET 8 and .NET 9, the parsing rules have been + * relaxed. The trailing '%' character is now ignored during parsing, allowing for inputs that + * would have previously failed. + * + * It is important to note that this scenario appears to be an extreme edge case, and developers + * should ensure that their applications handle IPAddress parsing appropriately across different + * target frameworks as expected.If in doubt it is suggested that IP Address based user input + * should be sanitized to meet your needs. + */ + [InlineData(false, "abcd::%", null)] +#else + [InlineData(true, "abcd::%", "abcd::")] +#endif + public void IPAddressTryParseSuccessTest(bool expected, string input, string expectedParseResult) { // Arrange diff --git a/src/Arcus.Tests/IPAddressRangeTests.cs b/src/Arcus.Tests/IPAddressRangeTests.cs index cf92ed1..e2cf52e 100644 --- a/src/Arcus.Tests/IPAddressRangeTests.cs +++ b/src/Arcus.Tests/IPAddressRangeTests.cs @@ -1,909 +1,967 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Net; +using Arcus; +using Arcus.Tests.XunitSerializers; +using Xunit; +using Xunit.Sdk; +#if NET48 // maintained for .NET 4.8 compatibility +using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; -using Xunit; +#endif + +[assembly: RegisterXunitSerializer(typeof(IPAddressRangeXunitSerializer), typeof(IPAddressRange))] namespace Arcus.Tests { - public class IPAddressRangeTests - { - #region GetHashCode - - [Theory] - [InlineData(true, "192.168.1.5", "192.168.1.100", "192.168.1.5", "192.168.1.100")] - [InlineData(false, "192.168.1.5", "192.168.1.100", "10.168.1.0", "10.168.1.100")] - [InlineData(true, "::abcd", "ff:12::abcd", "::abcd", "ff:12::abcd")] - [InlineData(false, "::abcd", "ff:12::abcd", "::ef", "ff:12::1234")] - public void GetHashCode_Test(bool expected, - string xHead, - string xTail, - string yHead, - string yTail) - { - // Arrange - _ = IPAddress.TryParse(xHead, out var xHeadAddress); - _ = IPAddress.TryParse(xTail, out var xTailAddress); - - var xAddressRange = new IPAddressRange(xHeadAddress, xTailAddress); - - _ = IPAddress.TryParse(yHead, out var yHeadAddress); - _ = IPAddress.TryParse(yTail, out var yTailAddress); - - var yAddressRange = new IPAddressRange(yHeadAddress, yTailAddress); - - // Act - var xHash = xAddressRange.GetHashCode(); - var yHash = yAddressRange.GetHashCode(); - var result = xHash.Equals(yHash); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: GetHashCode - - #region HeadOverlappedBy - - [Theory] - [InlineData(false, "192.168.1.0", "255.255.255.255", null, null)] - [InlineData(false, "192.168.1.0", "255.255.255.255", "::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:")] - [InlineData(false, "192.168.1.0", "255.255.255.255", "192.168.1.1", "255.255.255.255")] - [InlineData(false, "192.168.1.0", "255.255.255.255", "0.0.0.0", "192.168.0.255")] - [InlineData(true, "192.168.1.0", "255.255.255.255", "0.0.0.0", "255.255.255.255")] - [InlineData(true, "192.168.1.0", "255.255.255.255", "192.168.1.0", "192.168.1.0")] - public void HeadOverlappedBy_Test(bool expected, - string thisHead, - string thisTail, - string thatHead, - string thatTail) - { - // Arrange - // this - _ = IPAddress.TryParse(thisHead, out var thisHeadAddress); - _ = IPAddress.TryParse(thisTail, out var thisTailAddress); - - var thisAddressRange = new IPAddressRange(thisHeadAddress, thisTailAddress); - - // that - var thatAddressRange = IPAddress.TryParse(thatHead, out var thatHeadAddress) - && IPAddress.TryParse(thatTail, out var thatTailAddress) - ? new IPAddressRange(thatHeadAddress, thatTailAddress) - : null; - - // Act - var result = thisAddressRange.HeadOverlappedBy(thatAddressRange); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: HeadOverlappedBy - - #region Class - - [Theory] - [InlineData(typeof(AbstractIPAddressRange))] - [InlineData(typeof(IEquatable))] - [InlineData(typeof(IComparable))] - [InlineData(typeof(IComparable))] - [InlineData(typeof(ISerializable))] - public void Assignability_Test(Type assignableFromType) - { - // Arrange - var type = typeof(IPAddressRange); - - // Act - var isAssignableFrom = assignableFromType.IsAssignableFrom(type); - - // Assert - Assert.True(isAssignableFrom); - } - - #endregion //end: Class - - #region CompareTo / Operators - - public static IEnumerable Comparison_Values() - { - var ipv4Slash16 = Subnet.Parse("192.168.0.0/16"); - var ipv6Slash64 = Subnet.Parse("ab:cd::/64"); - var ipv4Slash20 = Subnet.Parse("192.168.0.0/20"); - var ipv6Slash96 = Subnet.Parse("ab:cd::/96"); - var ipv4All = Subnet.Parse("0.0.0.0/0"); - var ipv6All = Subnet.Parse("::/0"); - var ipv4Single = Subnet.Parse("0.0.0.0/32"); - var ipv6Single = Subnet.Parse("::/128"); - - yield return new object[] { 0, new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail) }; - yield return new object[] { 0, new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail) }; - yield return new object[] { 1, new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), null }; - yield return new object[] { 1, new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), null }; - yield return new object[] { 1, new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), new IPAddressRange(ipv4Slash20.Head, ipv4Slash20.Tail) }; - yield return new object[] { -1, new IPAddressRange(ipv4Slash20.Head, ipv4Slash20.Tail), new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail) }; - yield return new object[] { 1, new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), new IPAddressRange(ipv6Slash96.Head, ipv6Slash96.Tail) }; - yield return new object[] { -1, new IPAddressRange(ipv6Slash96.Head, ipv6Slash96.Tail), new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail) }; - yield return new object[] { -1, new IPAddressRange(ipv4All.Head, ipv4All.Tail), new IPAddressRange(ipv6All.Head, ipv6All.Tail) }; - yield return new object[] { 1, new IPAddressRange(ipv6All.Head, ipv6All.Tail), new IPAddressRange(ipv4All.Head, ipv4All.Tail) }; - yield return new object[] { -1, new IPAddressRange(ipv4Single.Head, ipv4Single.Tail), new IPAddressRange(ipv6Single.Head, ipv6Single.Tail) }; - yield return new object[] { 1, new IPAddressRange(ipv6Single.Head, ipv6Single.Tail), new IPAddressRange(ipv4Single.Head, ipv4Single.Tail) }; - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void CompareTo_Test(int expected, - IPAddressRange left, - IPAddressRange right) - { - // Arrange - // Act - var result = left.CompareTo(right); - - // Assert - Assert.Equal(expected, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_Equals_Test(int expected, - IPAddressRange left, - IPAddressRange right) - { - // Arrange - // Act - var result = left == right; - - // Assert - Assert.Equal(expected == 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_NotEquals_Test(int expected, - IPAddressRange left, - IPAddressRange right) - { - // Arrange - // Act - var result = left != right; - - // Assert - Assert.Equal(expected != 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_GreaterThan_Test(int expected, - IPAddressRange left, - IPAddressRange right) - { - // Arrange - // Act - var result = left > right; - - // Assert - Assert.Equal(expected > 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_GreaterThanOrEqual_Test(int expected, - IPAddressRange left, - IPAddressRange right) - { - // Arrange - // Act - var result = left >= right; - - // Assert - Assert.Equal(expected >= 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_LessThan_Test(int expected, - IPAddressRange left, - IPAddressRange right) - { - // Arrange - // Act - var result = left < right; - - // Assert - Assert.Equal(expected < 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_LessThanOrEqual_Test(int expected, - IPAddressRange left, - IPAddressRange right) - { - // Arrange - // Act - var result = left <= right; - - // Assert - Assert.Equal(expected <= 0, result); - } - - #endregion end CompareTo / Operators - - #region TailOverlappedBy - - [Theory] - [InlineData(false, "0.0.0.0", "192.168.1.0", null, null)] - [InlineData(false, "0.0.0.0", "192.168.1.0", "::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:")] - [InlineData(false, "0.0.0.0", "192.168.1.0", "192.168.1.1", "255.255.255.255")] - [InlineData(false, "0.0.0.0", "192.168.1.0", "0.0.0.0", "192.168.0.255")] - [InlineData(true, "0.0.0.0", "192.168.1.0", "0.0.0.0", "255.255.255.255")] - [InlineData(true, "0.0.0.0", "192.168.1.0", "192.168.1.0", "192.168.1.0")] - public void TailOverlappedBy_Test(bool expected, - string thisHead, - string thisTail, - string thatHead, - string thatTail) - { - // Arrange - // this - _ = IPAddress.TryParse(thisHead, out var thisHeadAddress); - _ = IPAddress.TryParse(thisTail, out var thisTailAddress); - - var thisAddressRange = new IPAddressRange(thisHeadAddress, thisTailAddress); - - // that - var thatAddressRange = IPAddress.TryParse(thatHead, out var thatHeadAddress) - && IPAddress.TryParse(thatTail, out var thatTailAddress) - ? new IPAddressRange(thatHeadAddress, thatTailAddress) - : null; - - // Act - var result = thisAddressRange.TailOverlappedBy(thatAddressRange); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: TailOverlappedBy - - #region TryMerge - - [Theory] - [InlineData(null, null, null, null, null)] - [InlineData(null, "192.168.1.1", "192.168.1.9", "::", "::")] - [InlineData(null, "192.168.1.1", "192.168.1.9", "192.168.1.11", "192.168.1.20")] - [InlineData(null, "192.168.1.1", "192.168.1.9", null, null)] - [InlineData(null, null, null, "192.168.1.1", "192.168.1.9")] - [InlineData("192.168.1.1-192.168.1.20", "192.168.1.1", "192.168.1.10", "192.168.1.11", "192.168.1.20")] - [InlineData("192.168.1.1-192.168.1.20", "192.168.1.1", "192.168.1.10", "192.168.1.10", "192.168.1.20")] - [InlineData("192.168.1.1-192.168.1.20", "192.168.1.11", "192.168.1.20", "192.168.1.1", "192.168.1.10")] - [InlineData("192.168.1.1-192.168.1.20", "192.168.1.10", "192.168.1.20", "192.168.1.1", "192.168.1.10")] - [InlineData("192.168.1.10-192.168.1.20", "192.168.1.10", "192.168.1.20", "192.168.1.10", "192.168.1.20")] - [InlineData("::-::", "::", "::", "::", "::")] - [InlineData("::1-::4", "::1", "::2", "::3", "::4")] - [InlineData(null, null, null, "::", "::")] - [InlineData(null, "::", "::", null, null)] - public void TryMergeResultTest(string expected, - string alphaHead, - string alphaTail, - string betaHead, - string betaTail) - { - // Arrange - var alphaAddressRange = IPAddress.TryParse(alphaHead, out var alphaHeadAddress) - && IPAddress.TryParse(alphaTail, out var alphaTailAddress) - ? new IPAddressRange(alphaHeadAddress, alphaTailAddress) - : null; - - var betaAddressRange = IPAddress.TryParse(betaHead, out var betaHeadAddress) - && IPAddress.TryParse(betaTail, out var betaTailAddress) - ? new IPAddressRange(betaHeadAddress, betaTailAddress) - : null; - - // Act - var successResult = IPAddressRange.TryMerge(alphaAddressRange, betaAddressRange, out var mergeResult); - - // Assert - Assert.Equal(expected != null, successResult); - - if (expected == null) - { - Assert.Null(mergeResult); - } - else - { - Assert.NotNull(mergeResult); - Assert.Equal(expected, $"{mergeResult.Head}-{mergeResult.Tail}"); - } - } - - #endregion - - #region Ctor - - [Fact] - public void Ctor_HeadAndTail_Specified_Test() - { - // Act - var head = IPAddress.Any; - var tail = IPAddress.Broadcast; - - // Act - var addressRange = new IPAddressRange(head, tail); - - // Assert - Assert.Equal(head, addressRange.Head); - Assert.Equal(tail, addressRange.Tail); - } - - [Fact] - public void Ctor_SingleAddressRange_Test() - { - // Act - var address = IPAddress.Any; - - // Act - var addressRange = new IPAddressRange(address); - - // Assert - Assert.Equal(address, addressRange.Head); - Assert.Equal(address, addressRange.Tail); - } - - #endregion - - #region ISerializable - - public static IEnumerable CanSerializable_Test_Values() - { - yield return new object[] { new IPAddressRange(IPAddress.Parse("192.168.1.0")) }; - yield return new object[] { new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")) }; - yield return new object[] { new IPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::FFFF:4321")) }; - } - - [Theory] - [MemberData(nameof(CanSerializable_Test_Values))] - public void CanSerializable_Test(IPAddressRange ipAddressRange) - { - // Arrange - var formatter = new BinaryFormatter(); - - // Act - using var writeStream = new MemoryStream(); - formatter.Serialize(writeStream, ipAddressRange); - - var bytes = writeStream.ToArray(); - var readStream = new MemoryStream(bytes); - var result = formatter.Deserialize(readStream); - - // Assert - Assert.IsType(result); - Assert.Equal(ipAddressRange, result); - } - - #endregion end: ISerializable - - #region Equals - - #region Equals(IPAddressRange) - - [Theory] - [InlineData(true, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.10")] - [InlineData(false, "192.168.1.1", "192.168.1.5", null, null)] - [InlineData(false, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.11")] - [InlineData(false, "12.168.1.1", "12.168.1.10", "192.18.1.1", "192.18.1.11")] - public void Equals_IPAddressRange_Test(bool expected, - string xHead, - string xTail, - string yHead, - string yTail) - { - // Arrange - _ = IPAddress.TryParse(xHead, out var xHeadAddress); - _ = IPAddress.TryParse(xTail, out var xTailAddress); - var xAddressRange = new IPAddressRange(xHeadAddress, xTailAddress); - - var yAddressRange = IPAddress.TryParse(yHead, out var yHeadAddress) - && IPAddress.TryParse(yTail, out var yTailAddress) - ? new IPAddressRange(yHeadAddress, yTailAddress) - : null; - - // Act - var result = xAddressRange.Equals(yAddressRange); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: Equals(IPAddressRange) - - #region Equals(object) - - [Theory] - [InlineData(true, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.10")] - [InlineData(false, "192.168.1.1", "192.168.1.5", null, null)] - [InlineData(false, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.11")] - [InlineData(false, "12.168.1.1", "12.168.1.10", "192.18.1.1", "192.18.1.11")] - public void Equals_Object_Test(bool expected, - string xHead, - string xTail, - string yHead, - string yTail) - { - // Arrange - _ = IPAddress.TryParse(xHead, out var xHeadAddress); - _ = IPAddress.TryParse(xTail, out var xTailAddress); - var xAddressRange = new IPAddressRange(xHeadAddress, xTailAddress); - - var yAddressRange = IPAddress.TryParse(yHead, out var yHeadAddress) - && IPAddress.TryParse(yTail, out var yTailAddress) - ? new IPAddressRange(yHeadAddress, yTailAddress) - : null; - - // Act - var result = xAddressRange.Equals((object)yAddressRange); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: Equals(object) - - #endregion // end: Equals - - #region Head set - - [Fact] - public void Head_Set_GreaterThanTail_Test() - { - // Arrange - // Act - // Assert - Assert.Throws(() => new IPAddressRange(IPAddress.Broadcast, IPAddress.Any)); - } - - [Fact] - public void Head_Set_DifferentAddressFamilyThanTail_Throw_InvalidOperationException_Test() - { - // Arrange - // Act - // Assert - Assert.Throws(() => new IPAddressRange(IPAddress.IPv6Any, IPAddress.Broadcast)); - } - - #endregion - - #region Tail set - - [Fact] - public void Tail_Set_DifferentAddressFamilyThanHead_Throw_InvalidOperationException_Test() - { - // Arrange - // Act - // Assert - Assert.Throws(() => new IPAddressRange(IPAddress.Any, IPAddress.IPv6Loopback)); - } - - [Fact] - public void Tail_Set_LessThanHead_Test() - { - // Arrange - // Act - // Assert - Assert.Throws(() => new IPAddressRange(IPAddress.Broadcast, IPAddress.Any)); - } - - #endregion - - #region TryCollapseAll - - [Fact] - public void TryCollapseAll_Consecutive_Test() - { - // Arrange - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), - new IPAddressRange(IPAddress.Parse("192.168.1.6"), IPAddress.Parse("192.168.1.7")), - new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")) - }; - - // Act - var success = IPAddressRange.TryCollapseAll(ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var collection = results.ToList(); - Assert.Single(collection); - - var result = collection.Single(); - Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); - Assert.Equal(IPAddress.Parse("192.168.1.20"), result.Tail); - } - - [Fact] - public void TryCollapse_MismatchedAddressFamilies_Test() - { - // Arrange - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), - new IPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("abcd::ef00")) - }; - - // Act - var success = IPAddressRange.TryCollapseAll(ranges, out var results); - - // Assert - Assert.False(success); - Assert.NotNull(results); - Assert.False(results.Any()); - } - - [Fact] - public void TryCollapseAll_EmptyInput_Test() - { - // Act - var success = IPAddressRange.TryCollapseAll(Enumerable.Empty(), out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - Assert.False(results.Any()); - } - - [Fact] - public void TryCollapse_AllInvalidInput_Test() - { - // Arrange - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), - null, - new IPAddressRange(IPAddress.Parse("192.168.1.30"), IPAddress.Parse("192.168.1.35")) - }; - - // Act - var success = IPAddressRange.TryCollapseAll(ranges, out var results); - - // Assert - Assert.False(success); - Assert.NotNull(results); - Assert.False(results.Any()); - } - - [Fact] - public void TryCollapse_AllOverlap_Test() - { - // Arrange - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), - new IPAddressRange(IPAddress.Parse("192.168.1.5"), IPAddress.Parse("192.168.1.10")), - new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")) - }; - - // Act - var success = IPAddressRange.TryCollapseAll(ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var collection = results.ToList(); - Assert.Single(collection); - - var result = collection.Single(); - Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); - Assert.Equal(IPAddress.Parse("192.168.1.20"), result.Tail); - } - - [Fact] - public void TryCollapseAll_SubsetContainsAll_Test() - { - // Arrange - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.20")), - new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")) - }; - - // Act - var success = IPAddressRange.TryCollapseAll(ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var collection = results.ToList(); - Assert.Single(collection); - - var result = collection.Single(); - Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); - Assert.Equal(IPAddress.Parse("192.168.1.20"), result.Tail); - } - - [Fact] - public void TryCollapseAll_WithGaps_Test() - { - // Arrange - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), - new IPAddressRange(IPAddress.Parse("192.168.1.7"), IPAddress.Parse("192.168.1.20")), - new IPAddressRange(IPAddress.Parse("192.168.1.30"), IPAddress.Parse("192.168.1.35")) - }; - - // Act - var success = IPAddressRange.TryCollapseAll(ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var enumerable = results.ToList(); - Assert.Equal(3, enumerable.Count); - Assert.Equal(enumerable, ranges.ToList()); - } - - #endregion // end: TryCollapseAll - - #region TryExcludeAll - - [Fact] - public void TryExcludeAll_Carve_Test() - { - // Arrange - var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.200")); - - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.10")), - new IPAddressRange(IPAddress.Parse("192.168.1.50"), IPAddress.Parse("192.168.1.100")), - new IPAddressRange(IPAddress.Parse("192.168.1.150"), IPAddress.Parse("192.168.1.200")) - }; - - // Act - var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var enumerable = results.ToList(); - Assert.Equal(2, enumerable.Count); - - Assert.Equal(enumerable, - new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.11"), IPAddress.Parse("192.168.1.49")), - new IPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.149")) - }.ToList()); - } - - [Fact] - public void TryExcludeAll_ConsecutiveCarve_Test() - { - // Arrange - var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.200")); - - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.100")), - new IPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.199")) - }; - - // Act - var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var enumerable = results.ToList(); - Assert.Equal(2, enumerable.Count); - - Assert.Equal(enumerable, - new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.0")), - new IPAddressRange(IPAddress.Parse("192.168.1.200"), IPAddress.Parse("192.168.1.200")) - }.ToList()); - } - - [Fact] - public void TryExcludeAll_Head_Test() - { - // Arrange - var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")); - - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.50")) - }; - - // Act - var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var collection = results.ToList(); - Assert.Single(collection); - - var result = collection.Single(); - - Assert.Equal(IPAddress.Parse("192.168.1.51"), result.Head); - Assert.Equal(IPAddress.Parse("192.168.1.100"), result.Tail); - } - - [Fact] - public void TryExcludeAll_Overlap_Test() - { - // Arrange - var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")); - - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.49")), - new IPAddressRange(IPAddress.Parse("192.168.1.50"), IPAddress.Parse("192.168.1.75")), - new IPAddressRange(IPAddress.Parse("192.168.1.75"), IPAddress.Parse("192.168.1.100")) - }; - - // Act - var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); - - //Assert - Assert.True(success); - Assert.Empty(results); - } - - [Fact] - public void TryExcludeAll_Tail_Test() - { - // Arrange - var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")); - - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.50"), IPAddress.Parse("192.168.1.100")) - }; - - // Act - var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var collection = results.ToList(); - Assert.Single(collection); - - var result = collection.Single(); - - Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); - Assert.Equal(IPAddress.Parse("192.168.1.49"), result.Tail); - } - - [Fact] - public void TryExcludeAll_NoExclusions_Test() - { - // Arrange - var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.200")); - - // Act - var success = IPAddressRange.TryExcludeAll(initialRange, Enumerable.Empty(), out var results); - - // Assert - Assert.True(success); - Assert.NotNull(results); - var collection = results.ToList(); - Assert.Single(collection); - - Assert.Equal(initialRange, collection.Single()); - } - - [Fact] - public void TryExcludeAll_InitialMissMatchedAddressFamily_Test() - { - // Arrange - var initialRange = new IPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")); - - var ranges = new[] - { - new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.10")) - }; - - // Act - var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); - - // Assert - Assert.False(success); - Assert.Empty(results); - } - - #endregion // end: TryExcludeAll - - #region Formatting - - #region ToString - - [Theory] - [InlineData("192.168.1.1", "192.168.1.42")] - [InlineData("::beef", "0123::dead")] - public void ToString_MatchesGeneralFormat_Test(string headString, - string tailString) - { - // Arrange - var head = IPAddress.Parse(headString); - var tail = IPAddress.Parse(tailString); - - var iPAddressRange = new IPAddressRange(head, tail); - - // Act - var result = iPAddressRange.ToString(); - - // Assert - Assert.Equal($"{iPAddressRange:G}", result); - } - - #endregion // end: ToString - - #region ToString(string, IFormatProvider) - - public static IEnumerable ToString_Format_Test_Values() - { - // general formats - foreach (var format in new[] { null, string.Empty, "g", "G" }) - { - foreach (var ipAddressRange in Ipv4AddressRanges() - .Concat(Ipv6AddressRanges())) + public class IPAddressRangeTests + { + #region GetHashCode + + [Theory] + [InlineData(true, "192.168.1.5", "192.168.1.100", "192.168.1.5", "192.168.1.100")] + [InlineData(false, "192.168.1.5", "192.168.1.100", "10.168.1.0", "10.168.1.100")] + [InlineData(true, "::abcd", "ff:12::abcd", "::abcd", "ff:12::abcd")] + [InlineData(false, "::abcd", "ff:12::abcd", "::ef", "ff:12::1234")] + public void GetHashCode_Test(bool expected, string xHead, string xTail, string yHead, string yTail) + { + // Arrange + _ = IPAddress.TryParse(xHead, out var xHeadAddress); + _ = IPAddress.TryParse(xTail, out var xTailAddress); + + var xAddressRange = new IPAddressRange(xHeadAddress, xTailAddress); + + _ = IPAddress.TryParse(yHead, out var yHeadAddress); + _ = IPAddress.TryParse(yTail, out var yTailAddress); + + var yAddressRange = new IPAddressRange(yHeadAddress, yTailAddress); + + // Act + var xHash = xAddressRange.GetHashCode(); + var yHash = yAddressRange.GetHashCode(); + var result = xHash.Equals(yHash); + + // Assert + Assert.Equal(expected, result); + } + + #endregion // end: GetHashCode + + #region HeadOverlappedBy + + [Theory] + [InlineData(false, "192.168.1.0", "255.255.255.255", null, null)] + [InlineData(false, "192.168.1.0", "255.255.255.255", "::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:")] + [InlineData(false, "192.168.1.0", "255.255.255.255", "192.168.1.1", "255.255.255.255")] + [InlineData(false, "192.168.1.0", "255.255.255.255", "0.0.0.0", "192.168.0.255")] + [InlineData(true, "192.168.1.0", "255.255.255.255", "0.0.0.0", "255.255.255.255")] + [InlineData(true, "192.168.1.0", "255.255.255.255", "192.168.1.0", "192.168.1.0")] + public void HeadOverlappedBy_Test(bool expected, string thisHead, string thisTail, string thatHead, string thatTail) + { + // Arrange + // this + _ = IPAddress.TryParse(thisHead, out var thisHeadAddress); + _ = IPAddress.TryParse(thisTail, out var thisTailAddress); + + var thisAddressRange = new IPAddressRange(thisHeadAddress, thisTailAddress); + + // that + var thatAddressRange = + IPAddress.TryParse(thatHead, out var thatHeadAddress) && IPAddress.TryParse(thatTail, out var thatTailAddress) + ? new IPAddressRange(thatHeadAddress, thatTailAddress) + : null; + + // Act + var result = thisAddressRange.HeadOverlappedBy(thatAddressRange); + + // Assert + Assert.Equal(expected, result); + } + + #endregion // end: HeadOverlappedBy + + #region Class + + [Theory] + [InlineData(typeof(AbstractIPAddressRange))] + [InlineData(typeof(IEquatable))] + [InlineData(typeof(IComparable))] + [InlineData(typeof(IComparable))] +#if NET48 + [InlineData(typeof(ISerializable))] +#endif + public void Assignability_Test(Type assignableFromType) + { + // Arrange + var type = typeof(IPAddressRange); + + // Act + var isAssignableFrom = assignableFromType.IsAssignableFrom(type); + + // Assert + Assert.True(isAssignableFrom); + } + + #endregion //end: Class + + #region CompareTo / Operators + + public static IEnumerable Comparison_Values() + { + var ipv4Slash16 = Subnet.Parse("192.168.0.0/16"); + var ipv6Slash64 = Subnet.Parse("ab:cd::/64"); + var ipv4Slash20 = Subnet.Parse("192.168.0.0/20"); + var ipv6Slash96 = Subnet.Parse("ab:cd::/96"); + var ipv4All = Subnet.Parse("0.0.0.0/0"); + var ipv6All = Subnet.Parse("::/0"); + var ipv4Single = Subnet.Parse("0.0.0.0/32"); + var ipv6Single = Subnet.Parse("::/128"); + + yield return new object[] + { + 0, + new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), + new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), + }; + yield return new object[] + { + 0, + new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), + new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), + }; + yield return new object[] { 1, new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), null }; + yield return new object[] { 1, new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), null }; + yield return new object[] + { + 1, + new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), + new IPAddressRange(ipv4Slash20.Head, ipv4Slash20.Tail), + }; + yield return new object[] + { + -1, + new IPAddressRange(ipv4Slash20.Head, ipv4Slash20.Tail), + new IPAddressRange(ipv4Slash16.Head, ipv4Slash16.Tail), + }; + yield return new object[] + { + 1, + new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), + new IPAddressRange(ipv6Slash96.Head, ipv6Slash96.Tail), + }; + yield return new object[] + { + -1, + new IPAddressRange(ipv6Slash96.Head, ipv6Slash96.Tail), + new IPAddressRange(ipv6Slash64.Head, ipv6Slash64.Tail), + }; + yield return new object[] + { + -1, + new IPAddressRange(ipv4All.Head, ipv4All.Tail), + new IPAddressRange(ipv6All.Head, ipv6All.Tail), + }; + yield return new object[] + { + 1, + new IPAddressRange(ipv6All.Head, ipv6All.Tail), + new IPAddressRange(ipv4All.Head, ipv4All.Tail), + }; + yield return new object[] + { + -1, + new IPAddressRange(ipv4Single.Head, ipv4Single.Tail), + new IPAddressRange(ipv6Single.Head, ipv6Single.Tail), + }; + yield return new object[] + { + 1, + new IPAddressRange(ipv6Single.Head, ipv6Single.Tail), + new IPAddressRange(ipv4Single.Head, ipv4Single.Tail), + }; + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void CompareTo_Test(int expected, IPAddressRange left, IPAddressRange right) + { + // Arrange + // Act + var result = left.CompareTo(right); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_Equals_Test(int expected, IPAddressRange left, IPAddressRange right) + { + // Arrange + // Act + var result = left == right; + + // Assert + Assert.Equal(expected == 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_NotEquals_Test(int expected, IPAddressRange left, IPAddressRange right) + { + // Arrange + // Act + var result = left != right; + + // Assert + Assert.Equal(expected != 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_GreaterThan_Test(int expected, IPAddressRange left, IPAddressRange right) + { + // Arrange + // Act + var result = left > right; + + // Assert + Assert.Equal(expected > 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_GreaterThanOrEqual_Test(int expected, IPAddressRange left, IPAddressRange right) + { + // Arrange + // Act + var result = left >= right; + + // Assert + Assert.Equal(expected >= 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_LessThan_Test(int expected, IPAddressRange left, IPAddressRange right) + { + // Arrange + // Act + var result = left < right; + + // Assert + Assert.Equal(expected < 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_LessThanOrEqual_Test(int expected, IPAddressRange left, IPAddressRange right) + { + // Arrange + // Act + var result = left <= right; + + // Assert + Assert.Equal(expected <= 0, result); + } + + #endregion end CompareTo / Operators + + #region TailOverlappedBy + + [Theory] + [InlineData(false, "0.0.0.0", "192.168.1.0", null, null)] + [InlineData(false, "0.0.0.0", "192.168.1.0", "::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:")] + [InlineData(false, "0.0.0.0", "192.168.1.0", "192.168.1.1", "255.255.255.255")] + [InlineData(false, "0.0.0.0", "192.168.1.0", "0.0.0.0", "192.168.0.255")] + [InlineData(true, "0.0.0.0", "192.168.1.0", "0.0.0.0", "255.255.255.255")] + [InlineData(true, "0.0.0.0", "192.168.1.0", "192.168.1.0", "192.168.1.0")] + public void TailOverlappedBy_Test(bool expected, string thisHead, string thisTail, string thatHead, string thatTail) + { + // Arrange + // this + _ = IPAddress.TryParse(thisHead, out var thisHeadAddress); + _ = IPAddress.TryParse(thisTail, out var thisTailAddress); + + var thisAddressRange = new IPAddressRange(thisHeadAddress, thisTailAddress); + + // that + var thatAddressRange = + IPAddress.TryParse(thatHead, out var thatHeadAddress) && IPAddress.TryParse(thatTail, out var thatTailAddress) + ? new IPAddressRange(thatHeadAddress, thatTailAddress) + : null; + + // Act + var result = thisAddressRange.TailOverlappedBy(thatAddressRange); + + // Assert + Assert.Equal(expected, result); + } + + #endregion // end: TailOverlappedBy + + #region TryMerge + + [Theory] + [InlineData(null, null, null, null, null)] + [InlineData(null, "192.168.1.1", "192.168.1.9", "::", "::")] + [InlineData(null, "192.168.1.1", "192.168.1.9", "192.168.1.11", "192.168.1.20")] + [InlineData(null, "192.168.1.1", "192.168.1.9", null, null)] + [InlineData(null, null, null, "192.168.1.1", "192.168.1.9")] + [InlineData("192.168.1.1-192.168.1.20", "192.168.1.1", "192.168.1.10", "192.168.1.11", "192.168.1.20")] + [InlineData("192.168.1.1-192.168.1.20", "192.168.1.1", "192.168.1.10", "192.168.1.10", "192.168.1.20")] + [InlineData("192.168.1.1-192.168.1.20", "192.168.1.11", "192.168.1.20", "192.168.1.1", "192.168.1.10")] + [InlineData("192.168.1.1-192.168.1.20", "192.168.1.10", "192.168.1.20", "192.168.1.1", "192.168.1.10")] + [InlineData("192.168.1.10-192.168.1.20", "192.168.1.10", "192.168.1.20", "192.168.1.10", "192.168.1.20")] + [InlineData("::-::", "::", "::", "::", "::")] + [InlineData("::1-::4", "::1", "::2", "::3", "::4")] + [InlineData(null, null, null, "::", "::")] + [InlineData(null, "::", "::", null, null)] + public void TryMergeResultTest(string expected, string alphaHead, string alphaTail, string betaHead, string betaTail) + { + // Arrange + var alphaAddressRange = + IPAddress.TryParse(alphaHead, out var alphaHeadAddress) + && IPAddress.TryParse(alphaTail, out var alphaTailAddress) + ? new IPAddressRange(alphaHeadAddress, alphaTailAddress) + : null; + + var betaAddressRange = + IPAddress.TryParse(betaHead, out var betaHeadAddress) && IPAddress.TryParse(betaTail, out var betaTailAddress) + ? new IPAddressRange(betaHeadAddress, betaTailAddress) + : null; + + // Act + var successResult = IPAddressRange.TryMerge(alphaAddressRange, betaAddressRange, out var mergeResult); + + // Assert + Assert.Equal(expected != null, successResult); + + if (expected == null) + { + Assert.Null(mergeResult); + } + else + { + Assert.NotNull(mergeResult); + Assert.Equal(expected, $"{mergeResult.Head}-{mergeResult.Tail}"); + } + } + + #endregion + + #region Ctor + + [Fact] + public void Ctor_HeadAndTail_Specified_Test() + { + // Act + var head = IPAddress.Any; + var tail = IPAddress.Broadcast; + + // Act + var addressRange = new IPAddressRange(head, tail); + + // Assert + Assert.Equal(head, addressRange.Head); + Assert.Equal(tail, addressRange.Tail); + } + + [Fact] + public void Ctor_SingleAddressRange_Test() + { + // Act + var address = IPAddress.Any; + + // Act + var addressRange = new IPAddressRange(address); + + // Assert + Assert.Equal(address, addressRange.Head); + Assert.Equal(address, addressRange.Tail); + } + + #endregion + + #region ISerializable +#if NET48 // maintained for .NET 4.8 compatibility + public static IEnumerable CanSerializable_Test_Values() + { + yield return new object[] { new IPAddressRange(IPAddress.Parse("192.168.1.0")) }; + yield return new object[] { new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")) }; + yield return new object[] { new IPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("::FFFF:4321")) }; + } + + [Theory] + [MemberData(nameof(CanSerializable_Test_Values))] + public void CanSerializable_Test(IPAddressRange ipAddressRange) + { + // Arrange + var formatter = new BinaryFormatter(); + + // Act + using (var writeStream = new MemoryStream()) + { + // Serialize the object to the stream + formatter.Serialize(writeStream, ipAddressRange); + writeStream.Seek(0, SeekOrigin.Begin); + + // Deserialize the object from the stream + var result = formatter.Deserialize(writeStream); + + // Assert + var actual = Assert.IsType(result); + + // using explicit EqualityComparer to avoid comparing elements of enumerable + Assert.Equal(ipAddressRange, actual, IPAddressRangeEqualityComparer.Instance); + } + } +#endif + #endregion end: ISerializable + + #region Equals + + #region Equals(IPAddressRange) + + [Theory] + [InlineData(true, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.10")] + [InlineData(false, "192.168.1.1", "192.168.1.5", null, null)] + [InlineData(false, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.11")] + [InlineData(false, "12.168.1.1", "12.168.1.10", "192.18.1.1", "192.18.1.11")] + public void Equals_IPAddressRange_Test(bool expected, string xHead, string xTail, string yHead, string yTail) + { + // Arrange + _ = IPAddress.TryParse(xHead, out var xHeadAddress); + _ = IPAddress.TryParse(xTail, out var xTailAddress); + var xAddressRange = new IPAddressRange(xHeadAddress, xTailAddress); + + var yAddressRange = + IPAddress.TryParse(yHead, out var yHeadAddress) && IPAddress.TryParse(yTail, out var yTailAddress) + ? new IPAddressRange(yHeadAddress, yTailAddress) + : null; + + // Act + var result = xAddressRange.Equals(yAddressRange); + + // Assert + Assert.Equal(expected, result); + } + + #endregion // end: Equals(IPAddressRange) + + #region Equals(object) + + [Theory] + [InlineData(true, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.10")] + [InlineData(false, "192.168.1.1", "192.168.1.5", null, null)] + [InlineData(false, "192.168.1.1", "192.168.1.10", "192.168.1.1", "192.168.1.11")] + [InlineData(false, "12.168.1.1", "12.168.1.10", "192.18.1.1", "192.18.1.11")] + public void Equals_Object_Test(bool expected, string xHead, string xTail, string yHead, string yTail) + { + // Arrange + _ = IPAddress.TryParse(xHead, out var xHeadAddress); + _ = IPAddress.TryParse(xTail, out var xTailAddress); + var xAddressRange = new IPAddressRange(xHeadAddress, xTailAddress); + + var yAddressRange = + IPAddress.TryParse(yHead, out var yHeadAddress) && IPAddress.TryParse(yTail, out var yTailAddress) + ? new IPAddressRange(yHeadAddress, yTailAddress) + : null; + + // Act + var result = xAddressRange.Equals((object)yAddressRange); + + // Assert + Assert.Equal(expected, result); + } + + #endregion // end: Equals(object) + + #endregion // end: Equals + + #region Head set + + [Fact] + public void Head_Set_GreaterThanTail_Test() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new IPAddressRange(IPAddress.Broadcast, IPAddress.Any)); + } + + [Fact] + public void Head_Set_DifferentAddressFamilyThanTail_Throw_InvalidOperationException_Test() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new IPAddressRange(IPAddress.IPv6Any, IPAddress.Broadcast)); + } + + #endregion + + #region Tail set + + [Fact] + public void Tail_Set_DifferentAddressFamilyThanHead_Throw_InvalidOperationException_Test() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new IPAddressRange(IPAddress.Any, IPAddress.IPv6Loopback)); + } + + [Fact] + public void Tail_Set_LessThanHead_Test() + { + // Arrange + // Act + // Assert + Assert.Throws(() => new IPAddressRange(IPAddress.Broadcast, IPAddress.Any)); + } + + #endregion + + #region TryCollapseAll + + [Fact] + public void TryCollapseAll_Consecutive_Test() + { + // Arrange + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), + new IPAddressRange(IPAddress.Parse("192.168.1.6"), IPAddress.Parse("192.168.1.7")), + new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")), + }; + + // Act + var success = IPAddressRange.TryCollapseAll(ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var collection = results.ToList(); + Assert.Single(collection); + + var result = collection.Single(); + Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); + Assert.Equal(IPAddress.Parse("192.168.1.20"), result.Tail); + } + + [Fact] + public void TryCollapse_MismatchedAddressFamilies_Test() + { + // Arrange + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), + new IPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("abcd::ef00")), + }; + + // Act + var success = IPAddressRange.TryCollapseAll(ranges, out var results); + + // Assert + Assert.False(success); + Assert.NotNull(results); + Assert.False(results.Any()); + } + + [Fact] + public void TryCollapseAll_EmptyInput_Test() + { + // Act + var success = IPAddressRange.TryCollapseAll(Enumerable.Empty(), out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + Assert.False(results.Any()); + } + + [Fact] + public void TryCollapse_AllInvalidInput_Test() + { + // Arrange + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), + null, + new IPAddressRange(IPAddress.Parse("192.168.1.30"), IPAddress.Parse("192.168.1.35")), + }; + + // Act + var success = IPAddressRange.TryCollapseAll(ranges, out var results); + + // Assert + Assert.False(success); + Assert.NotNull(results); + Assert.False(results.Any()); + } + + [Fact] + public void TryCollapse_AllOverlap_Test() + { + // Arrange + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), + new IPAddressRange(IPAddress.Parse("192.168.1.5"), IPAddress.Parse("192.168.1.10")), + new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")), + }; + + // Act + var success = IPAddressRange.TryCollapseAll(ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var collection = results.ToList(); + Assert.Single(collection); + + var result = collection.Single(); + Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); + Assert.Equal(IPAddress.Parse("192.168.1.20"), result.Tail); + } + + [Fact] + public void TryCollapseAll_SubsetContainsAll_Test() + { + // Arrange + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.20")), + new IPAddressRange(IPAddress.Parse("192.168.1.8"), IPAddress.Parse("192.168.1.20")), + }; + + // Act + var success = IPAddressRange.TryCollapseAll(ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var collection = results.ToList(); + Assert.Single(collection); + + var result = collection.Single(); + Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); + Assert.Equal(IPAddress.Parse("192.168.1.20"), result.Tail); + } + + [Fact] + public void TryCollapseAll_WithGaps_Test() + { + // Arrange + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.5")), + new IPAddressRange(IPAddress.Parse("192.168.1.7"), IPAddress.Parse("192.168.1.20")), + new IPAddressRange(IPAddress.Parse("192.168.1.30"), IPAddress.Parse("192.168.1.35")), + }; + + // Act + var success = IPAddressRange.TryCollapseAll(ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var enumerable = results.ToList(); + Assert.Equal(3, enumerable.Count); + Assert.Equal(enumerable, ranges.ToList()); + } + + #endregion // end: TryCollapseAll + + #region TryExcludeAll + + [Fact] + public void TryExcludeAll_Carve_Test() + { + // Arrange + var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.200")); + + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.10")), + new IPAddressRange(IPAddress.Parse("192.168.1.50"), IPAddress.Parse("192.168.1.100")), + new IPAddressRange(IPAddress.Parse("192.168.1.150"), IPAddress.Parse("192.168.1.200")), + }; + + // Act + var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var enumerable = results.ToList(); + Assert.Equal(2, enumerable.Count); + + Assert.Equal( + enumerable, + new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.11"), IPAddress.Parse("192.168.1.49")), + new IPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.149")), + }.ToList() + ); + } + + [Fact] + public void TryExcludeAll_ConsecutiveCarve_Test() + { + // Arrange + var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.200")); + + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.100")), + new IPAddressRange(IPAddress.Parse("192.168.1.101"), IPAddress.Parse("192.168.1.199")), + }; + + // Act + var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var enumerable = results.ToList(); + Assert.Equal(2, enumerable.Count); + + Assert.Equal( + enumerable, + new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.0")), + new IPAddressRange(IPAddress.Parse("192.168.1.200"), IPAddress.Parse("192.168.1.200")), + }.ToList() + ); + } + + [Fact] + public void TryExcludeAll_Head_Test() + { + // Arrange + var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")); + + var ranges = new[] { new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.50")) }; + + // Act + var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var collection = results.ToList(); + Assert.Single(collection); + + var result = collection.Single(); + + Assert.Equal(IPAddress.Parse("192.168.1.51"), result.Head); + Assert.Equal(IPAddress.Parse("192.168.1.100"), result.Tail); + } + + [Fact] + public void TryExcludeAll_Overlap_Test() + { + // Arrange + var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")); + + var ranges = new[] + { + new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.49")), + new IPAddressRange(IPAddress.Parse("192.168.1.50"), IPAddress.Parse("192.168.1.75")), + new IPAddressRange(IPAddress.Parse("192.168.1.75"), IPAddress.Parse("192.168.1.100")), + }; + + // Act + var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); + + //Assert + Assert.True(success); + Assert.Empty(results); + } + + [Fact] + public void TryExcludeAll_Tail_Test() + { + // Arrange + var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.100")); + + var ranges = new[] { new IPAddressRange(IPAddress.Parse("192.168.1.50"), IPAddress.Parse("192.168.1.100")) }; + + // Act + var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var collection = results.ToList(); + Assert.Single(collection); + + var result = collection.Single(); + + Assert.Equal(IPAddress.Parse("192.168.1.0"), result.Head); + Assert.Equal(IPAddress.Parse("192.168.1.49"), result.Tail); + } + + [Fact] + public void TryExcludeAll_NoExclusions_Test() + { + // Arrange + var initialRange = new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.200")); + + // Act + var success = IPAddressRange.TryExcludeAll(initialRange, Enumerable.Empty(), out var results); + + // Assert + Assert.True(success); + Assert.NotNull(results); + var collection = results.ToList(); + Assert.Single(collection); + + Assert.Equal(initialRange, collection.Single()); + } + + [Fact] + public void TryExcludeAll_InitialMissMatchedAddressFamily_Test() + { + // Arrange + var initialRange = new IPAddressRange(IPAddress.Parse("::"), IPAddress.Parse("ffff::ffff")); + + var ranges = new[] { new IPAddressRange(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.10")) }; + + // Act + var success = IPAddressRange.TryExcludeAll(initialRange, ranges, out var results); + + // Assert + Assert.False(success); + Assert.Empty(results); + } + + #endregion // end: TryExcludeAll + + #region Formatting + + #region ToString + + [Theory] + [InlineData("192.168.1.1", "192.168.1.42")] + [InlineData("::beef", "0123::dead")] + public void ToString_MatchesGeneralFormat_Test(string headString, string tailString) + { + // Arrange + var head = IPAddress.Parse(headString); + var tail = IPAddress.Parse(tailString); + + var iPAddressRange = new IPAddressRange(head, tail); + + // Act + var result = iPAddressRange.ToString(); + + // Assert + Assert.Equal($"{iPAddressRange:G}", result); + } + + #endregion // end: ToString + + #region ToString(string, IFormatProvider) + + public static IEnumerable ToString_Format_Test_Values() + { + // general formats + foreach (var format in new[] { null, string.Empty, "g", "G" }) + { + foreach (var ipAddressRange in Ipv4AddressRanges().Concat(Ipv6AddressRanges())) + { + yield return new object[] + { + $"{ipAddressRange.Head} - {ipAddressRange.Tail}", + format, + CultureInfo.CurrentCulture, + ipAddressRange, + }; + } + } + + IEnumerable Ipv4AddressRanges() + { + yield return new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.42")); + yield return new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.1")); + } + + IEnumerable Ipv6AddressRanges() + { + yield return new IPAddressRange(IPAddress.Parse("::beef"), IPAddress.Parse("0123::dead")); + yield return new IPAddressRange(IPAddress.Parse("::beef"), IPAddress.Parse("::beef")); + } + } + + [Theory] + [MemberData(nameof(ToString_Format_Test_Values))] + public void ToString_Format_Test( + string expected, + string format, + IFormatProvider formatProvider, + IPAddressRange ipAddressRange + ) + { + // Arrange + // Act + var result = ipAddressRange.ToString(format, formatProvider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void ToString_UnknownFormat_Throws_FormatException_Test() + { + // Arrange + var range = new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.42")); + + // Act + // Assert + Assert.Throws(() => range.ToString("potato", CultureInfo.CurrentCulture)); + } + + #endregion // end: ToString(string, IFormatProvider) + + #endregion // end: Formatting + + internal class IPAddressRangeEqualityComparer : IEqualityComparer + { + public static readonly IPAddressRangeEqualityComparer Instance = new IPAddressRangeEqualityComparer(); + + public bool Equals(IPAddressRange x, IPAddressRange y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (x is null && y is null) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + return x.Equals(y); + } + + public int GetHashCode(IPAddressRange obj) { - yield return new object[] { $"{ipAddressRange.Head} - {ipAddressRange.Tail}", format, CultureInfo.CurrentCulture, ipAddressRange }; + return obj is null ? -1 : obj.GetHashCode(); } - } - - IEnumerable Ipv4AddressRanges() - { - yield return new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.42")); - yield return new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.1")); - } - - IEnumerable Ipv6AddressRanges() - { - yield return new IPAddressRange(IPAddress.Parse("::beef"), IPAddress.Parse("0123::dead")); - yield return new IPAddressRange(IPAddress.Parse("::beef"), IPAddress.Parse("::beef")); - } - } - - [Theory] - [MemberData(nameof(ToString_Format_Test_Values))] - public void ToString_Format_Test(string expected, - string format, - IFormatProvider formatProvider, - IPAddressRange ipAddressRange) - { - // Arrange - // Act - var result = ipAddressRange.ToString(format, formatProvider); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void ToString_UnknownFormat_Throws_FormatException_Test() - { - // Arrange - var range = new IPAddressRange(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.42")); - - // Act - // Assert - Assert.Throws(() => range.ToString("potato", CultureInfo.CurrentCulture)); - } - - #endregion // end: ToString(string, IFormatProvider) - - #endregion // end: Formatting - } + } + } } diff --git a/src/Arcus.Tests/MacAddressTests.cs b/src/Arcus.Tests/MacAddressTests.cs index 026cd77..3a0afdc 100644 --- a/src/Arcus.Tests/MacAddressTests.cs +++ b/src/Arcus.Tests/MacAddressTests.cs @@ -1,989 +1,923 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; using System.Text.RegularExpressions; using Gulliver; using Xunit; +#if NET48 // maintained for .NET 4.8 compatibility +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +#endif namespace Arcus.Tests { - /// Tests for - public class MacAddressTests - { - #region IComparable - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void CompareTo_MacAddress_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left.CompareTo(right); - - // Assert - Assert.Equal(expected, result); - } - - #endregion end: IComparable - - #region DefaultMacAddress - - [Fact] - public void DefaultMacAddress_Test() - { - // Arrange - var expected = Enumerable.Repeat((byte)0xFF, 6) - .ToArray(); - - // Act - var defaultMacAddress = MacAddress.DefaultMacAddress; - - // Assert - Assert.NotNull(defaultMacAddress); - Assert.Equal(0, ByteArrayUtils.CompareUnsignedBigEndian(expected, defaultMacAddress.GetAddressBytes())); - } - - #endregion end: DefaultMacAddress - - #region GetAddressBytes - - [Theory] - [InlineData(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(new byte[] { 0x00, 0xff, 0x00, 0xff, 0x00, 0x00 })] - public void GetAddressBytes_Test(byte[] bytes) - { - // Arrange - var macAddress = new MacAddress(bytes); - - // Act - var addressBytes = macAddress.GetAddressBytes(); - - // Assert - Assert.NotNull(addressBytes); - Assert.IsType(addressBytes); - Assert.Equal(addressBytes.Length, addressBytes.Length); - Assert.Equal(0, ByteArrayUtils.CompareUnsignedBigEndian(bytes, addressBytes)); - } - - #endregion end: GetAddressBytes - - #region GetCidBytes - - [Fact] - public void GetCidBytes_Test() - { - // Arrange - var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB }; - - var macAddress = new MacAddress(bytes); - - // Act - var cidBytes = macAddress.GetCidBytes(); - - // Assert - Assert.Equal(0, - ByteArrayUtils.CompareUnsignedBigEndian(bytes.Skip(3) - .Take(3) - .ToArray(), - cidBytes)); - } - - #endregion end: GetCidBytes - - #region GetHashCode - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void GetHashCode_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - if (left is null - || right is null) - { - return; // skip null values and bail - } - - // Act - var leftHashCode = left.GetHashCode(); - var rightHashCode = right.GetHashCode(); - - // Assert - Assert.Equal(expected == 0, leftHashCode == rightHashCode); - } - - #endregion end: GetHashCode - - #region GetOuiBytes - - [Fact] - public void GetOuiBytes_Test() - { - // Arrange - var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB }; - - var macAddress = new MacAddress(bytes); - - // Act - var ouiBytes = macAddress.GetOuiBytes(); - - // Assert - Assert.Equal(0, - ByteArrayUtils.CompareUnsignedBigEndian(bytes.Take(3) - .ToArray(), - ouiBytes)); - } - - #endregion end: GetOuiBytes - - #region IsGloballyUnique - - [Theory] - [InlineData(true, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(false, new byte[] { 0b0000_0010, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(true, new byte[] { 0b1111_1101, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - [InlineData(false, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - public void IsGloballyUnique_Test(bool expected, - byte[] bytes) - { - // Arrange - var macAddress = new MacAddress(bytes); - - // Act - var isGloballyUnique = macAddress.IsGloballyUnique; - - // Assert - Assert.Equal(expected, isGloballyUnique); - } - - #endregion end: IsGloballyUnique - - #region IsLocallyAdministered - - [Theory] - [InlineData(false, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(true, new byte[] { 0b0000_0010, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(false, new byte[] { 0b1111_1101, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - [InlineData(true, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - public void IsLocallyAdministered_Test(bool expected, - byte[] bytes) - { - // Arrange - var macAddress = new MacAddress(bytes); - - // Act - var isLocallyAdministered = macAddress.IsLocallyAdministered; - - // Assert - Assert.Equal(expected, isLocallyAdministered); - } - - #endregion end: IsLocallyAdministered - - #region IsMulticast - - [Theory] - [InlineData(false, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(true, new byte[] { 0b0000_0001, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(false, new byte[] { 0b1111_1110, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - [InlineData(true, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - public void IsMulticast_Test(bool expected, - byte[] bytes) - { - // Arrange - var macAddress = new MacAddress(bytes); - - // Act - var isMulticast = macAddress.IsMulticast; - - // Assert - Assert.Equal(expected, isMulticast); - } - - #endregion end: IsMulticast - - #region IsUnicast - - [Theory] - [InlineData(true, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(false, new byte[] { 0b0000_0001, 0x00, 0x00, 0x00, 0x00, 0x00 })] - [InlineData(true, new byte[] { 0b1111_1110, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - [InlineData(false, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] - public void IsUnicast_Test(bool expected, - byte[] bytes) - { - // Arrange - var macAddress = new MacAddress(bytes); - - // Act - var isUnicast = macAddress.IsUnicast; - - // Assert - Assert.Equal(expected, isUnicast); - } - - #endregion end: IsUnicast - - #region other members - - public static IEnumerable Comparison_MacAddress_MacAddress_Values() - { - var byteArrayMax = Enumerable.Repeat((byte)0xff, 6) - .ToArray(); - - var byteArrayMin = Enumerable.Repeat((byte)0x00, 6) - .ToArray(); - - var macAddressMax = new MacAddress(byteArrayMax); - - var macAddressMin = new MacAddress(byteArrayMin); - - yield return new object[] { 0, macAddressMin, macAddressMin }; // same equal - yield return new object[] { 0, macAddressMin, new MacAddress(byteArrayMin) }; // same underlying bytes - - yield return new object[] { -1, macAddressMin, macAddressMax }; // left less than right - yield return new object[] { 1, macAddressMax, macAddressMin }; // left greater than right - - yield return new object[] { 1, macAddressMin, null }; // right is null - } - - public static IEnumerable Comparison_MacAddress_Object_Values() - { - var bytes = Enumerable.Repeat((byte)0xff, 6) - .ToArray(); - var macAddress = new MacAddress(bytes); - - yield return new object[] { -1, macAddress, "string" }; - yield return new object[] { -1, macAddress, 42 }; - yield return new object[] { -1, macAddress, bytes }; - } - - #endregion - - #region IsDefault - - public static IEnumerable IsDefault_Test_Values() - { - yield return new object[] { true, MacAddress.DefaultMacAddress }; - yield return new object[] { true, new MacAddress(Enumerable.Repeat((byte)0xFF, 6)) }; - yield return new object[] { false, new MacAddress(Enumerable.Repeat((byte)0x00, 6)) }; - } - - [Theory] - [MemberData(nameof(IsDefault_Test_Values))] - public void IsDefault_Test(bool expeted, - MacAddress macAddress) - { - // Arrange - // Act - var isDefault = macAddress.IsDefault; - - // Assert - Assert.Equal(expeted, isDefault); - } - - #endregion end: IsDefault - - #region IsUnusable - - public static IEnumerable IsUnusable_Test_Values() - { - yield return new object[] { false, new MacAddress(Enumerable.Repeat((byte)0x00, 6)) }; - yield return new object[] - { - false, new MacAddress(Enumerable.Repeat((byte) 0x00, 3) - .Concat(Enumerable.Repeat((byte) 0xFF, 3))) - }; - yield return new object[] { true, new MacAddress(Enumerable.Repeat((byte)0x01, 6)) }; - yield return new object[] { true, new MacAddress(Enumerable.Repeat((byte)0xFF, 6)) }; - } - - [Theory] - [MemberData(nameof(IsUnusable_Test_Values))] - public void IsUnusable_Test(bool expected, - MacAddress macAddress) - { - // Arrange - - // Act - var isUsable = macAddress.IsUnusable; - - // Assert - Assert.Equal(expected, isUsable); - } - - #endregion end: IsUnusable - - #region ctor bytes[] - - [Theory] - [InlineData(0)] - [InlineData(5)] - [InlineData(7)] - public void Ctor_WrongByteCount_Throws_ArgumentException_Test(int byteCount) - { - // Arrange - var bytes = Enumerable.Repeat((byte)0xAC, byteCount) - .ToArray(); - - // Act - // Assert - Assert.Throws(() => new MacAddress(bytes)); - } - - [Fact] - public void Ctor_NullBytes_Throws_ArgumentNullException_Test() - { - // Arrange - // Act - // Assert - Assert.Throws(() => new MacAddress(null)); - } - - #endregion end: ctor bytes[] - - #region ISerializable - - public static IEnumerable CanSerializable_Test_Values() - { - yield return new object[] { new MacAddress(Enumerable.Repeat((byte)0x00, 6)) }; - yield return new object[] { new MacAddress(Enumerable.Repeat((byte)0xFF, 6)) }; - yield return new object[] { new MacAddress(new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }) }; - } - - [Theory] - [MemberData(nameof(CanSerializable_Test_Values))] - public void CanSerializable_Test(MacAddress macAddress) - { - // Arrange - var formatter = new BinaryFormatter(); - - // Act - using var writeStream = new MemoryStream(); - formatter.Serialize(writeStream, macAddress); - - var bytes = writeStream.ToArray(); - var readStream = new MemoryStream(bytes); - var result = formatter.Deserialize(readStream); - - // Assert - Assert.IsType(result); - Assert.Equal(macAddress, result); - } - - #endregion end: ISerializable - - #region type - - [Theory] - [InlineData(typeof(IEquatable))] - [InlineData(typeof(IComparable))] - [InlineData(typeof(IComparable))] - [InlineData(typeof(IFormattable))] - [InlineData(typeof(ISerializable))] - public void Assignability_Test(Type assignableFromType) - { - // Arrange - var type = typeof(MacAddress); - - // Act - var isAssignableFrom = assignableFromType.IsAssignableFrom(type); - - // Assert - Assert.True(isAssignableFrom); - } - - [Fact] - public void IsConcrete_Test() - { - // Arrange - var type = typeof(MacAddress); - - // Act - var isConcrete = type.IsClass && !type.IsAbstract; - - // Assert - Assert.True(isConcrete); - } - - #endregion end: type - - #region IComparable - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void CompareTo_Object_Test(int expected, - MacAddress left, - object right) - { - // Arrange - // Act - var result = left.CompareTo(right); - - // Assert - Assert.Equal(expected, result); - } - - [Theory] - [MemberData(nameof(Comparison_MacAddress_Object_Values))] - public void CompareTo_Object_NotMacAddress_Test(int expected, - MacAddress left, - object right) - { - // Arrange - // Act - // Assert - Assert.Throws(() => left.CompareTo(right)); - } - - #endregion end: IComparable - - #region Parse - - #region Parse string - - [Theory] - [MemberData(nameof(Parse_Test_SuccessValues))] - public void Parse_HappyPath_Test(MacAddress expected, - string input) - { - // Arrange - - // Act - var macAddress = MacAddress.Parse(input); - - // Assert - Assert.Equal(expected, macAddress); - } - - [Theory] - [MemberData(nameof(Parse_Test_FailValues))] - public void Parse_Failure_Test(MacAddress expected, - string input) - { - // Arrange - // Act - // Assert - Assert.ThrowsAny(() => MacAddress.Parse(input)); - } - - [Theory] - [MemberData(nameof(Parse_Test_SuccessValues))] - [MemberData(nameof(Parse_Test_FailValues))] - public void TryParse_Test(MacAddress expected, - string input) - { - // Arrange - - // Act - var success = MacAddress.TryParse(input, out var macAddress); - - // Assert - Assert.True(expected is null ^ success); - Assert.Equal(expected, macAddress); - } - - public static IEnumerable Parse_Test_FailValues() - { - yield return new object[] { null, null }; - yield return new object[] { null, string.Empty }; - yield return new object[] { null, "A" }; - yield return new object[] { null, "potato" }; - } - - public static IEnumerable Parse_Test_SuccessValues() - { - var formatProvider = CultureInfo.InvariantCulture; - var formats = new[] { "g", "c", "s", "d", "X" }; - - var bytes = new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }; - - var macAddresses = new[] - { - new MacAddress(bytes), - new MacAddress(bytes.ReverseBytes()) - }; - - foreach (var macAddress in macAddresses) - { - foreach (var format in formats) +#if NET6_0_OR_GREATER +#pragma warning disable IDE0062 // Make local function static (IDE0062); purposely allowing non-static functions that could be static for .net4.8 compatibility +#endif + + /// Tests for + [Obsolete("MacAddress is a candidate for removal in future major version")] + public class MacAddressTests + { + #region IComparable + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void CompareTo_MacAddress_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left.CompareTo(right); + + // Assert + Assert.Equal(expected, result); + } + + #endregion end: IComparable + + #region DefaultMacAddress + + [Fact] + public void DefaultMacAddress_Test() + { + // Arrange + var expected = Enumerable.Repeat((byte)0xFF, 6).ToArray(); + + // Act + var defaultMacAddress = MacAddress.DefaultMacAddress; + + // Assert + Assert.NotNull(defaultMacAddress); + Assert.Equal(0, ByteArrayUtils.CompareUnsignedBigEndian(expected, defaultMacAddress.GetAddressBytes())); + } + + #endregion end: DefaultMacAddress + + #region GetAddressBytes + + [Theory] + [InlineData(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff })] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(new byte[] { 0x00, 0xff, 0x00, 0xff, 0x00, 0x00 })] + public void GetAddressBytes_Test(byte[] bytes) + { + // Arrange + var macAddress = new MacAddress(bytes); + + // Act + var addressBytes = macAddress.GetAddressBytes(); + + // Assert + Assert.NotNull(addressBytes); + Assert.IsType(addressBytes); + Assert.Equal(addressBytes.Length, addressBytes.Length); + Assert.Equal(0, ByteArrayUtils.CompareUnsignedBigEndian(bytes, addressBytes)); + } + + #endregion end: GetAddressBytes + + #region GetCidBytes + + [Fact] + public void GetCidBytes_Test() + { + // Arrange + var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB }; + + var macAddress = new MacAddress(bytes); + + // Act + var cidBytes = macAddress.GetCidBytes(); + + // Assert + Assert.Equal(0, ByteArrayUtils.CompareUnsignedBigEndian(bytes.Skip(3).Take(3).ToArray(), cidBytes)); + } + + #endregion end: GetCidBytes + + #region GetHashCode + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void GetHashCode_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + if (left is null || right is null) { - var s = macAddress.ToString(format, formatProvider); - yield return new object[] { macAddress, s }; - yield return new object[] { macAddress, s.ToLowerInvariant() }; + return; // skip null values and bail } - } - } - - #endregion end: Parse string - - #region ParseAny - - [Theory] - [MemberData(nameof(ParseAny_Test_SuccessValues))] - public void ParseAny_HappyPath_Test(MacAddress expected, - string input) - { - // Arrange - - // Act - var macAddress = MacAddress.ParseAny(input); - - // Assert - Assert.Equal(expected, macAddress); - } - - [Theory] - [MemberData(nameof(ParseAny_Test_FailValues))] - public void ParseAny_Failure_Test(MacAddress expected, - string input) - { - // Arrange - // Act - // Assert - Assert.ThrowsAny(() => MacAddress.ParseAny(input)); - } - - [Theory] - [MemberData(nameof(ParseAny_Test_SuccessValues))] - [MemberData(nameof(ParseAny_Test_FailValues))] - public void TryParseAny_Test(MacAddress expected, - string input) - { - // Arrange - - // Act - var success = MacAddress.TryParseAny(input, out var macAddress); - - // Assert - Assert.True(expected is null ^ success); - Assert.Equal(expected, macAddress); - } - - public static IEnumerable ParseAny_Test_FailValues() - { - yield return new object[] { null, null }; - yield return new object[] { null, string.Empty }; - yield return new object[] { null, "A" }; - yield return new object[] { null, "potato" }; - } - - public static IEnumerable ParseAny_Test_SuccessValues() - { - foreach (var testCase in StandardFormatTestCases()) - { - yield return testCase; - } - - foreach (var testCase in NonStandardFormatTestCases()) - { - yield return testCase; - } - - static IEnumerable NonStandardFormatTestCases() - { - var bytes = new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }; - var expected = new MacAddress(bytes); - var byteStrings = bytes.Select(b => $"{b:X2}") - .ToArray(); - yield return new object[] { expected, string.Join("?!&*", byteStrings) }; - yield return new object[] { expected, $"-_={string.Join(string.Empty, byteStrings)}=_-" }; + // Act + var leftHashCode = left.GetHashCode(); + var rightHashCode = right.GetHashCode(); + + // Assert + Assert.Equal(expected == 0, leftHashCode == rightHashCode); + } + + #endregion end: GetHashCode + + #region GetOuiBytes + + [Fact] + public void GetOuiBytes_Test() + { + // Arrange + var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB }; + + var macAddress = new MacAddress(bytes); + + // Act + var ouiBytes = macAddress.GetOuiBytes(); + + // Assert + Assert.Equal(0, ByteArrayUtils.CompareUnsignedBigEndian(bytes.Take(3).ToArray(), ouiBytes)); + } + + #endregion end: GetOuiBytes + + #region IsGloballyUnique + + [Theory] + [InlineData(true, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(false, new byte[] { 0b0000_0010, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(true, new byte[] { 0b1111_1101, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + [InlineData(false, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + public void IsGloballyUnique_Test(bool expected, byte[] bytes) + { + // Arrange + var macAddress = new MacAddress(bytes); + + // Act + var isGloballyUnique = macAddress.IsGloballyUnique; + + // Assert + Assert.Equal(expected, isGloballyUnique); + } + + #endregion end: IsGloballyUnique + + #region IsLocallyAdministered + + [Theory] + [InlineData(false, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(true, new byte[] { 0b0000_0010, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(false, new byte[] { 0b1111_1101, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + [InlineData(true, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + public void IsLocallyAdministered_Test(bool expected, byte[] bytes) + { + // Arrange + var macAddress = new MacAddress(bytes); + + // Act + var isLocallyAdministered = macAddress.IsLocallyAdministered; + + // Assert + Assert.Equal(expected, isLocallyAdministered); + } + + #endregion end: IsLocallyAdministered + + #region IsMulticast + + [Theory] + [InlineData(false, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(true, new byte[] { 0b0000_0001, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(false, new byte[] { 0b1111_1110, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + [InlineData(true, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + public void IsMulticast_Test(bool expected, byte[] bytes) + { + // Arrange + var macAddress = new MacAddress(bytes); + + // Act + var isMulticast = macAddress.IsMulticast; + + // Assert + Assert.Equal(expected, isMulticast); + } + + #endregion end: IsMulticast + + #region IsUnicast + + [Theory] + [InlineData(true, new byte[] { 0b0000_0000, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(false, new byte[] { 0b0000_0001, 0x00, 0x00, 0x00, 0x00, 0x00 })] + [InlineData(true, new byte[] { 0b1111_1110, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + [InlineData(false, new byte[] { 0b1111_1111, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })] + public void IsUnicast_Test(bool expected, byte[] bytes) + { + // Arrange + var macAddress = new MacAddress(bytes); + + // Act + var isUnicast = macAddress.IsUnicast; + + // Assert + Assert.Equal(expected, isUnicast); + } + + #endregion end: IsUnicast + + #region other members + + public static IEnumerable Comparison_MacAddress_MacAddress_Values() + { + var byteArrayMax = Enumerable.Repeat((byte)0xff, 6).ToArray(); + + var byteArrayMin = Enumerable.Repeat((byte)0x00, 6).ToArray(); + + var macAddressMax = new MacAddress(byteArrayMax); + + var macAddressMin = new MacAddress(byteArrayMin); + + yield return new object[] { 0, macAddressMin, macAddressMin }; // same equal + yield return new object[] { 0, macAddressMin, new MacAddress(byteArrayMin) }; // same underlying bytes + + yield return new object[] { -1, macAddressMin, macAddressMax }; // left less than right + yield return new object[] { 1, macAddressMax, macAddressMin }; // left greater than right + + yield return new object[] { 1, macAddressMin, null }; // right is null + } + + public static IEnumerable Comparison_MacAddress_Object_Values() + { + var bytes = Enumerable.Repeat((byte)0xff, 6).ToArray(); + var macAddress = new MacAddress(bytes); + + yield return new object[] { -1, macAddress, "string" }; + yield return new object[] { -1, macAddress, 42 }; + yield return new object[] { -1, macAddress, bytes }; + } + + #endregion + + #region IsDefault + + public static IEnumerable IsDefault_Test_Values() + { + yield return new object[] { true, MacAddress.DefaultMacAddress }; + yield return new object[] { true, new MacAddress(Enumerable.Repeat((byte)0xFF, 6)) }; + yield return new object[] { false, new MacAddress(Enumerable.Repeat((byte)0x00, 6)) }; + } + + [Theory] + [MemberData(nameof(IsDefault_Test_Values))] + public void IsDefault_Test(bool expeted, MacAddress macAddress) + { + // Arrange + // Act + var isDefault = macAddress.IsDefault; + + // Assert + Assert.Equal(expeted, isDefault); + } + + #endregion end: IsDefault + + #region IsUnusable + + public static IEnumerable IsUnusable_Test_Values() + { + yield return new object[] { false, new MacAddress(Enumerable.Repeat((byte)0x00, 6)) }; yield return new object[] - { - expected, string.Join("_", - string.Join(string.Empty, byteStrings) - .Select(c => $"{c}")) - }; - } - - static IEnumerable StandardFormatTestCases() - { + { + false, + new MacAddress(Enumerable.Repeat((byte)0x00, 3).Concat(Enumerable.Repeat((byte)0xFF, 3))), + }; + yield return new object[] { true, new MacAddress(Enumerable.Repeat((byte)0x01, 6)) }; + yield return new object[] { true, new MacAddress(Enumerable.Repeat((byte)0xFF, 6)) }; + } + + [Theory] + [MemberData(nameof(IsUnusable_Test_Values))] + public void IsUnusable_Test(bool expected, MacAddress macAddress) + { + // Arrange + + // Act + var isUsable = macAddress.IsUnusable; + + // Assert + Assert.Equal(expected, isUsable); + } + + #endregion end: IsUnusable + + #region ctor bytes[] + + [Theory] + [InlineData(0)] + [InlineData(5)] + [InlineData(7)] + public void Ctor_WrongByteCount_Throws_ArgumentException_Test(int byteCount) + { + // Arrange + var bytes = Enumerable.Repeat((byte)0xAC, byteCount).ToArray(); + + // Act + // Assert + Assert.Throws(() => new MacAddress(bytes)); + } + + [Fact] + public void Ctor_NullBytes_Throws_ArgumentNullException_Test() => + // Arrange + // Act + // Assert + Assert.Throws(() => new MacAddress(null)); + + #endregion end: ctor bytes[] + + #region ISerializable +#if NET48 // maintained for .NET 4.8 compatibility + public static IEnumerable CanSerializable_Test_Values() + { + yield return new object[] { new MacAddress(Enumerable.Repeat((byte)0x00, 6)) }; + yield return new object[] { new MacAddress(Enumerable.Repeat((byte)0xFF, 6)) }; + yield return new object[] { new MacAddress(new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }) }; + } + + [Theory] + [MemberData(nameof(CanSerializable_Test_Values))] + public void CanSerializable_Test(MacAddress macAddress) + { + // Arrange + var formatter = new BinaryFormatter(); + + // Act + using (var writeStream = new MemoryStream()) + { + formatter.Serialize(writeStream, macAddress); + writeStream.Seek(0, SeekOrigin.Begin); + + // Deserialize the object from the stream + var result = formatter.Deserialize(writeStream); + + // Assert + Assert.IsType(result); + Assert.Equal(macAddress, result); + } + } +#endif + #endregion end: ISerializable + + #region type + + [Theory] + [InlineData(typeof(IEquatable))] + [InlineData(typeof(IComparable))] + [InlineData(typeof(IComparable))] + [InlineData(typeof(IFormattable))] +#if NET48 + [InlineData(typeof(ISerializable))] +#endif + public void Assignability_Test(Type assignableFromType) + { + // Arrange + var type = typeof(MacAddress); + + // Act + var isAssignableFrom = assignableFromType.IsAssignableFrom(type); + + // Assert + Assert.True(isAssignableFrom); + } + + [Fact] + public void IsConcrete_Test() + { + // Arrange + var type = typeof(MacAddress); + + // Act + var isConcrete = type.IsClass && !type.IsAbstract; + + // Assert + Assert.True(isConcrete); + } + + #endregion end: type + + #region IComparable + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void CompareTo_Object_Test(int expected, MacAddress left, object right) + { + // Arrange + // Act + var result = left.CompareTo(right); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Comparison_MacAddress_Object_Values))] +#pragma warning disable xUnit1026 // theory methods should use all their parameters; Agreed. We're jsut not there yet + public void CompareTo_Object_NotMacAddress_Test(int expected, MacAddress left, object right) => + // Arrange + // Act + // Assert + Assert.Throws(() => left.CompareTo(right)); +#pragma warning restore xUnit1026 + + #endregion end: IComparable + + #region Parse + + #region Parse string + + [Theory] + [MemberData(nameof(Parse_Test_SuccessValues))] + public void Parse_HappyPath_Test(MacAddress expected, string input) + { + // Arrange + + // Act + var macAddress = MacAddress.Parse(input); + + // Assert + Assert.Equal(expected, macAddress); + } + + [Theory] + [MemberData(nameof(Parse_Test_FailValues))] +#pragma warning disable xUnit1026 // theory methods should use all their parameters; Agreed. We're jsut not there yet + public void Parse_Failure_Test(MacAddress expected, string input) => + // Arrange + // Act + // Assert + Assert.ThrowsAny(() => MacAddress.Parse(input)); +#pragma warning restore xUnit1026 + + [Theory] + [MemberData(nameof(Parse_Test_SuccessValues))] + [MemberData(nameof(Parse_Test_FailValues))] + public void TryParse_Test(MacAddress expected, string input) + { + // Arrange + + // Act + var success = MacAddress.TryParse(input, out var macAddress); + + // Assert + Assert.True(expected is null ^ success); + Assert.Equal(expected, macAddress); + } + + public static IEnumerable Parse_Test_FailValues() + { + yield return new object[] { null, null }; + yield return new object[] { null, string.Empty }; + yield return new object[] { null, "A" }; + yield return new object[] { null, "potato" }; + } + + public static IEnumerable Parse_Test_SuccessValues() + { var formatProvider = CultureInfo.InvariantCulture; var formats = new[] { "g", "c", "s", "d", "X" }; var bytes = new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }; - var macAddresses = new[] - { - new MacAddress(bytes), - new MacAddress(bytes.ReverseBytes()) - }; + var macAddresses = new[] { new MacAddress(bytes), new MacAddress(bytes.ReverseBytes()) }; foreach (var macAddress in macAddresses) { - foreach (var format in formats) - { - var s = macAddress.ToString(format, formatProvider); - yield return new object[] { macAddress, s }; - yield return new object[] { macAddress, s.ToLowerInvariant() }; - } + foreach (var format in formats) + { + var s = macAddress.ToString(format, formatProvider); + yield return new object[] { macAddress, s }; + yield return new object[] { macAddress, s.ToLowerInvariant() }; + } + } + } + + #endregion end: Parse string + + #region ParseAny + + [Theory] + [MemberData(nameof(ParseAny_Test_SuccessValues))] + public void ParseAny_HappyPath_Test(MacAddress expected, string input) + { + // Arrange + + // Act + var macAddress = MacAddress.ParseAny(input); + + // Assert + Assert.Equal(expected, macAddress); + } + + [Theory] + [MemberData(nameof(ParseAny_Test_FailValues))] +#pragma warning disable xUnit1026 // theory methods should use all their parameters; Agreed. We're jsut not there yet + public void ParseAny_Failure_Test(MacAddress expected, string input) => + // Arrange + // Act + // Assert + Assert.ThrowsAny(() => MacAddress.ParseAny(input)); +#pragma warning restore xUnit1026 + + [Theory] + [MemberData(nameof(ParseAny_Test_SuccessValues))] + [MemberData(nameof(ParseAny_Test_FailValues))] + public void TryParseAny_Test(MacAddress expected, string input) + { + // Arrange + + // Act + var success = MacAddress.TryParseAny(input, out var macAddress); + + // Assert + Assert.True(expected is null ^ success); + Assert.Equal(expected, macAddress); + } + + public static IEnumerable ParseAny_Test_FailValues() + { + yield return new object[] { null, null }; + yield return new object[] { null, string.Empty }; + yield return new object[] { null, "A" }; + yield return new object[] { null, "potato" }; + } + + public static IEnumerable ParseAny_Test_SuccessValues() + { + foreach (var testCase in StandardFormatTestCases()) + { + yield return testCase; + } + + foreach (var testCase in NonStandardFormatTestCases()) + { + yield return testCase; + } + + IEnumerable NonStandardFormatTestCases() + { + var bytes = new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }; + var expected = new MacAddress(bytes); + var byteStrings = bytes.Select(b => $"{b:X2}").ToArray(); + + yield return new object[] { expected, string.Join("?!&*", byteStrings) }; + yield return new object[] { expected, $"-_={string.Join(string.Empty, byteStrings)}=_-" }; + yield return new object[] + { + expected, + string.Join("_", string.Join(string.Empty, byteStrings).Select(c => $"{c}")), + }; + } + + IEnumerable StandardFormatTestCases() + { + var formatProvider = CultureInfo.InvariantCulture; + var formats = new[] { "g", "c", "s", "d", "X" }; + + var bytes = new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }; + + var macAddresses = new[] { new MacAddress(bytes), new MacAddress(bytes.ReverseBytes()) }; + + foreach (var macAddress in macAddresses) + { + foreach (var format in formats) + { + var s = macAddress.ToString(format, formatProvider); + yield return new object[] { macAddress, s }; + yield return new object[] { macAddress, s.ToLowerInvariant() }; + } + } } - } - } + } + + #endregion end: ParseAny + + #endregion end: Parse + + #region IFormattable + + public static IEnumerable IFormattable_Test_Values() + { + var formatProvider = CultureInfo.InvariantCulture; + var bytes = new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }; + + yield return new object[] { "00:CD:EF:01:23:45", bytes, null, formatProvider }; + yield return new object[] { "00:CD:EF:01:23:45", bytes, string.Empty, formatProvider }; + yield return new object[] { "00:CD:EF:01:23:45", bytes, "g", formatProvider }; + + yield return new object[] { "00cdef012345", bytes, "x", formatProvider }; + yield return new object[] { "00CDEF012345", bytes, "X", formatProvider }; + + yield return new object[] { "00CD.EF01.2345", bytes, "c", formatProvider }; + + yield return new object[] { "00 CD EF 01 23 45", bytes, "s", formatProvider }; + + yield return new object[] { "884478124869", bytes, "i", formatProvider }; + + yield return new object[] { "00-CD-EF-01-23-45", bytes, "d", formatProvider }; + } + + [Theory] + [MemberData(nameof(IFormattable_Test_Values))] + public void IFormattable_Test(string expected, byte[] bytes, string format, IFormatProvider formatProvider) + { + // Arrange + var macAddress = new MacAddress(bytes); + + // Act + var result = macAddress.ToString(format, formatProvider); - #endregion end: ParseAny + // Assert + Assert.Equal(expected, result, StringComparer.Ordinal); + } - #endregion end: Parse + #endregion end: IFormattable - #region IFormattable + #region IEquatable - public static IEnumerable IFormattable_Test_Values() - { - var formatProvider = CultureInfo.InvariantCulture; - var bytes = new byte[] { 0x00, 0xCD, 0xEF, 0x01, 0x23, 0x45 }; + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void Equals_MacAddress_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left.Equals(right); - yield return new object[] { "00:CD:EF:01:23:45", bytes, null, formatProvider }; - yield return new object[] { "00:CD:EF:01:23:45", bytes, string.Empty, formatProvider }; - yield return new object[] { "00:CD:EF:01:23:45", bytes, "g", formatProvider }; + // Assert + Assert.Equal(expected == 0, result); + } - yield return new object[] { "00cdef012345", bytes, "x", formatProvider }; - yield return new object[] { "00CDEF012345", bytes, "X", formatProvider }; + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + [MemberData(nameof(Comparison_MacAddress_Object_Values))] + public void Equals_Object_Test(int expected, MacAddress left, object right) + { + // Arrange + // Act + var result = left.Equals(right); - yield return new object[] { "00CD.EF01.2345", bytes, "c", formatProvider }; + // Assert + Assert.Equal(expected == 0, result); + } - yield return new object[] { "00 CD EF 01 23 45", bytes, "s", formatProvider }; + #endregion end: IEquatable - yield return new object[] { "884478124869", bytes, "i", formatProvider }; + #region operators - yield return new object[] { "00-CD-EF-01-23-45", bytes, "d", formatProvider }; - } - - [Theory] - [MemberData(nameof(IFormattable_Test_Values))] - public void IFormattable_Test(string expected, - byte[] bytes, - string format, - IFormatProvider formatProvider) - { - // Arrange - var macAddress = new MacAddress(bytes); + #region equal - // Act - var result = macAddress.ToString(format, formatProvider); + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void Operator_Equal_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left == right; - // Assert - Assert.Equal(expected, result, StringComparer.Ordinal); - } - - #endregion end: IFormattable - - #region IEquatable - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void Equals_MacAddress_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left.Equals(right); - - // Assert - Assert.Equal(expected == 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - [MemberData(nameof(Comparison_MacAddress_Object_Values))] - public void Equals_Object_Test(int expected, - MacAddress left, - object right) - { - // Arrange - // Act - var result = left.Equals(right); - - // Assert - Assert.Equal(expected == 0, result); - } - - #endregion end: IEquatable - - #region operators - - #region equal - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void Operator_Equal_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left == right; - - // Assert - Assert.Equal(expected == 0, result); - } - - #endregion end: equal - - #region not equal - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void Operator_NotEqual_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left != right; - - // Assert - Assert.Equal(expected != 0, result); - } - - #endregion end: not equal - - #region less than - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void Operator_LessThan_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left < right; - - // Assert - Assert.Equal(expected == -1, result); - } - - #endregion end: less than - - #region greater than - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void Operator_GreaterThan_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left > right; - - // Assert - Assert.Equal(expected == 1, result); - } - - #endregion end: greater than - - #region less than or equal - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void Operator_LessThanOrEqualTo_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left <= right; - - // Assert - Assert.Equal(expected <= 0, result); - } - - #endregion end: less than or equal - - #region greater than or equal - - [Theory] - [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] - public void Operator_GreaterThanOrEqual_Test(int expected, - MacAddress left, - MacAddress right) - { - // Arrange - // Act - var result = left >= right; - - // Assert - Assert.Equal(expected >= 0, result); - } - - #endregion end: greater than or equal - - #endregion end: operators - - #region AllFormatMacAddressRegularExpression - - public static IEnumerable AllFormantMacAddressRefularExpressionMatches() - { - var values = new[] - { - "00D.7FB.576.F14", - "01 D5 7A D5 18 9B", - "015.721.263.754", - "02 41 7F 6E AA 59", - "041E8FF5EB0B", - "08 ED 85 A7 56 6D", - "0D-FF-28-25-33-B1", - "14:C7:A3:D3:B4:97", - "158213168721", - "15:D4:84:DE:72:A7", - "19-7E-1D-96-48-59", - "1C DD 35 BA E0 B8", - "1C56A2268779", - "1D8.E06.643.9BF", - "22-EA-45-59-E9-99", - "23E4.6AE9.14EF", - "24D3240A23C3", - "24 F4 73 7E DF 24", - "29:2A:00:D4:11:C1", - "2DD.215.C37.CF1", - "31-D5-77-54-5C-B4", - "314D705B7F5D", - "3277.E822.DBEE", - "34:ED:5C:3A:C2:26", - "38:46:A8:85:2A:F1", - "3D3E466575FE", - "3DE7.E1C3.F33D", - "3E:5B:37:15:C7:D0", - "3F-8B-BE-06-B6-F7", - "42-9E-C0-72-EF-EA", - "4420.6185.24C0", - "4ABB.D96B.6197", - "4E7C.49D9.ABA8", - "5436.5D82.9215", - "55 B3 13 7D 98 EF", - "565.FBF.891.1DB", - "5A6.1A2.B36.643", - "5C4FE09A5CEE", - "5E758934109F", - "68:40:C6:33:61:67", - "69-61-46-84-5B-03", - "6961.6D06.864A", - "69CA.30DC.394C", - "6AA3C4C8FAED", - "6E-B6-1B-11-CA-28", - "70:47:5F:E3:42:8A", - "70D.04B.5EA.88C", - "72 71 DC 01 99 79", - "75BB2A7F454A", - "765.E80.3FE.05D" - }; - - foreach (var value in values) - { - yield return new object[] { value }; - yield return new object[] { value.ToLowerInvariant() }; - } - } - - [Theory] - [MemberData(nameof(AllFormantMacAddressRefularExpressionMatches))] - public void AllFormantMacAddressRefularExpression_Matches_Test(string input) - { - // Arrange - // Act - var isMatch = MacAddress.AllFormatMacAddressRegularExpression.IsMatch(input); - - // Assert - Assert.True(isMatch); - var count = Regex.Matches(input, @"[\dA-Fa-f]") - .Count; - Assert.Equal(12, - count); - } - - #endregion end: AllFormatMacAddressRegularExpression - - #region CommonFormatMacAddressRegularExpression - - public static IEnumerable CommonFormatMacAddressRegularExpressionMatches() - { - var values = new[] - { - "DC:F1:EE:7C:9A:E1", - "67:2F:7C:7A:FF:C8", - "B9:53:18:2A:71:7D", - "29:11:05:52:82:92", - "E9:33:5E:09:75:72", - "33:2F:3B:80:ED:BD", - "A1:29:5A:EF:DE:F0", - "28:9F:D1:37:29:42", - "F4:84:B5:55:44:66", - "27:3E:BF:1A:79:52", - "09:AF:25:C2:B0:BB", - "58:9B:98:C7:7D:FE", - "55:7D:F4:80:B8:5F", - "F3:E1:FC:C6:69:5E", - "50:BF:59:FD:80:94", - "1D:18:5C:C7:62:61", - "0C:31:21:91:B7:80", - "30:5B:A5:91:57:DD", - "A4:20:B2:52:F7:E9", - "63:93:49:2E:3C:5F", - "4B:ED:5C:DF:EE:B9", - "12:63:97:53:12:B4", - "13:DC:EE:70:5E:47", - "CA:1E:E1:6F:AC:3E", - "DF:96:00:4B:51:56", - "59:26:6A:11:DC:D3", - "D0:DC:A7:1B:CE:A1", - "AB:72:20:97:BD:E4", - "03:DD:05:A9:63:D5", - "93:58:E8:3F:AA:2C", - "48:98:C8:B8:B6:B2", - "C1:E0:D1:95:4C:D2", - "D0:3F:44:DE:07:D8", - "D6:26:E3:18:03:29", - "98:C7:5C:A6:E3:4F", - "FB:E8:2A:DF:55:00", - "E5:28:A5:B0:49:FF", - "1B:63:3F:2C:19:9A", - "2D:2D:FA:92:80:AF", - "DA:B2:60:C3:C3:AB", - "27:81:0B:67:BC:02", - "8F:35:3E:DB:7E:69", - "50:16:42:2D:5D:C3", - "3F:0D:45:E9:0F:90", - "94:58:0D:54:72:D9", - "FE:E8:9E:19:F2:0F", - "4C:7F:D1:E4:4D:4B", - "A0:09:97:78:65:95", - "E6:01:AC:DF:D3:DC", - "49:24:6D:2E:30:58" - }; - - foreach (var value in values) - { - yield return new object[] { value }; - yield return new object[] { value.ToLowerInvariant() }; - } - } - - [Theory] - [MemberData(nameof(CommonFormatMacAddressRegularExpressionMatches))] - public void CommonFormatMacAddressRegularExpression_Matches_Test(string input) - { - // Arrange - // Act - var isMatch = MacAddress.CommonFormatMacAddressRegularExpression.IsMatch(input); - - // Assert - Assert.True(isMatch); - var count = Regex.Matches(input, @"[\dA-Fa-f]") - .Count; - Assert.Equal(12, count); - } - - #endregion end: CommonFormatMacAddressRegularExpression - } + // Assert + Assert.Equal(expected == 0, result); + } + + #endregion end: equal + + #region not equal + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void Operator_NotEqual_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left != right; + + // Assert + Assert.Equal(expected != 0, result); + } + + #endregion end: not equal + + #region less than + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void Operator_LessThan_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left < right; + + // Assert + Assert.Equal(expected == -1, result); + } + + #endregion end: less than + + #region greater than + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void Operator_GreaterThan_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left > right; + + // Assert + Assert.Equal(expected == 1, result); + } + + #endregion end: greater than + + #region less than or equal + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void Operator_LessThanOrEqualTo_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left <= right; + + // Assert + Assert.Equal(expected <= 0, result); + } + + #endregion end: less than or equal + + #region greater than or equal + + [Theory] + [MemberData(nameof(Comparison_MacAddress_MacAddress_Values))] + public void Operator_GreaterThanOrEqual_Test(int expected, MacAddress left, MacAddress right) + { + // Arrange + // Act + var result = left >= right; + + // Assert + Assert.Equal(expected >= 0, result); + } + + #endregion end: greater than or equal + + #endregion end: operators + + #region AllFormatMacAddressRegularExpression + + public static IEnumerable AllFormantMacAddressRegularExpressionMatches() => + new[] + { + "00D.7FB.576.F14", + "01 D5 7A D5 18 9B", + "015.721.263.754", + "02 41 7F 6E AA 59", + "041E8FF5EB0B", + "08 ED 85 A7 56 6D", + "0D-FF-28-25-33-B1", + "14:C7:A3:D3:B4:97", + "158213168721", + "15:D4:84:DE:72:A7", + "19-7E-1D-96-48-59", + "1C DD 35 BA E0 B8", + "1C56A2268779", + "1D8.E06.643.9BF", + "22-EA-45-59-E9-99", + "23E4.6AE9.14EF", + "24D3240A23C3", + "24 F4 73 7E DF 24", + "29:2A:00:D4:11:C1", + "2DD.215.C37.CF1", + "31-D5-77-54-5C-B4", + "314D705B7F5D", + "3277.E822.DBEE", + "34:ED:5C:3A:C2:26", + "38:46:A8:85:2A:F1", + "3D3E466575FE", + "3DE7.E1C3.F33D", + "3E:5B:37:15:C7:D0", + "3F-8B-BE-06-B6-F7", + "42-9E-C0-72-EF-EA", + "4420.6185.24C0", + "4ABB.D96B.6197", + "4E7C.49D9.ABA8", + "5436.5D82.9215", + "55 B3 13 7D 98 EF", + "565.FBF.891.1DB", + "5A6.1A2.B36.643", + "5C4FE09A5CEE", + "5E758934109F", + "68:40:C6:33:61:67", + "69-61-46-84-5B-03", + "6961.6D06.864A", + "69CA.30DC.394C", + "6AA3C4C8FAED", + "6E-B6-1B-11-CA-28", + "70:47:5F:E3:42:8A", + "70D.04B.5EA.88C", + "72 71 DC 01 99 79", + "75BB2A7F454A", + "765.E80.3FE.05D", + } + .SelectMany(s => new[] { s, s.ToLowerInvariant() }) // Select original and lower case + .Distinct() + .Select(s => new object[] { s }); + + [Theory] + [MemberData(nameof(AllFormantMacAddressRegularExpressionMatches))] + public void AllFormantMacAddressRegularExpression_Matches_Test(string input) + { + // Arrange + // Act + var isMatch = MacAddress.AllFormatMacAddressRegularExpression.IsMatch(input); + + // Assert + Assert.True(isMatch); + var count = Regex.Matches(input, @"[\dA-Fa-f]").Count; + Assert.Equal(12, count); + } + + #endregion end: AllFormatMacAddressRegularExpression + + #region CommonFormatMacAddressRegularExpression + + public static IEnumerable CommonFormatMacAddressRegularExpressionMatches() => + new[] + { + "DC:F1:EE:7C:9A:E1", + "67:2F:7C:7A:FF:C8", + "B9:53:18:2A:71:7D", + "29:11:05:52:82:92", + "E9:33:5E:09:75:72", + "33:2F:3B:80:ED:BD", + "A1:29:5A:EF:DE:F0", + "28:9F:D1:37:29:42", + "F4:84:B5:55:44:66", + "27:3E:BF:1A:79:52", + "09:AF:25:C2:B0:BB", + "58:9B:98:C7:7D:FE", + "55:7D:F4:80:B8:5F", + "F3:E1:FC:C6:69:5E", + "50:BF:59:FD:80:94", + "1D:18:5C:C7:62:61", + "0C:31:21:91:B7:80", + "30:5B:A5:91:57:DD", + "A4:20:B2:52:F7:E9", + "63:93:49:2E:3C:5F", + "4B:ED:5C:DF:EE:B9", + "12:63:97:53:12:B4", + "13:DC:EE:70:5E:47", + "CA:1E:E1:6F:AC:3E", + "DF:96:00:4B:51:56", + "59:26:6A:11:DC:D3", + "D0:DC:A7:1B:CE:A1", + "AB:72:20:97:BD:E4", + "03:DD:05:A9:63:D5", + "93:58:E8:3F:AA:2C", + "48:98:C8:B8:B6:B2", + "C1:E0:D1:95:4C:D2", + "D0:3F:44:DE:07:D8", + "D6:26:E3:18:03:29", + "98:C7:5C:A6:E3:4F", + "FB:E8:2A:DF:55:00", + "E5:28:A5:B0:49:FF", + "1B:63:3F:2C:19:9A", + "2D:2D:FA:92:80:AF", + "DA:B2:60:C3:C3:AB", + "27:81:0B:67:BC:02", + "8F:35:3E:DB:7E:69", + "50:16:42:2D:5D:C3", + "3F:0D:45:E9:0F:90", + "94:58:0D:54:72:D9", + "FE:E8:9E:19:F2:0F", + "4C:7F:D1:E4:4D:4B", + "A0:09:97:78:65:95", + "E6:01:AC:DF:D3:DC", + "49:24:6D:2E:30:58", + } + .SelectMany(s => new[] { s, s.ToLowerInvariant() }) // Select original and lower case + .Distinct() + .Select(s => new object[] { s }); + + [Theory] + [MemberData(nameof(CommonFormatMacAddressRegularExpressionMatches))] + public void CommonFormatMacAddressRegularExpression_Matches_Test(string input) + { + // Arrange + // Act + var isMatch = MacAddress.CommonFormatMacAddressRegularExpression.IsMatch(input); + + // Assert + Assert.True(isMatch); + var count = Regex.Matches(input, @"[\dA-Fa-f]").Count; + Assert.Equal(12, count); + } + + #endregion end: CommonFormatMacAddressRegularExpression + } } diff --git a/src/Arcus.Tests/Math/IPAddressMathTests.cs b/src/Arcus.Tests/Math/IPAddressMathTests.cs index 83a8292..ae0ae68 100644 --- a/src/Arcus.Tests/Math/IPAddressMathTests.cs +++ b/src/Arcus.Tests/Math/IPAddressMathTests.cs @@ -14,13 +14,13 @@ public static IEnumerable IsEqualTo_Test_Values() { foreach (var testCase in NonTransitiveTestCases()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; + yield return new object[] { testCase.expected, testCase.left, testCase.right }; } foreach (var testCase in TransitiveTestCases()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; - yield return new object[] {testCase.expected, testCase.right, testCase.left}; // Transitive law test + yield return new object[] { testCase.expected, testCase.left, testCase.right }; + yield return new object[] { testCase.expected, testCase.right, testCase.left }; // Transitive law test } IEnumerable<(bool expected, IPAddress left, IPAddress right)> NonTransitiveTestCases() @@ -57,9 +57,7 @@ public static IEnumerable IsEqualTo_Test_Values() [Theory] [MemberData(nameof(IsEqualTo_Test_Values))] - public void IsEqualTo_Test(bool expected, - IPAddress left, - IPAddress right) + public void IsEqualTo_Test(bool expected, IPAddress left, IPAddress right) { // Arrange // Act @@ -77,13 +75,13 @@ public static IEnumerable IsGreaterThan_Test_Values() { foreach (var testCase in NonTransitiveTestCases()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; + yield return new object[] { testCase.expected, testCase.left, testCase.right }; } foreach (var testCase in TransitiveInverse()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; - yield return new object[] {!testCase.expected, testCase.right, testCase.left}; // Transitive law test + yield return new object[] { testCase.expected, testCase.left, testCase.right }; + yield return new object[] { !testCase.expected, testCase.right, testCase.left }; // Transitive law test } IEnumerable<(bool expected, IPAddress left, IPAddress right)> NonTransitiveTestCases() @@ -125,9 +123,7 @@ public static IEnumerable IsGreaterThan_Test_Values() [Theory] [MemberData(nameof(IsGreaterThan_Test_Values))] - public void IsGreaterThan_Test(bool expected, - IPAddress left, - IPAddress right) + public void IsGreaterThan_Test(bool expected, IPAddress left, IPAddress right) { // Arrange // Act @@ -145,13 +141,13 @@ public static IEnumerable IsGreaterThanOrEqualTo_Test_Values() { foreach (var testCase in NonTransitiveTestCases()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; + yield return new object[] { testCase.expected, testCase.left, testCase.right }; } foreach (var testCase in TransitiveInverse()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; - yield return new object[] {!testCase.expected, testCase.right, testCase.left}; // Transitive law test + yield return new object[] { testCase.expected, testCase.left, testCase.right }; + yield return new object[] { !testCase.expected, testCase.right, testCase.left }; // Transitive law test } IEnumerable<(bool expected, IPAddress left, IPAddress right)> NonTransitiveTestCases() @@ -193,9 +189,7 @@ public static IEnumerable IsGreaterThanOrEqualTo_Test_Values() [Theory] [MemberData(nameof(IsGreaterThanOrEqualTo_Test_Values))] - public void IsGreaterThanOrEqualTo_Test(bool expected, - IPAddress left, - IPAddress right) + public void IsGreaterThanOrEqualTo_Test(bool expected, IPAddress left, IPAddress right) { // Arrange // Act @@ -213,13 +207,13 @@ public static IEnumerable IsLessThan_Test_Values() { foreach (var testCase in NonTransitiveTestCases()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; + yield return new object[] { testCase.expected, testCase.left, testCase.right }; } foreach (var testCase in TransitiveInverse()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; - yield return new object[] {!testCase.expected, testCase.right, testCase.left}; // Transitive law test + yield return new object[] { testCase.expected, testCase.left, testCase.right }; + yield return new object[] { !testCase.expected, testCase.right, testCase.left }; // Transitive law test } IEnumerable<(bool expected, IPAddress left, IPAddress right)> NonTransitiveTestCases() @@ -261,9 +255,7 @@ public static IEnumerable IsLessThan_Test_Values() [Theory] [MemberData(nameof(IsLessThan_Test_Values))] - public void IsLessThan_Test(bool expected, - IPAddress left, - IPAddress right) + public void IsLessThan_Test(bool expected, IPAddress left, IPAddress right) { // Arrange // Act @@ -281,13 +273,13 @@ public static IEnumerable IsLessThanOrEqualTo_Test_Values() { foreach (var testCase in NonTransitiveTestCases()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; + yield return new object[] { testCase.expected, testCase.left, testCase.right }; } foreach (var testCase in TransitiveInverse()) { - yield return new object[] {testCase.expected, testCase.left, testCase.right}; - yield return new object[] {!testCase.expected, testCase.right, testCase.left}; // Transitive law test + yield return new object[] { testCase.expected, testCase.left, testCase.right }; + yield return new object[] { !testCase.expected, testCase.right, testCase.left }; // Transitive law test } IEnumerable<(bool expected, IPAddress left, IPAddress right)> NonTransitiveTestCases() @@ -329,9 +321,7 @@ public static IEnumerable IsLessThanOrEqualTo_Test_Values() [Theory] [MemberData(nameof(IsLessThanOrEqualTo_Test_Values))] - public void IsLessThanOrEqualTo_Test(bool expected, - IPAddress left, - IPAddress right) + public void IsLessThanOrEqualTo_Test(bool expected, IPAddress left, IPAddress right) { // Arrange // Act @@ -350,15 +340,15 @@ public static IEnumerable IsBetween_Test_Values() // inclusive tests foreach (var testCase in EdgeEqualityTestCases()) { - yield return new object[] {true, testCase.input, testCase.low, testCase.high, true}; - yield return new object[] {false, testCase.input, testCase.low, testCase.high, false}; + yield return new object[] { true, testCase.input, testCase.low, testCase.high, true }; + yield return new object[] { false, testCase.input, testCase.low, testCase.high, false }; } // exclusive tests foreach (var testCase in OverlappedTestCases()) { - yield return new object[] {testCase.expected, testCase.input, testCase.low, testCase.high, true}; - yield return new object[] {testCase.expected, testCase.input, testCase.low, testCase.high, false}; + yield return new object[] { testCase.expected, testCase.input, testCase.low, testCase.high, true }; + yield return new object[] { testCase.expected, testCase.input, testCase.low, testCase.high, false }; } // edges are equal @@ -398,26 +388,37 @@ public static IEnumerable IsBetween_Test_Values() IEnumerable<(bool expected, IPAddress input, IPAddress low, IPAddress high)> OverlappedTestCases() { // before low - yield return (false, IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.10.0"), IPAddress.Parse("192.168.10.255")); + yield return ( + false, + IPAddress.Parse("192.168.1.0"), + IPAddress.Parse("192.168.10.0"), + IPAddress.Parse("192.168.10.255") + ); yield return (false, IPAddress.Parse("abc::"), IPAddress.Parse("abc::ff"), IPAddress.Parse("abc::ffff")); // after high - yield return (false, IPAddress.Parse("192.168.20.0"), IPAddress.Parse("192.168.10.0"), IPAddress.Parse("192.168.10.255")); + yield return ( + false, + IPAddress.Parse("192.168.20.0"), + IPAddress.Parse("192.168.10.0"), + IPAddress.Parse("192.168.10.255") + ); yield return (false, IPAddress.Parse("abcd::"), IPAddress.Parse("abc::ff"), IPAddress.Parse("abc::ffff")); // inside range - yield return (true, IPAddress.Parse("192.168.10.128"), IPAddress.Parse("192.168.10.0"), IPAddress.Parse("192.168.10.255")); + yield return ( + true, + IPAddress.Parse("192.168.10.128"), + IPAddress.Parse("192.168.10.0"), + IPAddress.Parse("192.168.10.255") + ); yield return (true, IPAddress.Parse("abc::fff0"), IPAddress.Parse("abc::ff"), IPAddress.Parse("abc::ffff")); } } [Theory] [MemberData(nameof(IsBetween_Test_Values))] - public void IsBetween_Test(bool expected, - IPAddress input, - IPAddress low, - IPAddress high, - bool inclusive) + public void IsBetween_Test(bool expected, IPAddress input, IPAddress low, IPAddress high, bool inclusive) { // Arrange // Act @@ -433,8 +434,8 @@ public void IsBetween_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => ((IPAddress) null).IsBetween(IPAddress.Any, IPAddress.Any)); + + Assert.Throws(() => ((IPAddress)null).IsBetween(IPAddress.Any, IPAddress.Any)); } [Fact] @@ -443,7 +444,7 @@ public void IsBetween_NullHigh_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddress.Any.IsBetween(IPAddress.Any, null)); } @@ -453,7 +454,7 @@ public void IsBetween_NullLow_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddress.Any.IsBetween(null, IPAddress.Any)); } @@ -463,8 +464,10 @@ public void IsBetween_LowGreaterThanHigh_Throws_InvalidOperationException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => IPAddress.Any.IsBetween(IPAddress.Parse("100.1.1.1"), IPAddress.Parse("10.1.1.1"))); + + Assert.Throws( + () => IPAddress.Any.IsBetween(IPAddress.Parse("100.1.1.1"), IPAddress.Parse("10.1.1.1")) + ); } public static IEnumerable IsBetween_UnmatchedAddressFamilies_Test_Values() @@ -472,20 +475,22 @@ public static IEnumerable IsBetween_UnmatchedAddressFamilies_Test_Valu var ipv4 = IPAddress.Any; var ipv6 = IPAddress.IPv6Any; - yield return new object[] {ipv4, ipv4, ipv6}; - yield return new object[] {ipv4, ipv6, ipv4}; - yield return new object[] {ipv6, ipv4, ipv4}; + yield return new object[] { ipv4, ipv4, ipv6 }; + yield return new object[] { ipv4, ipv6, ipv4 }; + yield return new object[] { ipv6, ipv4, ipv4 }; - yield return new object[] {ipv6, ipv6, ipv4}; - yield return new object[] {ipv6, ipv4, ipv6}; - yield return new object[] {ipv4, ipv6, ipv6}; + yield return new object[] { ipv6, ipv6, ipv4 }; + yield return new object[] { ipv6, ipv4, ipv6 }; + yield return new object[] { ipv4, ipv6, ipv6 }; } [Theory] [MemberData(nameof(IsBetween_UnmatchedAddressFamilies_Test_Values))] - public void IsBetween_UnmatchedAddressFamilies_Throws_InvalidOperationException_Test(IPAddress input, - IPAddress low, - IPAddress high) + public void IsBetween_UnmatchedAddressFamilies_Throws_InvalidOperationException_Test( + IPAddress input, + IPAddress low, + IPAddress high + ) { // Arrange // Act @@ -504,8 +509,7 @@ public void IsBetween_UnmatchedAddressFamilies_Throws_InvalidOperationException_ [InlineData(false, "7777:7777:7777:7777:7777:7777:7777:7777")] [InlineData(true, "255.255.255.255")] [InlineData(true, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] - public void IsAtMax_Test(bool expected, - string input) + public void IsAtMax_Test(bool expected, string input) { // Arrange var address = IPAddress.Parse(input); @@ -523,8 +527,8 @@ public void IsAtMax_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => ((IPAddress) null).IsAtMax()); + + Assert.Throws(() => ((IPAddress)null).IsAtMax()); } #endregion // end: IsAtMax @@ -538,8 +542,7 @@ public void IsAtMax_NullInput_Throws_ArgumentNullException_Test() [InlineData(false, "7777:7777:7777:7777:7777:7777:7777:7777")] [InlineData(true, "::")] [InlineData(true, "0.0.0.0")] - public void IsAtMin_Test(bool expected, - string input) + public void IsAtMin_Test(bool expected, string input) { // Arrange var address = IPAddress.Parse(input); @@ -557,8 +560,8 @@ public void IsAtMin_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => ((IPAddress) null).IsAtMin()); + + Assert.Throws(() => ((IPAddress)null).IsAtMin()); } #endregion // end: IsAtMin @@ -570,23 +573,21 @@ public static IEnumerable Max_Test_Values() var minIpv4 = IPAddress.Parse("192.168.1.1"); var maxIpv4 = IPAddress.Parse("192.168.100.1"); - yield return new object[] {maxIpv4, maxIpv4, maxIpv4}; - yield return new object[] {maxIpv4, minIpv4, maxIpv4}; - yield return new object[] {maxIpv4, maxIpv4, minIpv4}; + yield return new object[] { maxIpv4, maxIpv4, maxIpv4 }; + yield return new object[] { maxIpv4, minIpv4, maxIpv4 }; + yield return new object[] { maxIpv4, maxIpv4, minIpv4 }; var minIpv6 = IPAddress.Parse("abc::01"); var maxIpv6 = IPAddress.Parse("ffff::f123"); - yield return new object[] {maxIpv6, maxIpv6, maxIpv6}; - yield return new object[] {maxIpv6, minIpv6, maxIpv6}; - yield return new object[] {maxIpv6, maxIpv6, minIpv6}; + yield return new object[] { maxIpv6, maxIpv6, maxIpv6 }; + yield return new object[] { maxIpv6, minIpv6, maxIpv6 }; + yield return new object[] { maxIpv6, maxIpv6, minIpv6 }; } [Theory] [MemberData(nameof(Max_Test_Values))] - public void Max_Test(IPAddress expected, - IPAddress left, - IPAddress right) + public void Max_Test(IPAddress expected, IPAddress left, IPAddress right) { // Arrange // Act @@ -602,7 +603,7 @@ public void Max_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressMath.Max(null, IPAddress.Any)); Assert.Throws(() => IPAddressMath.Max(IPAddress.Any, null)); Assert.Throws(() => IPAddressMath.Max(null, null)); @@ -627,23 +628,21 @@ public static IEnumerable Min_Test_Values() var minIpv4 = IPAddress.Parse("192.168.1.1"); var maxIpv4 = IPAddress.Parse("192.168.100.1"); - yield return new object[] {minIpv4, minIpv4, minIpv4}; - yield return new object[] {minIpv4, minIpv4, maxIpv4}; - yield return new object[] {minIpv4, maxIpv4, minIpv4}; + yield return new object[] { minIpv4, minIpv4, minIpv4 }; + yield return new object[] { minIpv4, minIpv4, maxIpv4 }; + yield return new object[] { minIpv4, maxIpv4, minIpv4 }; var minIpv6 = IPAddress.Parse("abc::01"); var maxIpv6 = IPAddress.Parse("ffff::f123"); - yield return new object[] {minIpv6, minIpv6, minIpv6}; - yield return new object[] {minIpv6, minIpv6, maxIpv6}; - yield return new object[] {minIpv6, maxIpv6, minIpv6}; + yield return new object[] { minIpv6, minIpv6, minIpv6 }; + yield return new object[] { minIpv6, minIpv6, maxIpv6 }; + yield return new object[] { minIpv6, maxIpv6, minIpv6 }; } [Theory] [MemberData(nameof(Min_Test_Values))] - public void Min_Test(IPAddress expected, - IPAddress left, - IPAddress right) + public void Min_Test(IPAddress expected, IPAddress left, IPAddress right) { // Arrange // Act @@ -659,7 +658,7 @@ public void Min_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressMath.Min(null, IPAddress.Any)); Assert.Throws(() => IPAddressMath.Min(IPAddress.Any, null)); Assert.Throws(() => IPAddressMath.Min(null, null)); @@ -700,9 +699,7 @@ public void Min_MismatchedAddressFamily_Throws_InvalidOperationException_Test() [InlineData("255.255.255.254", "255.255.255.255", -1)] [InlineData("255.255.255.255", "255.255.255.254", 1)] [InlineData("255.255.255.255", "255.255.255.255", 0)] - public void Increment_Test(string expected, - string input, - long delta) + public void Increment_Test(string expected, string input, long delta) { // Arrange var address = IPAddress.Parse(input); @@ -719,8 +716,7 @@ public void Increment_Test(string expected, [InlineData("::FF", -1024)] [InlineData("0.0.0.0", -1)] [InlineData("0.0.0.255", -1024)] - public void Increment_Underflow_Throws_InvalidOperationException_Test(string input, - long delta) + public void Increment_Underflow_Throws_InvalidOperationException_Test(string input, long delta) { // Arrange var address = IPAddress.Parse(input); @@ -735,8 +731,7 @@ public void Increment_Underflow_Throws_InvalidOperationException_Test(string inp [InlineData("255.255.255.255", 1)] [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff", 65535)] [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 1)] - public void Increment_OverflowThrows_InvalidOperationException_Test(string input, - long delta) + public void Increment_OverflowThrows_InvalidOperationException_Test(string input, long delta) { // Arrange var address = IPAddress.Parse(input); @@ -752,8 +747,8 @@ public void Increment_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => ((IPAddress) null).Increment()); + + Assert.Throws(() => ((IPAddress)null).Increment()); } #region TryIncrement @@ -788,10 +783,7 @@ public void Increment_NullInput_Throws_ArgumentNullException_Test() [InlineData(true, "255.255.255.254", "255.255.255.255", -1)] [InlineData(true, "255.255.255.255", "255.255.255.254", 1)] [InlineData(true, "255.255.255.255", "255.255.255.255", 0)] - public void TryIncrement_Test(bool expectedSuccess, - string expectedResultString, - string inputString, - long delta) + public void TryIncrement_Test(bool expectedSuccess, string expectedResultString, string inputString, long delta) { // Arrange _ = IPAddress.TryParse(inputString, out var input); diff --git a/src/Arcus.Tests/SubnetTests.cs b/src/Arcus.Tests/SubnetTests.cs index 64b53b5..4a3c461 100644 --- a/src/Arcus.Tests/SubnetTests.cs +++ b/src/Arcus.Tests/SubnetTests.cs @@ -1,1850 +1,1808 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Net; using System.Numerics; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; using System.Text; +using Arcus; +using Arcus.Tests.XunitSerializers; using Gulliver; using Xunit; -using Xunit.Abstractions; +using Xunit.Sdk; +#if NET48 // maintained for .NET 4.8 compatibility +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +#endif + +[assembly: RegisterXunitSerializer(typeof(SubnetXunitSerializer), typeof(Subnet))] namespace Arcus.Tests { - public class SubnetTests - { - #region Setup / Teardown - - public SubnetTests(ITestOutputHelper testOutputHelper) - { - this._testOutputHelper = testOutputHelper; - } - - private readonly ITestOutputHelper _testOutputHelper; - - #endregion - - #region Addresses - - [Theory] - [InlineData("192.168.1.0/24")] - [InlineData("16.8.14.12/28")] - [InlineData("16.8.14.12/32")] - [InlineData("::/128")] - [InlineData("feed:beef::/120")] - public void Addresses_Test(string input) - { - // Arrange - var subnet = Subnet.Parse(input); - - // Act - var hosts = subnet.ToList(); - - // Assert - Assert.Equal(subnet.ToArray(), hosts); - } - - #endregion // end: Addresses - - #region Class - - [Theory] - [InlineData(typeof(AbstractIPAddressRange))] - [InlineData(typeof(IEquatable))] - [InlineData(typeof(IComparable))] - [InlineData(typeof(IComparable))] - [InlineData(typeof(ISerializable))] - public void Assignability_Test(Type assignableFromType) - { - // Arrange - var type = typeof(Subnet); - - // Act - var isAssignableFrom = assignableFromType.IsAssignableFrom(type); - - // Assert - Assert.True(isAssignableFrom); - } - - #endregion // end: Class - - #region CompareTo / Operators - - public static IEnumerable Comparison_Values() - { - yield return new object[] { 0, Subnet.Parse("192.168.0.0/16"), Subnet.Parse("192.168.0.0/16") }; - yield return new object[] { 0, Subnet.Parse("ab:cd::/64"), Subnet.Parse("ab:cd::/64") }; - yield return new object[] { 1, Subnet.Parse("192.168.0.0/16"), null }; - yield return new object[] { 1, Subnet.Parse("ab:cd::/64"), null }; - yield return new object[] { 1, Subnet.Parse("192.168.0.0/16"), Subnet.Parse("192.168.0.0/20") }; - yield return new object[] { -1, Subnet.Parse("192.168.0.0/20"), Subnet.Parse("192.168.0.0/16") }; - yield return new object[] { 1, Subnet.Parse("ab:cd::/64"), Subnet.Parse("ab:cd::/96") }; - yield return new object[] { -1, Subnet.Parse("ab:cd::/96"), Subnet.Parse("ab:cd::/64") }; - yield return new object[] { -1, Subnet.Parse("0.0.0.0/0"), Subnet.Parse("::/0") }; - yield return new object[] { 1, Subnet.Parse("::/0"), Subnet.Parse("0.0.0.0/0") }; - yield return new object[] { -1, Subnet.Parse("0.0.0.0/32"), Subnet.Parse("::/128") }; - yield return new object[] { 1, Subnet.Parse("::/128"), Subnet.Parse("0.0.0.0/32") }; - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void CompareTo_Test(int expected, - Subnet left, - Subnet right) - { - // Arrange - // Act - var result = left.CompareTo(right); - - // Assert - Assert.Equal(expected, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_Equals_Test(int expected, - Subnet left, - Subnet right) - { - // Arrange - // Act - var result = left == right; - - // Assert - Assert.Equal(expected == 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_NotEquals_Test(int expected, - Subnet left, - Subnet right) - { - // Arrange - // Act - var result = left != right; - - // Assert - Assert.Equal(expected != 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_GreaterThan_Test(int expected, - Subnet left, - Subnet right) - { - // Arrange - // Act - var result = left > right; - - // Assert - Assert.Equal(expected > 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_GreaterThanOrEqual_Test(int expected, - Subnet left, - Subnet right) - { - // Arrange - // Act - var result = left >= right; - - // Assert - Assert.Equal(expected >= 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_LessThan_Test(int expected, - Subnet left, - Subnet right) - { - // Arrange - // Act - var result = left < right; - - // Assert - Assert.Equal(expected < 0, result); - } - - [Theory] - [MemberData(nameof(Comparison_Values))] - public void Operator_LessThanOrEqual_Test(int expected, - Subnet left, - Subnet right) - { - // Arrange - // Act - var result = left <= right; - - // Assert - Assert.Equal(expected <= 0, result); - } - - #endregion - - #region Netmask - - [Fact] - public void Netmask_Null_ForIPv6_Test() - { - // Arrange - var subnet = new Subnet(IPAddress.IPv6Any, 32); - - // Act - var netmask = subnet.Netmask; - - // Assert - Assert.Null(netmask); - } - - #endregion // end: Netmask - - #region Overlaps - - [Theory] - [InlineData(true, "0.0.0.0/0", "0.0.0.0/0")] - [InlineData(true, "::/0", "::/0")] - [InlineData(true, "0.0.0.0/0", "255.255.0.0/16")] - [InlineData(true, "255.255.0.0/16", "0.0.0.0/0")] - [InlineData(true, "::/0", "abcd:ef01::/64")] - [InlineData(true, "abcd:ef01::/64", "::/0")] - [InlineData(false, "0.0.0.0/0", null)] - [InlineData(false, "::/0", null)] - [InlineData(false, "0.0.0.0/0", "::/0")] - [InlineData(false, "::/0", "0.0.0.0/0")] - public void Overlaps_Test(bool expected, - string subnetAString, - string subnetBString) - { - // Arrange - var subnetA = Subnet.Parse(subnetAString); - _ = Subnet.TryParse(subnetBString, out var subnetB); - - // Act - var result = subnetA.Overlaps(subnetB); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: Overlaps - - #region ToString - - [Theory] - [InlineData("192.168.1.1/32", "192.168.1.1/32")] - [InlineData("192.168.0.0/16", "192.168.1.1/16")] - [InlineData("0.0.0.0/0", "192.168.1.1/0")] - [InlineData("::/128", "::/128")] - [InlineData("::/64", "::/64")] - [InlineData("::/0", "::/0")] - public void ToString_Test(string expected, - string input) - { - // Arrange - // Act - var result = Subnet.Parse(input) - .ToString(); - - // Assert - Assert.Equal(expected, result); - } - - #endregion - - #region UsableHostAddressCount - - public static IEnumerable UsableHostAddressCount_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + public class SubnetTests + { + #region Addresses + + [Theory] + [InlineData("192.168.1.0/24")] + [InlineData("16.8.14.12/28")] + [InlineData("16.8.14.12/32")] + [InlineData("::/128")] + [InlineData("feed:beef::/120")] + public void Addresses_Test(string input) + { + // Arrange + var subnet = Subnet.Parse(input); + + // Act + var hosts = subnet.ToList(); + + // Assert + Assert.Equal(subnet.ToArray(), hosts); + } + + #endregion // end: Addresses + + #region Class + + [Theory] + [InlineData(typeof(AbstractIPAddressRange))] + [InlineData(typeof(IEquatable))] + [InlineData(typeof(IComparable))] + [InlineData(typeof(IComparable))] +#if NET48 + [InlineData(typeof(ISerializable))] +#endif + public void Assignability_Test(Type assignableFromType) + { + // Arrange + var type = typeof(Subnet); + + // Act + var isAssignableFrom = assignableFromType.IsAssignableFrom(type); + + // Assert + Assert.True(isAssignableFrom); + } + + #endregion // end: Class + + #region CompareTo / Operators + + public static IEnumerable Comparison_Values() + { + yield return new object[] { 0, Subnet.Parse("192.168.0.0/16"), Subnet.Parse("192.168.0.0/16") }; + yield return new object[] { 0, Subnet.Parse("ab:cd::/64"), Subnet.Parse("ab:cd::/64") }; + yield return new object[] { 1, Subnet.Parse("192.168.0.0/16"), null }; + yield return new object[] { 1, Subnet.Parse("ab:cd::/64"), null }; + yield return new object[] { 1, Subnet.Parse("192.168.0.0/16"), Subnet.Parse("192.168.0.0/20") }; + yield return new object[] { -1, Subnet.Parse("192.168.0.0/20"), Subnet.Parse("192.168.0.0/16") }; + yield return new object[] { 1, Subnet.Parse("ab:cd::/64"), Subnet.Parse("ab:cd::/96") }; + yield return new object[] { -1, Subnet.Parse("ab:cd::/96"), Subnet.Parse("ab:cd::/64") }; + yield return new object[] { -1, Subnet.Parse("0.0.0.0/0"), Subnet.Parse("::/0") }; + yield return new object[] { 1, Subnet.Parse("::/0"), Subnet.Parse("0.0.0.0/0") }; + yield return new object[] { -1, Subnet.Parse("0.0.0.0/32"), Subnet.Parse("::/128") }; + yield return new object[] { 1, Subnet.Parse("::/128"), Subnet.Parse("0.0.0.0/32") }; + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void CompareTo_Test(int expected, Subnet left, Subnet right) + { + // Arrange + // Act + var result = left.CompareTo(right); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_Equals_Test(int expected, Subnet left, Subnet right) + { + // Arrange + // Act + var result = left == right; + + // Assert + Assert.Equal(expected == 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_NotEquals_Test(int expected, Subnet left, Subnet right) + { + // Arrange + // Act + var result = left != right; + + // Assert + Assert.Equal(expected != 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_GreaterThan_Test(int expected, Subnet left, Subnet right) + { + // Arrange + // Act + var result = left > right; + + // Assert + Assert.Equal(expected > 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_GreaterThanOrEqual_Test(int expected, Subnet left, Subnet right) + { + // Arrange + // Act + var result = left >= right; + + // Assert + Assert.Equal(expected >= 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_LessThan_Test(int expected, Subnet left, Subnet right) + { + // Arrange + // Act + var result = left < right; + + // Assert + Assert.Equal(expected < 0, result); + } + + [Theory] + [MemberData(nameof(Comparison_Values))] + public void Operator_LessThanOrEqual_Test(int expected, Subnet left, Subnet right) + { + // Arrange + // Act + var result = left <= right; + + // Assert + Assert.Equal(expected <= 0, result); + } + + #endregion + + #region Netmask + + [Fact] + public void Netmask_Null_ForIPv6_Test() + { + // Arrange + var subnet = new Subnet(IPAddress.IPv6Any, 32); + + // Act + var netmask = subnet.Netmask; + + // Assert + Assert.Null(netmask); + } + + #endregion // end: Netmask + + #region Overlaps + + [Theory] + [InlineData(true, "0.0.0.0/0", "0.0.0.0/0")] + [InlineData(true, "::/0", "::/0")] + [InlineData(true, "0.0.0.0/0", "255.255.0.0/16")] + [InlineData(true, "255.255.0.0/16", "0.0.0.0/0")] + [InlineData(true, "::/0", "abcd:ef01::/64")] + [InlineData(true, "abcd:ef01::/64", "::/0")] + [InlineData(false, "0.0.0.0/0", null)] + [InlineData(false, "::/0", null)] + [InlineData(false, "0.0.0.0/0", "::/0")] + [InlineData(false, "::/0", "0.0.0.0/0")] + public void Overlaps_Test(bool expected, string subnetAString, string subnetBString) + { + // Arrange + var subnetA = Subnet.Parse(subnetAString); + _ = Subnet.TryParse(subnetBString, out var subnetB); + + // Act + var result = subnetA.Overlaps(subnetB); + + // Assert + Assert.Equal(expected, result); + } + + #endregion // end: Overlaps + + #region ToString + + [Theory] + [InlineData("192.168.1.1/32", "192.168.1.1/32")] + [InlineData("192.168.0.0/16", "192.168.1.1/16")] + [InlineData("0.0.0.0/0", "192.168.1.1/0")] + [InlineData("::/128", "::/128")] + [InlineData("::/64", "::/64")] + [InlineData("::/0", "::/0")] + public void ToString_Test(string expected, string input) + { + // Arrange + // Act + var result = Subnet.Parse(input).ToString(); + + // Assert + Assert.Equal(expected, result); + } + + #endregion + + #region UsableHostAddressCount + + public static IEnumerable UsableHostAddressCount_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var routePrefix = 32 - i; + var count = routePrefix < 2 ? BigInteger.Zero : BigInteger.Subtract(BigInteger.Pow(2, routePrefix), 2); + + var subnet = new Subnet(ipAddress, i); + yield return new object[] { count, subnet }; + } + } + + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var routePrefix = 128 - i; + var count = routePrefix < 2 ? BigInteger.Zero : BigInteger.Subtract(BigInteger.Pow(2, routePrefix), 2); + + var subnet = new Subnet(ipAddress, i); + yield return new object[] { count, subnet }; + } + } + + IEnumerable IPv4Addresses() { - var routePrefix = 32 - i; - var count = routePrefix < 2 - ? BigInteger.Zero - : BigInteger.Subtract(BigInteger.Pow(2, routePrefix), 2); - - var subnet = new Subnet(ipAddress, i); - yield return new object[] { count, subnet }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var routePrefix = 128 - i; - var count = routePrefix < 2 - ? BigInteger.Zero - : BigInteger.Subtract(BigInteger.Pow(2, routePrefix), 2); - - var subnet = new Subnet(ipAddress, i); - yield return new object[] { count, subnet }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(UsableHostAddressCount_Test_Values))] - public void UsableHostAddressCount_Test(BigInteger expected, - Subnet subnet) - { - // Arrange - - // Act - var result = subnet.UsableHostAddressCount; - - // Assert - Assert.Equal(expected, result); - } - - #endregion - - #region Contains - - #region Contains(IPAddress) - - [Theory] - [InlineData(true, "192.168.0.0/16", "192.168.0.0")] - [InlineData(true, "192.168.0.0/16", "192.168.0.16")] - [InlineData(false, "192.168.0.0/16", "192.255.0.0")] - [InlineData(false, "0.0.0.0/0", null)] - [InlineData(false, "0.0.0.0/0", "::")] - [InlineData(true, "::/0", "::")] - [InlineData(true, "2001:0db8:85a3:0042::/64", "2001:0db8:85a3:0042:1000:8a2e:0370:7334")] - [InlineData(false, "2001:0db8:85a3:0042::/64", "2007:0db8:85a3::abc")] - [InlineData(false, "::/0", null)] - [InlineData(false, "::/0", "192.168.1.1")] - public void Contains_IPAddress_Test(bool expected, - string subnetString, - string containsIPAddressString) - { - // Arrange - var subnet = Subnet.Parse(subnetString); - var containsIPAddress = containsIPAddressString != null - ? IPAddress.Parse(containsIPAddressString) - : null; - - // Act - var result = subnet.Contains(containsIPAddress); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: Contains(IPAddress) - - #region Contains(Subnet) - - [Theory] - [InlineData(true, "192.168.0.0/16", "192.168.0.0/16")] - [InlineData(true, "192.168.0.0/16", "192.168.0.0/32")] - [InlineData(false, "192.168.0.0/16", "192.168.0.0/8")] - [InlineData(false, "192.168.0.0/16", null)] - public void Contains_Subnet_Test(bool expected, - string subnetString, - string containsSubnetString) - { - // Arrange - var subnet = Subnet.Parse(subnetString); - var containsSubnet = containsSubnetString != null - ? Subnet.Parse(containsSubnetString) - : null; - - // Act - var result = subnet.Contains(containsSubnet); - - // Arrange - Assert.Equal(expected, result); - } - - [Fact] - public void Ipv4SubnetDoesNotContainIPv6Test() - { - // Arrange - var subnet = Subnet.Parse("0.0.0.0/0"); - - // Act - - // Assert - Assert.False(subnet.Contains(IPAddress.Parse("::"))); - } - - #endregion // end: Contains(Subnet) - - #endregion // end: Contains - - #region TryIPv4FromPartial - - public static IEnumerable TryIPv4FromPartial_Test_Values() - { - yield return new object[] { null, null }; - yield return new object[] { null, string.Empty }; - yield return new object[] { null, "potato" }; - yield return new object[] { Subnet.Parse("192.0.0.0/8"), "192" }; - yield return new object[] { Subnet.Parse("192.0.0.0/8"), "192." }; - yield return new object[] { Subnet.Parse("192.168.0.0/16"), "192.168" }; - yield return new object[] { Subnet.Parse("192.168.0.0/16"), "192.168." }; - yield return new object[] { Subnet.Parse("192.168.1.0/24"), "192.168.1" }; - yield return new object[] { Subnet.Parse("192.168.1.0/24"), "192.168.1." }; - yield return new object[] { Subnet.Parse("192.168.1.1/32"), "192.168.1.1" }; - yield return new object[] { null, "192.168.1.1." }; - yield return new object[] { null, "192.168.0.1.5" }; - } + yield return IPAddress.Parse("192.168.1.1"); + } - [Theory] - [MemberData(nameof(TryIPv4FromPartial_Test_Values))] - public void TryIPv4FromPartial_Test(Subnet expected, - string input) - { - // Arrange - - // Act - var success = Subnet.TryIPv4FromPartial(input, out var subnet); - - // Assert - Assert.Equal(expected != null, success); - Assert.Equal(expected, subnet); - } - - #endregion // end: TryIPv4FromPartial - - #region TryIPv6FromPartial - - public static IEnumerable TryIPv6FromPartial_Test_Values() - { - // bad input formats - yield return new object[] { Enumerable.Empty(), null }; - yield return new object[] { Enumerable.Empty(), string.Empty }; - yield return new object[] { Enumerable.Empty(), "potato" }; + IEnumerable IPv6Addresses() + { + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(UsableHostAddressCount_Test_Values))] + public void UsableHostAddressCount_Test(BigInteger expected, Subnet subnet) + { + // Arrange + + // Act + var result = subnet.UsableHostAddressCount; + + // Assert + Assert.Equal(expected, result); + } + + #endregion + + #region Contains + + #region Contains(IPAddress) + + [Theory] + [InlineData(true, "192.168.0.0/16", "192.168.0.0")] + [InlineData(true, "192.168.0.0/16", "192.168.0.16")] + [InlineData(false, "192.168.0.0/16", "192.255.0.0")] + [InlineData(false, "0.0.0.0/0", null)] + [InlineData(false, "0.0.0.0/0", "::")] + [InlineData(true, "::/0", "::")] + [InlineData(true, "2001:0db8:85a3:0042::/64", "2001:0db8:85a3:0042:1000:8a2e:0370:7334")] + [InlineData(false, "2001:0db8:85a3:0042::/64", "2007:0db8:85a3::abc")] + [InlineData(false, "::/0", null)] + [InlineData(false, "::/0", "192.168.1.1")] + public void Contains_IPAddress_Test(bool expected, string subnetString, string containsIPAddressString) + { + // Arrange + var subnet = Subnet.Parse(subnetString); + var containsIPAddress = containsIPAddressString != null ? IPAddress.Parse(containsIPAddressString) : null; + + // Act + var result = subnet.Contains(containsIPAddress); + + // Assert + Assert.Equal(expected, result); + } + + #endregion // end: Contains(IPAddress) + + #region Contains(Subnet) + + [Theory] + [InlineData(true, "192.168.0.0/16", "192.168.0.0/16")] + [InlineData(true, "192.168.0.0/16", "192.168.0.0/32")] + [InlineData(false, "192.168.0.0/16", "192.168.0.0/8")] + [InlineData(false, "192.168.0.0/16", null)] + public void Contains_Subnet_Test(bool expected, string subnetString, string containsSubnetString) + { + // Arrange + var subnet = Subnet.Parse(subnetString); + var containsSubnet = containsSubnetString != null ? Subnet.Parse(containsSubnetString) : null; + + // Act + var result = subnet.Contains(containsSubnet); + + // Arrange + Assert.Equal(expected, result); + } + + [Fact] + public void Ipv4SubnetDoesNotContainIPv6Test() + { + // Arrange + var subnet = Subnet.Parse("0.0.0.0/0"); + + // Act + + // Assert + Assert.False(subnet.Contains(IPAddress.Parse("::"))); + } + + #endregion // end: Contains(Subnet) + + #endregion // end: Contains + + #region TryIPv4FromPartial + + public static IEnumerable TryIPv4FromPartial_Test_Values() + { + yield return new object[] { null, null }; + yield return new object[] { null, string.Empty }; + yield return new object[] { null, "potato" }; + yield return new object[] { Subnet.Parse("192.0.0.0/8"), "192" }; + yield return new object[] { Subnet.Parse("192.0.0.0/8"), "192." }; + yield return new object[] { Subnet.Parse("192.168.0.0/16"), "192.168" }; + yield return new object[] { Subnet.Parse("192.168.0.0/16"), "192.168." }; + yield return new object[] { Subnet.Parse("192.168.1.0/24"), "192.168.1" }; + yield return new object[] { Subnet.Parse("192.168.1.0/24"), "192.168.1." }; + yield return new object[] { Subnet.Parse("192.168.1.1/32"), "192.168.1.1" }; + yield return new object[] { null, "192.168.1.1." }; + yield return new object[] { null, "192.168.0.1.5" }; + yield return new object[] { null, "10.209.005.029" }; // Addresses #59 - [BUG] Subnet.TryIPv4FromPartial(string , out Subnet) throws FormatException if the string input contains a malformed 0-prefixed octet that is not a valid octal number. + } + + [Theory] + [MemberData(nameof(TryIPv4FromPartial_Test_Values))] + public void TryIPv4FromPartial_Test(Subnet expected, string input) + { + // Arrange + + // Act + var success = Subnet.TryIPv4FromPartial(input, out var subnet); + + // Assert + Assert.Equal(expected != null, success); + Assert.Equal(expected, subnet); + } + + #endregion // end: TryIPv4FromPartial + + #region TryIPv6FromPartial + + public static IEnumerable TryIPv6FromPartial_Test_Values() + { + // bad input formats + yield return new object[] { Enumerable.Empty(), null }; + yield return new object[] { Enumerable.Empty(), string.Empty }; + yield return new object[] { Enumerable.Empty(), "potato" }; + + // invalid input + yield return new object[] { Enumerable.Empty(), ":" }; + yield return new object[] { Enumerable.Empty(), ":::" }; + yield return new object[] { Enumerable.Empty(), "0:0:0:0:0:0:0:0:0" }; // too many hextets + yield return new object[] { Enumerable.Empty(), "0:0:0:0:0:0:0:0::" }; + + // invalid input, multiple "::" + yield return new object[] { Enumerable.Empty(), "::0::" }; + yield return new object[] { Enumerable.Empty(), "0::0::0" }; + yield return new object[] { Enumerable.Empty(), "::0:0:0::" }; + + // explicit valid subnets + yield return new object[] { new[] { Subnet.Parse("::/128") }, "::/128" }; + yield return new object[] { new[] { Subnet.Parse("2001:db8:85a3:42::/64") }, "2001:db8:85a3:42::/64" }; + yield return new object[] + { + new[] { Subnet.Parse("2001:0db8:85a3:0042:1000:0001:0370:7334/128") }, + "2001:0db8:85a3:0042:1000:0001:0370:7334/128", + }; - // invalid input - yield return new object[] { Enumerable.Empty(), ":" }; - yield return new object[] { Enumerable.Empty(), ":::" }; - yield return new object[] { Enumerable.Empty(), "0:0:0:0:0:0:0:0:0" }; // too many hextets - yield return new object[] { Enumerable.Empty(), "0:0:0:0:0:0:0:0::" }; + for (var hextetCount = 0; hextetCount <= 8; hextetCount++) + { + var sb = new StringBuilder(); - // invalid input, multiple "::" - yield return new object[] { Enumerable.Empty(), "::0::" }; - yield return new object[] { Enumerable.Empty(), "0::0::0" }; - yield return new object[] { Enumerable.Empty(), "::0:0:0::" }; + sb.Append(string.Join(":", Enumerable.Repeat("0", hextetCount))); - // explicit valid subnets - yield return new object[] { new[] { Subnet.Parse("::/128") }, "::/128" }; - yield return new object[] { new[] { Subnet.Parse("2001:db8:85a3:42::/64") }, "2001:db8:85a3:42::/64" }; - yield return new object[] { new[] { Subnet.Parse("2001:0db8:85a3:0042:1000:0001:0370:7334/128") }, "2001:0db8:85a3:0042:1000:0001:0370:7334/128" }; + if (hextetCount < 8) + { + sb.Append("::"); + } - for (var hextetCount = 0; hextetCount <= 8; hextetCount++) - { - var sb = new StringBuilder(); + var subnets = new List(); + for (var i = 0; i <= 8 - hextetCount; i++) + { + subnets.Add(Subnet.Parse($"::/{128 - (16 * i)}")); + } - sb.Append(string.Join(":", Enumerable.Repeat("0", hextetCount))); + yield return new object[] { subnets, sb.ToString() }; + } - if (hextetCount < 8) + var hextets = "2001:0db8:85a3:0042:1000:0001:0370:7334".Split(':'); + + for (var hextetCount = 1; hextetCount <= 8; hextetCount++) { - sb.Append("::"); + var subnets = new List(); + for (var i = 8 - hextetCount; i >= 0; i--) + { + var enumerable = hextets.Take(hextetCount).Select(s => new string(s.SkipWhile(c => c == '0').ToArray())); + var trimmedLeadingZero = string.Join(":", enumerable); + var subnet = + hextetCount < 8 + ? Subnet.Parse($"{trimmedLeadingZero}::/{128 - (16 * i)}") + : Subnet.Parse($"{trimmedLeadingZero}"); + + subnets.Add(subnet); + } + + var inputString = string.Join(":", hextets.Take(hextetCount)); + + if (hextetCount < 8) + { + yield return new object[] { subnets, $"{inputString}::" }; + yield return new object[] { subnets, $"{inputString}:" }; + } + + if ( + !string.IsNullOrEmpty(inputString) // no match for an empty string + && inputString != hextets[0] + ) // TODO should this work, a hextet w/o any ':'? Unsure + { + yield return new object[] { subnets, inputString }; + } } + } + + [Theory] + [MemberData(nameof(TryIPv6FromPartial_Test_Values))] + public void TryIPv6FromPartial_Test(IEnumerable expected, string input) + { + // Arrange + // Act +#pragma warning disable CS0618 // testing a known obsolete method; ignoring the fact that it is obsolete + var success = Subnet.TryIPv6FromPartial(input, out var subnets); +#pragma warning restore CS0618 + + // Assert + var expectedList = expected.ToList(); + var subnetList = subnets.ToList(); + + Assert.Equal(expectedList.Any(), success); + Assert.Equal(expectedList.Count, subnetList.Count); + Assert.All(subnetList, subnet => Assert.Contains(subnet, expectedList)); + } + + #endregion // end: TryIPv6FromPartial - var subnets = new List(); - for (var i = 0; i <= 8 - hextetCount; i++) + #region Ctor + + #region Ctor(IPAddress) + + public static IEnumerable Ctor_IPAddress_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) { - subnets.Add(Subnet.Parse($"::/{128 - (16 * i)}")); + var subnet = new Subnet(ipAddress, 32); + yield return new object[] { subnet, ipAddress }; } - yield return new object[] { subnets, sb.ToString() }; - } + foreach (var ipAddress in IPv6Addresses()) + { + var subnet = new Subnet(ipAddress, 128); + yield return new object[] { subnet, ipAddress }; + } - var hextets = "2001:0db8:85a3:0042:1000:0001:0370:7334".Split(":"); + IEnumerable IPv4Addresses() + { + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } - for (var hextetCount = 1; hextetCount <= 8; hextetCount++) - { - var subnets = new List(); - for (var i = 8 - hextetCount; i >= 0; i--) + IEnumerable IPv6Addresses() { - var enumerable = hextets.Take(hextetCount) - .Select(s => new string(s.SkipWhile(c => c == '0') - .ToArray())); - var trimmedLeadingZero = string.Join(":", enumerable); - var subnet = hextetCount < 8 - ? Subnet.Parse($"{trimmedLeadingZero}::/{128 - (16 * i)}") - : Subnet.Parse($"{trimmedLeadingZero}"); + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(Ctor_IPAddress_Test_Values))] + public void Ctor_IPAddress_Test(Subnet expected, IPAddress address) + { + // Arrange + // Act + var subnet = new Subnet(address); + + // Assert + Assert.Equal(expected, subnet); + } + + [Fact] + public void Ctor_IPAddress_NullIPAddress_Throws_ArgumentNullException_Test() + { + Assert.Throws(() => new Subnet(null)); + } + + #endregion // end: Ctor(IPAddress) + + #region Ctor(IPAddress, int) + + [Fact] + public void Ctor_IPAddress_Int_NullIPAddress_Throws_ArgumentNullException_Test() + { + Assert.Throws(() => new Subnet(null, 42)); + } + + [Theory] + [InlineData("192.168.1.1", -1)] + [InlineData("192.168.1.1", 33)] + [InlineData("::", -1)] + [InlineData("::", 129)] + public void Ctor_IPAddress_Int_IntOutOfRange_Throws_ArgumentNullException_Test(string address, int routingPrefix) + { + Assert.Throws(() => new Subnet(IPAddress.Parse(address), routingPrefix)); + } + + #endregion + + #region Ctor(IPAddress, IPAddress) + + public static IEnumerable Ctor_IPAddress_IPAddress_Test_Values() + { + yield return new object[] + { + new Subnet(IPAddress.Parse("0.0.0.0"), 0), + IPAddress.Parse("0.0.0.0"), + IPAddress.Parse("255.255.255.255"), + }; + yield return new object[] + { + new Subnet(IPAddress.Parse("::"), 0), + IPAddress.Parse("::"), + IPAddress.Parse("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"), + }; - subnets.Add(subnet); + foreach (var address in IPv4Addresses()) + { + for (var routePrefix = 0; routePrefix <= 32; routePrefix++) + { + var subnet = new Subnet(address, routePrefix); + yield return new object[] { subnet, subnet.Head, subnet.Tail }; + } } - var inputString = string.Join(":", hextets.Take(hextetCount)); + foreach (var address in IPv6Addresses()) + { + for (var routePrefix = 0; routePrefix <= 128; routePrefix++) + { + var subnet = new Subnet(address, routePrefix); + yield return new object[] { subnet, subnet.Head, subnet.Tail }; + } + } - if (hextetCount < 8) + IEnumerable IPv4Addresses() { - yield return new object[] { subnets, $"{inputString}::" }; - yield return new object[] { subnets, $"{inputString}:" }; + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); } - if (!string.IsNullOrEmpty(inputString) // no match for an empty string - && inputString != hextets[0]) // TODO should this work, a hextet w/o any ':'? Unsure + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(Ctor_IPAddress_IPAddress_Test_Values))] + public void Ctor_IPAddress_IPAddress_Test(Subnet expected, IPAddress primary, IPAddress secondary) + { + // Arrange + // Act + var result = new Subnet(primary, secondary); + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("192.168.3.25", null)] + [InlineData(null, "192.168.3.25")] + [InlineData("::", null)] + [InlineData(null, "::")] + [InlineData(null, null)] + public void Ctor_IPAddress_IPAddress_Null_Input_Throws_ArgumentNullException_Test(string primary, string secondary) + { + // Arrange + _ = IPAddress.TryParse(primary, out var primaryAddress); + _ = IPAddress.TryParse(secondary, out var secondaryAddress); + + // Act + // Assert + Assert.Throws(() => new Subnet(primaryAddress, secondaryAddress)); + } + + [Theory] + [InlineData("192.168.3.25", "192.168.3.0")] + [InlineData("ff::dc", "::")] + public void Ctor_IPAddress_IPAddress_Input_Invalid_Ordering_Throws_InvalidOperationException_Test( + string primary, + string secondary + ) + { + // Arrange + var primaryAddress = IPAddress.Parse(primary); + var secondaryAddress = IPAddress.Parse(secondary); + + // Act + // Assert + Assert.Throws(() => new Subnet(primaryAddress, secondaryAddress)); + } + + [Theory] + [InlineData("192.168.3.25", "2001:0db8:85a3:0042:1000:8a2e:0370:7334")] + [InlineData("2001:0db8:85a3:0042:1000:8a2e:0370:7334", "192.168.3.25")] + public void Ctor_IPAddress_IPAddress_MismatchAddressFamily_Throws_ArgumentException_Test( + string primary, + string secondary + ) + { + // Arrange + var primaryAddress = IPAddress.Parse(primary); + var secondaryAddress = IPAddress.Parse(secondary); + + // Act + // Assert + Assert.Throws(() => new Subnet(primaryAddress, secondaryAddress)); + } + + #endregion // end: Ctor(IPAddress, IPAddress) + + #endregion // end: Ctor + + #region ISerializable +#if NET48 // maintained for .NET 4.8 compatibility + public static IEnumerable CanSerializable_Test_Values() + { + yield return new object[] { new Subnet(IPAddress.Parse("192.168.1.0")) }; + yield return new object[] { new Subnet(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")) }; + yield return new object[] { new Subnet(IPAddress.Parse("::"), IPAddress.Parse("::FFFF")) }; + } + + [Theory] + [MemberData(nameof(CanSerializable_Test_Values))] + public void CanSerializable_Test(Subnet subnet) + { + // Arrange + var formatter = new BinaryFormatter(); + + // Act + using (var writeStream = new MemoryStream()) { - yield return new object[] { subnets, inputString }; + formatter.Serialize(writeStream, subnet); + writeStream.Seek(0, SeekOrigin.Begin); + + // Deserialize the object from the stream + var result = formatter.Deserialize(writeStream); + + // Assert + var actual = Assert.IsType(result); + + // using explicit EqualityComparer to avoid comparing elements of enumerable + Assert.Equal(subnet, actual, SubnetEqualityComparer.Instance); } - } - } + } +#endif + #endregion end: ISerializable - [Theory] - [MemberData(nameof(TryIPv6FromPartial_Test_Values))] - public void TryIPv6FromPartial_Test(IEnumerable expected, - string input) - { - // Arrange - // Act -#pragma warning disable CS0618 // testing a known obsolete method; ignoring the fact that it is obsolete - var success = Subnet.TryIPv6FromPartial(input, out var subnets); -#pragma warning restore CS0618 + #region Static Factory Methods - // Assert - var expectedList = expected.ToList(); - var subnetList = subnets.ToList(); - - Assert.Equal(expectedList.Any(), success); - Assert.Equal(expectedList.Count, subnetList.Count); - Assert.All(subnetList, subnet => Assert.Contains(subnet, expectedList)); - } - - #endregion // end: TryIPv6FromPartial - - #region Ctor - - #region Ctor(IPAddress) - - public static IEnumerable Ctor_IPAddress_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { - var subnet = new Subnet(ipAddress, 32); - yield return new object[] { subnet, ipAddress }; - } - - foreach (var ipAddress in IPv6Addresses()) - { - var subnet = new Subnet(ipAddress, 128); - yield return new object[] { subnet, ipAddress }; - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(Ctor_IPAddress_Test_Values))] - public void Ctor_IPAddress_Test(Subnet expected, - IPAddress address) - { - // Arrange - // Act - var subnet = new Subnet(address); - - // Assert - Assert.Equal(expected, subnet); - } - - [Fact] - public void Ctor_IPAddress_NullIPAddress_Throws_ArgumentNullException_Test() - { - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => new Subnet(null)); - } - - #endregion // end: Ctor(IPAddress) - - #region Ctor(IPAddress, int) - - [Fact] - public void Ctor_IPAddress_Int_NullIPAddress_Throws_ArgumentNullException_Test() - { - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => new Subnet(null, 42)); - } - - [Theory] - [InlineData("192.168.1.1", -1)] - [InlineData("192.168.1.1", 33)] - [InlineData("::", -1)] - [InlineData("::", 129)] - public void Ctor_IPAddress_Int_IntOutOfRange_Throws_ArgumentNullException_Test(string address, - int routingPrefix) - { - Assert.Throws(() => new Subnet(IPAddress.Parse(address), routingPrefix)); - } - - #endregion - - #region Ctor(IPAddress, IPAddress) - - public static IEnumerable Ctor_IPAddress_IPAddress_Test_Values() - { - yield return new object[] { new Subnet(IPAddress.Parse("0.0.0.0"), 0), IPAddress.Parse("0.0.0.0"), IPAddress.Parse("255.255.255.255") }; - yield return new object[] { new Subnet(IPAddress.Parse("::"), 0), IPAddress.Parse("::"), IPAddress.Parse("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF") }; - - foreach (var address in IPv4Addresses()) - { - for (var routePrefix = 0; routePrefix <= 32; routePrefix++) - { - var subnet = new Subnet(address, routePrefix); - yield return new object[] { subnet, subnet.Head, subnet.Tail }; - } - } - - foreach (var address in IPv6Addresses()) - { - for (var routePrefix = 0; routePrefix <= 128; routePrefix++) - { - var subnet = new Subnet(address, routePrefix); - yield return new object[] { subnet, subnet.Head, subnet.Tail }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(Ctor_IPAddress_IPAddress_Test_Values))] - public void Ctor_IPAddress_IPAddress_Test(Subnet expected, - IPAddress primary, - IPAddress secondary) - { - // Arrange - // Act - var result = new Subnet(primary, secondary); - - // Assert - Assert.Equal(expected, result); - } - - [Theory] - [InlineData("192.168.3.25", null)] - [InlineData(null, "192.168.3.25")] - [InlineData("::", null)] - [InlineData(null, "::")] - [InlineData(null, null)] - public void Ctor_IPAddress_IPAddress_Null_Input_Throws_ArgumentNullException_Test(string primary, - string secondary) - { - // Arrange - _ = IPAddress.TryParse(primary, out var primaryAddress); - _ = IPAddress.TryParse(secondary, out var secondaryAddress); - - // Act - // Assert - // ReSharper disable once UnusedVariable - Assert.Throws(() => - { - var subnet = new Subnet(primaryAddress, secondaryAddress); - }); - } - - [Theory] - [InlineData("192.168.3.25", "192.168.3.0")] - [InlineData("ff::dc", "::")] - public void Ctor_IPAddress_IPAddress_Input_Invalid_Ordering_Throws_InvalidOperationException_Test(string primary, - string secondary) - { - // Arrange - var primaryAddress = IPAddress.Parse(primary); - var secondaryAddress = IPAddress.Parse(secondary); - - // Act - // Assert - // ReSharper disable once UnusedVariable - Assert.Throws(() => - { - var subnet = new Subnet(primaryAddress, secondaryAddress); - }); - } - - [Theory] - [InlineData("192.168.3.25", "2001:0db8:85a3:0042:1000:8a2e:0370:7334")] - [InlineData("2001:0db8:85a3:0042:1000:8a2e:0370:7334", "192.168.3.25")] - public void Ctor_IPAddress_IPAddress_MismatchAddressFamily_Throws_ArgumentException_Test(string primary, - string secondary) - { - // Arrange - var primaryAddress = IPAddress.Parse(primary); - var secondaryAddress = IPAddress.Parse(secondary); - - // Act - // Assert - // ReSharper disable once UnusedVariable - Assert.Throws(() => - { - var subnet = new Subnet(primaryAddress, secondaryAddress); - }); - } - - #endregion // end: Ctor(IPAddress, IPAddress) - - #endregion // end: Ctor - - #region ISerializable - - public static IEnumerable CanSerializable_Test_Values() - { - yield return new object[] { new Subnet(IPAddress.Parse("192.168.1.0")) }; - yield return new object[] { new Subnet(IPAddress.Parse("192.168.1.0"), IPAddress.Parse("192.168.1.255")) }; - yield return new object[] { new Subnet(IPAddress.Parse("::"), IPAddress.Parse("::FFFF")) }; - } - - [Theory] - [MemberData(nameof(CanSerializable_Test_Values))] - public void CanSerializable_Test(Subnet subnet) - { - // Arrange - var formatter = new BinaryFormatter(); - - // Act - using var writeStream = new MemoryStream(); - formatter.Serialize(writeStream, subnet); - - var bytes = writeStream.ToArray(); - var readStream = new MemoryStream(bytes); - var result = formatter.Deserialize(readStream); - - // Assert - Assert.IsType(result); - Assert.Equal(subnet, result); - } - - #endregion end: ISerializable - - #region Static Factory Methods - - #region FromBytes - - public static IEnumerable FromBytes_Bytes_Bytes_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + #region FromBytes + + public static IEnumerable FromBytes_Bytes_Bytes_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(FromBytes_Bytes_Bytes_Test_Values))] - public void FromBytes_Bytes_Bytes_Test(Subnet expected, - byte[] lowAddressBytes, - byte[] highAddressBytes) - { - // Arrange - // Act - var subnet = Subnet.FromBytes(lowAddressBytes, highAddressBytes); - - // Assert - Assert.Equal(expected, subnet); - } - - [Theory] - [InlineData(null, new byte[] { 0x01, 0x01, 0xA8, 0xC0 })] - [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0 }, null)] - [InlineData(null, new byte[] { })] - public void FromBytes_Null_Input_Throws_ArgumentNullException_Test(byte[] lowAddressBytes, - byte[] highAddressBytes) - { - // Arrange - // Act - // Assert - Assert.Throws(() => Subnet.FromBytes(lowAddressBytes, highAddressBytes)); - } - - [Theory] - [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF }, new byte[] { 0x01, 0x01, 0xA8, 0xC0 })] - [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0 }, new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF })] - [InlineData(new byte[] { }, new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF })] - [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF }, new byte[] { })] - public void FromBytes_Invalid_Input_Throws_ArgumentException_Test(byte[] lowAddressBytes, - byte[] highAddressBytes) - { - // Arrange - // Act - // Assert - var exception = Assert.Throws(() => Subnet.FromBytes(lowAddressBytes, highAddressBytes)); - Assert.IsType(exception.InnerException); - } - - #endregion // end: FromBytes - - #region TryFromBytes - - public static IEnumerable TryFromBytes_Bytes_Bytes_Test_Values() - { - yield return new object[] { false, null, null, null }; - yield return new object[] { false, null, IPAddress.Any.GetAddressBytes(), IPAddress.IPv6Any.GetAddressBytes() }; - yield return new object[] { false, null, Array.Empty(), IPAddress.IPv6Any.GetAddressBytes() }; - yield return new object[] { false, null, Array.Empty(), IPAddress.Any.GetAddressBytes() }; - yield return new object[] { false, null, IPAddress.IPv6Any.GetAddressBytes(), Array.Empty() }; - yield return new object[] { false, null, IPAddress.Any.GetAddressBytes(), Array.Empty() }; - - yield return new object[] { false, null, new byte[] { 0x00 }, IPAddress.IPv6Any.GetAddressBytes() }; - yield return new object[] { false, null, new byte[] { 0x00 }, IPAddress.Any.GetAddressBytes() }; - yield return new object[] { false, null, IPAddress.IPv6Any.GetAddressBytes(), new byte[] { 0x00 } }; - yield return new object[] { false, null, IPAddress.Any.GetAddressBytes(), new byte[] { 0x00 } }; - - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; + } + } + + foreach (var ipAddress in IPv6Addresses()) { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { true, subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { true, subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(TryFromBytes_Bytes_Bytes_Test_Values))] - public void TryFromBytes_Bytes_Bytes_Test(bool expectedSuccess, - Subnet expectedSubnet, - byte[] lowAddressBytes, - byte[] highAddressBytes) - { - // Arrange - // Act - var success = Subnet.TryFromBytes(lowAddressBytes, highAddressBytes, out var subnet); - - // Assert - Assert.Equal(expectedSuccess, success); - Assert.Equal(expectedSubnet, subnet); - } - - #endregion // end: TryFromBytes - - #region Parse(string) - - public static IEnumerable Parse_String_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; + } + } + + IEnumerable IPv4Addresses() { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, $"{ipAddress}/{i}" }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, $"{subnet}" }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(Parse_String_Test_Values))] - public void Parse_String_Test(Subnet expected, - string input) - { - // Arrange - - // Act - var subnet = Subnet.Parse(input); - - // Assert - Assert.Equal(expected, subnet); - } - - [Fact] - public void Parse_Failure_Throws_FormatException_Test() - { - // Arrange - // Act - // Assert - Assert.Throws(() => Subnet.Parse("potato")); - } - - #endregion // end: Parse(string) - - #region TryParse(string) - - public static IEnumerable TryParse_String_Test_Values() - { - yield return new object[] { null, null }; - yield return new object[] { null, string.Empty }; - yield return new object[] { null, "potato" }; - yield return new object[] { null, "2001:0db8:85a3:0042:1000:8a2e:0370:7334/129" }; - yield return new object[] { null, "0.0.0.0/33" }; - yield return new object[] { null, "0.0.0.0/potato" }; - yield return new object[] { null, "potato/16" }; - yield return new object[] { null, "0.0.0.0/" }; - yield return new object[] { null, "/32" }; - - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } + + IEnumerable IPv6Addresses() { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, $"{ipAddress}/{i}" }; + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(FromBytes_Bytes_Bytes_Test_Values))] + public void FromBytes_Bytes_Bytes_Test(Subnet expected, byte[] lowAddressBytes, byte[] highAddressBytes) + { + // Arrange + // Act + var subnet = Subnet.FromBytes(lowAddressBytes, highAddressBytes); + + // Assert + Assert.Equal(expected, subnet); + } + + [Theory] + [InlineData(null, new byte[] { 0x01, 0x01, 0xA8, 0xC0 })] + [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0 }, null)] + [InlineData(null, new byte[] { })] + public void FromBytes_Null_Input_Throws_ArgumentNullException_Test(byte[] lowAddressBytes, byte[] highAddressBytes) + { + // Arrange + // Act + // Assert + Assert.Throws(() => Subnet.FromBytes(lowAddressBytes, highAddressBytes)); + } + + [Theory] + [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF }, new byte[] { 0x01, 0x01, 0xA8, 0xC0 })] + [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0 }, new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF })] + [InlineData(new byte[] { }, new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF })] + [InlineData(new byte[] { 0x01, 0x01, 0xA8, 0xC0, 0xFF }, new byte[] { })] + public void FromBytes_Invalid_Input_Throws_ArgumentException_Test(byte[] lowAddressBytes, byte[] highAddressBytes) + { + // Arrange + // Act + // Assert + var exception = Assert.Throws(() => Subnet.FromBytes(lowAddressBytes, highAddressBytes)); + Assert.IsType(exception.InnerException); + } + + #endregion // end: FromBytes + + #region TryFromBytes + + public static IEnumerable TryFromBytes_Bytes_Bytes_Test_Values() + { + yield return new object[] { false, null, null, null }; + yield return new object[] { false, null, IPAddress.Any.GetAddressBytes(), IPAddress.IPv6Any.GetAddressBytes() }; + yield return new object[] { false, null, Array.Empty(), IPAddress.IPv6Any.GetAddressBytes() }; + yield return new object[] { false, null, Array.Empty(), IPAddress.Any.GetAddressBytes() }; + yield return new object[] { false, null, IPAddress.IPv6Any.GetAddressBytes(), Array.Empty() }; + yield return new object[] { false, null, IPAddress.Any.GetAddressBytes(), Array.Empty() }; + + yield return new object[] { false, null, new byte[] { 0x00 }, IPAddress.IPv6Any.GetAddressBytes() }; + yield return new object[] { false, null, new byte[] { 0x00 }, IPAddress.Any.GetAddressBytes() }; + yield return new object[] { false, null, IPAddress.IPv6Any.GetAddressBytes(), new byte[] { 0x00 } }; + yield return new object[] { false, null, IPAddress.Any.GetAddressBytes(), new byte[] { 0x00 } }; + + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { true, subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; + } } - yield return new object[] { new Subnet(ipAddress, ipAddress), ipAddress.ToString() }; - } + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { true, subnet, subnet.Head.GetAddressBytes(), subnet.Tail.GetAddressBytes() }; + } + } - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) + IEnumerable IPv4Addresses() { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, $"{subnet}" }; + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); } - yield return new object[] { new Subnet(ipAddress, ipAddress), ipAddress.ToString() }; - } + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(TryFromBytes_Bytes_Bytes_Test_Values))] + public void TryFromBytes_Bytes_Bytes_Test( + bool expectedSuccess, + Subnet expectedSubnet, + byte[] lowAddressBytes, + byte[] highAddressBytes + ) + { + // Arrange + // Act + var success = Subnet.TryFromBytes(lowAddressBytes, highAddressBytes, out var subnet); + + // Assert + Assert.Equal(expectedSuccess, success); + Assert.Equal(expectedSubnet, subnet); + } + + #endregion // end: TryFromBytes + + #region Parse(string) + + public static IEnumerable Parse_String_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, $"{ipAddress}/{i}" }; + } + } - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, $"{subnet}" }; + } + } - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } + IEnumerable IPv4Addresses() + { + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } - [Theory] - [MemberData(nameof(TryParse_String_Test_Values))] - public void TryParse_String_Test(Subnet expected, - string input) - { - // Arrange + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(Parse_String_Test_Values))] + public void Parse_String_Test(Subnet expected, string input) + { + // Arrange + + // Act + var subnet = Subnet.Parse(input); + + // Assert + Assert.Equal(expected, subnet); + } + + [Fact] + public void Parse_Failure_Throws_FormatException_Test() + { + // Arrange + // Act + // Assert + Assert.Throws(() => Subnet.Parse("potato")); + } + + #endregion // end: Parse(string) + + #region TryParse(string) + + public static IEnumerable TryParse_String_Test_Values() + { + yield return new object[] { null, null }; + yield return new object[] { null, string.Empty }; + yield return new object[] { null, "potato" }; + yield return new object[] { null, "2001:0db8:85a3:0042:1000:8a2e:0370:7334/129" }; + yield return new object[] { null, "0.0.0.0/33" }; + yield return new object[] { null, "0.0.0.0/potato" }; + yield return new object[] { null, "potato/16" }; + yield return new object[] { null, "0.0.0.0/" }; + yield return new object[] { null, "/32" }; + + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, $"{ipAddress}/{i}" }; + } - // Act - var success = Subnet.TryParse(input, out var subnet); + yield return new object[] { new Subnet(ipAddress, ipAddress), ipAddress.ToString() }; + } - // Assert - Assert.Equal(expected != null, success); - Assert.Equal(expected, subnet); - } + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, $"{subnet}" }; + } - #endregion // end: TryParse(string) + yield return new object[] { new Subnet(ipAddress, ipAddress), ipAddress.ToString() }; + } - #region Parse(string, int) + IEnumerable IPv4Addresses() + { + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } - public static IEnumerable Parse_String_Int_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + IEnumerable IPv6Addresses() { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.ToString(), i }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.ToString(), i }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(Parse_String_Int_Test_Values))] - public void Parse_String_Int_Test(Subnet expected, - string addressString, - int routePrefix) - { - // Arrange - // Act - var subnet = Subnet.Parse(addressString, routePrefix); - - // Assert - Assert.Equal(expected, subnet); - } - - #endregion // end: Parse(string, int) - - #region TryParse(string, int) - - public static IEnumerable TryParse_String_Int_Test_Values() - { - yield return new object[] { false, null, null, 0 }; - yield return new object[] { false, null, "potato", 0 }; - yield return new object[] { false, null, IPAddress.Any.ToString(), -5 }; - yield return new object[] { false, null, IPAddress.IPv6Any.ToString(), -5 }; - yield return new object[] { false, null, IPAddress.Any.ToString(), 33 }; - yield return new object[] { false, null, IPAddress.IPv6Any.ToString(), 129 }; - yield return new object[] { false, null, null, 0 }; - - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(TryParse_String_Test_Values))] + public void TryParse_String_Test(Subnet expected, string input) + { + // Arrange + + // Act + var success = Subnet.TryParse(input, out var subnet); + + // Assert + Assert.Equal(expected != null, success); + Assert.Equal(expected, subnet); + } + + #endregion // end: TryParse(string) + + #region Parse(string, int) + + public static IEnumerable Parse_String_Int_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { true, subnet, subnet.Head.ToString(), i }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { true, subnet, subnet.Head.ToString(), i }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(TryParse_String_Int_Test_Values))] - public void TryParse_String_Int_Test(bool expectedSuccess, - Subnet expectedSubnet, - string addressString, - int routePrefix) - { - // Arrange - // Act - var success = Subnet.TryParse(addressString, routePrefix, out var subnet); - - // Assert - Assert.Equal(expectedSuccess, success); - Assert.Equal(expectedSubnet, subnet); - } - - #endregion // end: TryParse(string, int) - - #region Parse(string, string) - - public static IEnumerable Parse_String_String_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.ToString(), i }; + } + } + + foreach (var ipAddress in IPv6Addresses()) { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(Parse_String_String_Test_Values))] - public void Parse_String_String_Test(Subnet expected, - string low, - string high) - { - // Arrange - // Act - var subnet = Subnet.Parse(low, high); - - // Assert - Assert.Equal(expected, subnet); - } - - [Theory] - [InlineData("::", null)] - [InlineData(null, "::")] - [InlineData("192.168.1.1", null)] - [InlineData(null, "192.168.1.1")] - [InlineData(null, null)] - public void Parse_String_String_NullAddressString_Throws_ArgumentNullException_Test(string low, - string high) - { - // Arrange - // Act - // Assert - Assert.Throws(() => Subnet.Parse(low, high)); - } - - [Theory] - [InlineData("::", "potato")] - [InlineData("potato", "::")] - [InlineData("192.168.1.1", "potato")] - [InlineData("potato", "192.168.1.1")] - public void Parse_String_String_BadAddressFormat_Throws_ArgumentException_Test(string low, - string high) - { - // Arrange - // Act - // Assert - Assert.Throws(() => Subnet.Parse(low, high)); - } - - [Theory] - [InlineData("192.168.1.1", "::")] - [InlineData("::", "192.168.1.1")] - public void Parse_String_String_MisMatchAddressFamily_Throws_ArgumentException_Test(string low, - string high) - { - // Arrange - // Act - // Assert - Assert.Throws(() => Subnet.Parse(low, high)); - } - - [Theory] - [InlineData("192.168.1.32", "192.168.1.0")] - [InlineData("::20", "::")] - public void Parse_String_String_InvalidRange_Throws_InvalidOperationException_Test(string low, - string high) - { - // Arrange - // Act - // Assert - Assert.Throws(() => Subnet.Parse(low, high)); - } - - #endregion Parse(string, string) - - #region TryParse(string, string) - - public static IEnumerable TryParse_String_String_Test_Values() - { - foreach (var s in new[] { null, string.Empty, "\t", "potato" }) - { - yield return new object[] { null, "192.168.1.1", s }; - yield return new object[] { null, s, "192.168.1.1" }; - yield return new object[] { null, "2001:0db8:85a3:0042:1000:8a2e:0370:7334", s }; - yield return new object[] { null, s, "2001:0db8:85a3:0042:1000:8a2e:0370:7334" }; - } - - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.ToString(), i }; + } + } + + IEnumerable IPv4Addresses() + { + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } + + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(Parse_String_Int_Test_Values))] + public void Parse_String_Int_Test(Subnet expected, string addressString, int routePrefix) + { + // Arrange + // Act + var subnet = Subnet.Parse(addressString, routePrefix); + + // Assert + Assert.Equal(expected, subnet); + } + + #endregion // end: Parse(string, int) + + #region TryParse(string, int) + + public static IEnumerable TryParse_String_Int_Test_Values() + { + yield return new object[] { false, null, null, 0 }; + yield return new object[] { false, null, "potato", 0 }; + yield return new object[] { false, null, IPAddress.Any.ToString(), -5 }; + yield return new object[] { false, null, IPAddress.IPv6Any.ToString(), -5 }; + yield return new object[] { false, null, IPAddress.Any.ToString(), 33 }; + yield return new object[] { false, null, IPAddress.IPv6Any.ToString(), 129 }; + yield return new object[] { false, null, null, 0 }; + + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { true, subnet, subnet.Head.ToString(), i }; + } + } + + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { true, subnet, subnet.Head.ToString(), i }; + } + } + + IEnumerable IPv4Addresses() + { + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } + + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(TryParse_String_Int_Test_Values))] + public void TryParse_String_Int_Test(bool expectedSuccess, Subnet expectedSubnet, string addressString, int routePrefix) + { + // Arrange + // Act + var success = Subnet.TryParse(addressString, routePrefix, out var subnet); + + // Assert + Assert.Equal(expectedSuccess, success); + Assert.Equal(expectedSubnet, subnet); + } + + #endregion // end: TryParse(string, int) + + #region Parse(string, string) + + public static IEnumerable Parse_String_String_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; + } + } + + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; + } + } + + IEnumerable IPv4Addresses() + { + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } + + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(Parse_String_String_Test_Values))] + public void Parse_String_String_Test(Subnet expected, string low, string high) + { + // Arrange + // Act + var subnet = Subnet.Parse(low, high); + + // Assert + Assert.Equal(expected, subnet); + } + + [Theory] + [InlineData("::", null)] + [InlineData(null, "::")] + [InlineData("192.168.1.1", null)] + [InlineData(null, "192.168.1.1")] + [InlineData(null, null)] + public void Parse_String_String_NullAddressString_Throws_ArgumentNullException_Test(string low, string high) + { + // Arrange + // Act + // Assert + Assert.Throws(() => Subnet.Parse(low, high)); + } + + [Theory] + [InlineData("::", "potato")] + [InlineData("potato", "::")] + [InlineData("192.168.1.1", "potato")] + [InlineData("potato", "192.168.1.1")] + public void Parse_String_String_BadAddressFormat_Throws_ArgumentException_Test(string low, string high) + { + // Arrange + // Act + // Assert + Assert.Throws(() => Subnet.Parse(low, high)); + } + + [Theory] + [InlineData("192.168.1.1", "::")] + [InlineData("::", "192.168.1.1")] + public void Parse_String_String_MisMatchAddressFamily_Throws_ArgumentException_Test(string low, string high) + { + // Arrange + // Act + // Assert + Assert.Throws(() => Subnet.Parse(low, high)); + } + + [Theory] + [InlineData("192.168.1.32", "192.168.1.0")] + [InlineData("::20", "::")] + public void Parse_String_String_InvalidRange_Throws_InvalidOperationException_Test(string low, string high) + { + // Arrange + // Act + // Assert + Assert.Throws(() => Subnet.Parse(low, high)); + } + + #endregion Parse(string, string) + + #region TryParse(string, string) + + public static IEnumerable TryParse_String_String_Test_Values() + { + foreach (var s in new[] { null, string.Empty, "\t", "potato" }) + { + yield return new object[] { null, "192.168.1.1", s }; + yield return new object[] { null, s, "192.168.1.1" }; + yield return new object[] { null, "2001:0db8:85a3:0042:1000:8a2e:0370:7334", s }; + yield return new object[] { null, s, "2001:0db8:85a3:0042:1000:8a2e:0370:7334" }; + } + + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; + } + } + + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var subnet = new Subnet(ipAddress, i); + yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; + } + } + + IEnumerable IPv4Addresses() + { + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); + } + + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(TryParse_String_String_Test_Values))] + public void TryParse_String_String_Test(Subnet expected, string low, string high) + { + // Arrange + + // Act + var success = Subnet.TryParse(low, high, out var subnet); + + // Assert + Assert.Equal(expected != null, success); + Assert.Equal(expected, subnet); + } + + #endregion // end: TryParse(string, string) + + #endregion // end: Static Factory Methods + + #region Length / TryGetLength + + public static IEnumerable Length_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + var routePrefix = 32 - i; + var length = BigInteger.Pow(2, routePrefix); + + var subnet = new Subnet(ipAddress, i); + yield return new object[] { length, subnet }; + } + } + + foreach (var ipAddress in IPv6Addresses()) + { + for (var i = 0; i <= 128; i++) + { + var routePrefix = 128 - i; + var length = BigInteger.Pow(2, routePrefix); + + var subnet = new Subnet(ipAddress, i); + yield return new object[] { length, subnet }; + } + } + + IEnumerable IPv4Addresses() + { + yield return IPAddress.Parse("192.168.1.1"); + } + + IEnumerable IPv6Addresses() + { + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(Length_Test_Values))] + public static void Length_Test(BigInteger expected, Subnet subnet) + { + // Arrange + // Act + + var result = subnet.Length; + + // Assert + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Length_Test_Values))] + public static void TryGetLength_Integer_Test(BigInteger expected, Subnet subnet) + { + // Arrange + // Act + var success = subnet.TryGetLength(out int length); + + // Assert + Assert.Equal(expected <= int.MaxValue, success); + Assert.Equal(expected <= int.MaxValue ? (int)expected : -1, length); + } + + [Theory] + [MemberData(nameof(Length_Test_Values))] + public static void TryGetLength_Long_Test(BigInteger expected, Subnet subnet) + { + // Arrange + // Act + var success = subnet.TryGetLength(out long length); + + // Assert + Assert.Equal(expected <= long.MaxValue, success); + Assert.Equal(expected <= long.MaxValue ? (long)expected : -1, length); + } + + #endregion // end: Length + + #region Equals + + #region Equals(Subnet) + + public static IEnumerable Equals_Subnet_Test_Values() + { + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + yield return new object[] { true, new Subnet(ipAddress, i), new Subnet(ipAddress, i) }; // equivalent + + yield return new object[] { false, new Subnet(ipAddress, i), new Subnet(ipAddress, (i + 2) % 32) }; // differing routes equivalent + + var subnet = new Subnet(ipAddress, i); + yield return new object[] { true, subnet, subnet }; // same + + foreach (var ipv6Address in IPv6Addresses().Take(2)) + { + yield return new object[] { false, new Subnet(ipAddress, i), new Subnet(ipv6Address, i) }; // different families + } + } + } + + foreach (var ipAddress in IPv6Addresses()) { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; + for (var i = 0; i <= 128; i++) + { + yield return new object[] { true, new Subnet(ipAddress, i), new Subnet(ipAddress, i) }; // equivalent + + yield return new object[] { false, new Subnet(ipAddress, i), new Subnet(ipAddress, (i + 2) % 128) }; // differing routes equivalent + + var subnet = new Subnet(ipAddress, i); + yield return new object[] { true, subnet, subnet }; // same + } } - } - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) + IEnumerable IPv4Addresses() { - var subnet = new Subnet(ipAddress, i); - yield return new object[] { subnet, subnet.Head.ToString(), subnet.Tail.ToString() }; + yield return IPAddress.Any; + yield return IPAddress.Loopback; + yield return IPAddress.None; + yield return IPAddress.Parse("192.168.1.1"); } - } - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } + IEnumerable IPv6Addresses() + { + yield return IPAddress.IPv6Any; + yield return IPAddress.IPv6Loopback; + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + } + } + + [Theory] + [MemberData(nameof(Equals_Subnet_Test_Values))] + public void Equals_Subnet_Test(bool expected, Subnet subnetA, Subnet subnetB) + { + // Arrange + + // Act + var result = subnetA.Equals(subnetB); + + // Assert + Assert.Equal(expected, result); + Assert.Equal(result, expected); + } + + #endregion // end: Equals(Subnet) - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } + #region Equals(object) - [Theory] - [MemberData(nameof(TryParse_String_String_Test_Values))] - public void TryParse_String_String_Test(Subnet expected, - string low, - string high) - { - // Arrange + [Theory] + [MemberData(nameof(Equals_Subnet_Test_Values))] + public void Equals_Object_Test(bool expected, Subnet subnetA, object subnetB) + { + // Arrange - // Act - var success = Subnet.TryParse(low, high, out var subnet); + // Act + var result = subnetA.Equals(subnetB); - // Assert - Assert.Equal(expected != null, success); - Assert.Equal(expected, subnet); - } + // Assert + Assert.Equal(expected, result); + } - #endregion // end: TryParse(string, string) + #endregion // end: Equals(object) - #endregion // end: Static Factory Methods + #endregion // end: Equals - #region Length / TryGetLength + #region FromNetMask + + public static IEnumerable FromNetMask_Test_Values() + { + var networkPrefix = IPAddress.Parse("192.168.1.1"); - public static IEnumerable Length_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { for (var i = 0; i <= 32; i++) { - var routePrefix = 32 - i; - var length = BigInteger.Pow(2, routePrefix); - - var subnet = new Subnet(ipAddress, i); - yield return new object[] { length, subnet }; - } - } - - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) - { - var routePrefix = 128 - i; - var length = BigInteger.Pow(2, routePrefix); - - var subnet = new Subnet(ipAddress, i); - yield return new object[] { length, subnet }; - } - } - - IEnumerable IPv4Addresses() - { - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(Length_Test_Values))] - public static void Length_Test(BigInteger expected, - Subnet subnet) - { - // Arrange - // Act - - var result = subnet.Length; - - // Assert - Assert.Equal(expected, result); - } - - [Theory] - [MemberData(nameof(Length_Test_Values))] - public static void TryGetLength_Integer_Test(BigInteger expected, - Subnet subnet) - { - // Arrange - // Act - var success = subnet.TryGetLength(out int length); - - // Assert - Assert.Equal(expected <= int.MaxValue, success); - Assert.Equal(expected <= int.MaxValue - ? (int)expected - : -1, - length); - } - - [Theory] - [MemberData(nameof(Length_Test_Values))] - public static void TryGetLength_Long_Test(BigInteger expected, - Subnet subnet) - { - // Arrange - // Act - var success = subnet.TryGetLength(out long length); - - // Assert - Assert.Equal(expected <= long.MaxValue, success); - Assert.Equal(expected <= long.MaxValue - ? (long)expected - : -1, - length); - } - - #endregion // end: Length - - #region Equals - - #region Equals(Subnet) - - public static IEnumerable Equals_Subnet_Test_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { + var netmaskBytes = Enumerable.Repeat((byte)0xFF, 4).ToArray().ShiftBitsLeft(32 - i); + + var netmask = new IPAddress(netmaskBytes); + + var expected = new Subnet(networkPrefix, i); + + yield return new object[] { expected, networkPrefix, netmask }; + } + } + + [Theory] + [MemberData(nameof(FromNetMask_Test_Values))] + public void FromNetMask_Test(Subnet expected, IPAddress networkPrefix, IPAddress netmask) + { + // Arrange + // Act + var result = Subnet.FromNetMask(networkPrefix, netmask); + + // Assert + Assert.Equal(expected, result); + Assert.Equal(netmask, result.Netmask); + } + + [Fact] + public void FromNetMask_AddressNull_Throws_ArgumentNullException_Test() + { + // Act + // Arrange + // Assert + + Assert.Throws(() => Subnet.FromNetMask(null, IPAddress.Any)); + } + + [Fact] + public void FromNetMask_InvalidNetMask_Throws_ArgumentNullException_Test() + { + // Act + // Arrange + // Assert + Assert.Throws(() => Subnet.FromNetMask(IPAddress.Any, IPAddress.IPv6Any)); + } + + [Fact] + public void FromNetMask_IPv6Address_Throws_ArgumentNullException_Test() + { + // Act + // Arrange + // Assert + Assert.Throws(() => Subnet.FromNetMask(IPAddress.IPv6Any, IPAddress.Any)); + } + + [Fact] + public void FromNetMaskNetMask_NullNetMask_Throws_ArgumentNullException_Test() + { + // Act + // Arrange + // Assert + + Assert.Throws(() => Subnet.FromNetMask(IPAddress.Any, null)); + } + + public static IEnumerable TryFromNetMask_Test_Values() + { + var networkPrefix = IPAddress.Parse("192.168.1.1"); + for (var i = 0; i <= 32; i++) { - yield return new object[] { true, new Subnet(ipAddress, i), new Subnet(ipAddress, i) }; // equivalent + var netmaskBytes = Enumerable.Repeat((byte)0xFF, 4).ToArray().ShiftBitsLeft(32 - i); + + var netmask = new IPAddress(netmaskBytes); + + var expected = new Subnet(networkPrefix, i); + + yield return new object[] { true, expected, networkPrefix, netmask }; + } + + yield return new object[] { false, null, null, IPAddress.Parse("192.168.1.1") }; + yield return new object[] { false, null, IPAddress.Parse("192.168.1.1"), null }; + yield return new object[] { false, null, null, IPAddress.Parse("::") }; + yield return new object[] { false, null, IPAddress.Parse("::"), null }; + yield return new object[] { false, null, null, null }; + } + + [Theory] + [MemberData(nameof(TryFromNetMask_Test_Values))] + public void TryFromNetMask_Test(bool expectedSuccess, Subnet expectedSubnet, IPAddress networkPrefix, IPAddress netmask) + { + // Arrange + // Act + var success = Subnet.TryFromNetMask(networkPrefix, netmask, out var subnet); + + // Assert + Assert.Equal(expectedSuccess, success); + Assert.Equal(expectedSubnet, subnet); + } - yield return new object[] { false, new Subnet(ipAddress, i), new Subnet(ipAddress, (i + 2) % 32) }; // differing routes equivalent + #endregion - var subnet = new Subnet(ipAddress, i); - yield return new object[] { true, subnet, subnet }; // same + #region Formatting - foreach (var ipv6Address in IPv6Addresses() - .Take(2)) - { - yield return new object[] { false, new Subnet(ipAddress, i), new Subnet(ipv6Address, i) }; // different families - } + #region ToString + + [Theory] + [InlineData("192.168.1.1", "192.168.1.42")] + [InlineData("::beef", "0123::dead")] + public void ToString_MatchesGeneralFormat_Test(string headString, string tailString) + { + // Arrange + var head = IPAddress.Parse(headString); + var tail = IPAddress.Parse(tailString); + + var iPAddressRange = new IPAddressRange(head, tail); + + // Act + var result = iPAddressRange.ToString(); + + // Assert + Assert.Equal($"{iPAddressRange:G}", result); + } + + #endregion // end: ToString + + #region ToString(string, IFormatProvider) + + public static IEnumerable ToString_Format_Test_Values() + { + // general formats + foreach (var format in new[] { null, string.Empty, "g", "G" }) + { + foreach (var subnet in Ipv4AddressSubnets().Concat(Ipv6AddressSubnets())) + { + yield return new object[] + { + $"{subnet.NetworkPrefixAddress}/{subnet.RoutingPrefix}", + format, + CultureInfo.CurrentCulture, + subnet, + }; + } } - } - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) + // "friendly" formats + foreach (var format in new[] { "f", "F" }) { - yield return new object[] { true, new Subnet(ipAddress, i), new Subnet(ipAddress, i) }; // equivalent + foreach (var subnet in Ipv4AddressSubnets().Concat(Ipv6AddressSubnets())) + { + yield return new object[] + { + subnet.IsSingleIP + ? $"{subnet.NetworkPrefixAddress}" + : $"{subnet.NetworkPrefixAddress}/{subnet.RoutingPrefix}", + format, + CultureInfo.CurrentCulture, + subnet, + }; + } + } - yield return new object[] { false, new Subnet(ipAddress, i), new Subnet(ipAddress, (i + 2) % 128) }; // differing routes equivalent + // range formats + foreach (var format in new[] { "r", "R" }) + { + foreach (var subnet in Ipv4AddressSubnets().Concat(Ipv6AddressSubnets())) + { + yield return new object[] { $"{subnet.Head} - {subnet.Tail}", format, CultureInfo.CurrentCulture, subnet }; + } + } - var subnet = new Subnet(ipAddress, i); - yield return new object[] { true, subnet, subnet }; // same + IEnumerable Ipv4AddressSubnets() + { + yield return new Subnet(IPAddress.Parse("192.168.1.1"), 16); + yield return new Subnet(IPAddress.Parse("192.168.1.1"), 32); } - } - IEnumerable IPv4Addresses() - { - yield return IPAddress.Any; - yield return IPAddress.Loopback; - yield return IPAddress.None; - yield return IPAddress.Parse("192.168.1.1"); - } - - IEnumerable IPv6Addresses() - { - yield return IPAddress.IPv6Any; - yield return IPAddress.IPv6Loopback; - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - } - } - - [Theory] - [MemberData(nameof(Equals_Subnet_Test_Values))] - public void Equals_Subnet_Test(bool expected, - Subnet subnetA, - Subnet subnetB) - { - // Arrange - - // Act - var result = subnetA.Equals(subnetB); - - // Assert - Assert.Equal(expected, result); - Assert.Equal(result, expected); - } - - #endregion // end: Equals(Subnet) - - #region Equals(object) - - [Theory] - [MemberData(nameof(Equals_Subnet_Test_Values))] - public void Equals_Object_Test(bool expected, - Subnet subnetA, - object subnetB) - { - // Arrange - - // Act - var result = subnetA.Equals(subnetB); - - // Assert - Assert.Equal(expected, result); - } - - #endregion // end: Equals(object) - - #endregion // end: Equals - - #region FromNetMask - - public static IEnumerable FromNetMask_Test_Values() - { - var networkPrefix = IPAddress.Parse("192.168.1.1"); - - for (var i = 0; i <= 32; i++) - { - var netmaskBytes = Enumerable.Repeat((byte)0xFF, 4) - .ToArray() - .ShiftBitsLeft(32 - i); - - var netmask = new IPAddress(netmaskBytes); - - var expected = new Subnet(networkPrefix, i); - - yield return new object[] { expected, networkPrefix, netmask }; - } - } - - [Theory] - [MemberData(nameof(FromNetMask_Test_Values))] - public void FromNetMask_Test(Subnet expected, - IPAddress networkPrefix, - IPAddress netmask) - { - // Arrange - // Act - var result = Subnet.FromNetMask(networkPrefix, netmask); - - // Assert - Assert.Equal(expected, result); - Assert.Equal(netmask, result.Netmask); - } - - [Fact] - public void FromNetMask_AddressNull_Throws_ArgumentNullException_Test() - { - // Act - // Arrange - // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => Subnet.FromNetMask(null, IPAddress.Any)); - } - - [Fact] - public void FromNetMask_InvalidNetMask_Throws_ArgumentNullException_Test() - { - // Act - // Arrange - // Assert - Assert.Throws(() => Subnet.FromNetMask(IPAddress.Any, IPAddress.IPv6Any)); - } - - [Fact] - public void FromNetMask_IPv6Address_Throws_ArgumentNullException_Test() - { - // Act - // Arrange - // Assert - Assert.Throws(() => Subnet.FromNetMask(IPAddress.IPv6Any, IPAddress.Any)); - } - - [Fact] - public void FromNetMaskNetMask_NullNetMask_Throws_ArgumentNullException_Test() - { - // Act - // Arrange - // Assert - // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => Subnet.FromNetMask(IPAddress.Any, null)); - } - - public static IEnumerable TryFromNetMask_Test_Values() - { - var networkPrefix = IPAddress.Parse("192.168.1.1"); - - for (var i = 0; i <= 32; i++) - { - var netmaskBytes = Enumerable.Repeat((byte)0xFF, 4) - .ToArray() - .ShiftBitsLeft(32 - i); - - var netmask = new IPAddress(netmaskBytes); - - var expected = new Subnet(networkPrefix, i); - - yield return new object[] { true, expected, networkPrefix, netmask }; - } - - yield return new object[] { false, null, null, IPAddress.Parse("192.168.1.1") }; - yield return new object[] { false, null, IPAddress.Parse("192.168.1.1"), null }; - yield return new object[] { false, null, null, IPAddress.Parse("::") }; - yield return new object[] { false, null, IPAddress.Parse("::"), null }; - yield return new object[] { false, null, null, null }; - } - - [Theory] - [MemberData(nameof(TryFromNetMask_Test_Values))] - public void TryFromNetMask_Test(bool expectedSuccess, - Subnet expectedSubnet, - IPAddress networkPrefix, - IPAddress netmask) - { - // Arrange - // Act - var success = Subnet.TryFromNetMask(networkPrefix, netmask, out var subnet); - - // Assert - Assert.Equal(expectedSuccess, success); - Assert.Equal(expectedSubnet, subnet); - } - - #endregion - - #region Formatting - - #region ToString - - [Theory] - [InlineData("192.168.1.1", "192.168.1.42")] - [InlineData("::beef", "0123::dead")] - public void ToString_MatchesGeneralFormat_Test(string headString, - string tailString) - { - // Arrange - var head = IPAddress.Parse(headString); - var tail = IPAddress.Parse(tailString); - - var iPAddressRange = new IPAddressRange(head, tail); - - // Act - var result = iPAddressRange.ToString(); - - // Assert - Assert.Equal($"{iPAddressRange:G}", result); - } - - #endregion // end: ToString - - #region ToString(string, IFormatProvider) - - public static IEnumerable ToString_Format_Test_Values() - { - // general formats - foreach (var format in new[] { null, string.Empty, "g", "G" }) - { - foreach (var subnet in Ipv4AddressSubnets() - .Concat(Ipv6AddressSubnets())) - { - yield return new object[] { $"{subnet.NetworkPrefixAddress}/{subnet.RoutingPrefix}", format, CultureInfo.CurrentCulture, subnet }; - } - } - - // "friendly" formats - foreach (var format in new[] { "f", "F" }) - { - foreach (var subnet in Ipv4AddressSubnets() - .Concat(Ipv6AddressSubnets())) - { - yield return new object[] - { - subnet.IsSingleIP - ? $"{subnet.NetworkPrefixAddress}" - : $"{subnet.NetworkPrefixAddress}/{subnet.RoutingPrefix}", - format, CultureInfo.CurrentCulture, subnet - }; - } - } - - // range formats - foreach (var format in new[] { "r", "R" }) - { - foreach (var subnet in Ipv4AddressSubnets() - .Concat(Ipv6AddressSubnets())) - { - yield return new object[] { $"{subnet.Head} - {subnet.Tail}", format, CultureInfo.CurrentCulture, subnet }; - } - } - - IEnumerable Ipv4AddressSubnets() - { - yield return new Subnet(IPAddress.Parse("192.168.1.1"), 16); - yield return new Subnet(IPAddress.Parse("192.168.1.1"), 32); - } - - IEnumerable Ipv6AddressSubnets() - { - yield return new Subnet(IPAddress.Parse("abc:123::beef"), 16); - yield return new Subnet(IPAddress.Parse("abc:123::beef"), 128); - } - } - - [Theory] - [MemberData(nameof(ToString_Format_Test_Values))] - public void ToString_Format_Test(string expected, - string format, - IFormatProvider formatProvider, - Subnet subnet) - { - // Arrange - // Act - var result = subnet.ToString(format, formatProvider); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void ToString_UnknownFormat_Throws_FormatException_Test() - { - // Arrange - var range = new Subnet(IPAddress.Parse("192.168.1.1"), 16); - - // Act - // Assert - Assert.Throws(() => range.ToString("potato", CultureInfo.CurrentCulture)); - } - - #endregion // end: ToString(string, IFormatProvider) - - #endregion // end: Formatting - - #region GetHashCode - - public static IEnumerable GetHashCode_Test_Values() - { - // equal - yield return new object[] { true, new Subnet(IPAddress.Any, 16), new Subnet(IPAddress.Any, 16) }; - yield return new object[] { true, new Subnet(IPAddress.IPv6Any, 16), new Subnet(IPAddress.IPv6Any, 16) }; - - // reference equal - var ipv4Subnet = new Subnet(IPAddress.Any, 16); - yield return new object[] { true, ipv4Subnet, ipv4Subnet }; - - var ipv6Subnet = new Subnet(IPAddress.IPv6Any, 16); - yield return new object[] { true, ipv6Subnet, ipv6Subnet }; - - // expected different - yield return new object[] { false, ipv4Subnet, ipv6Subnet }; - yield return new object[] { false, new Subnet(IPAddress.Any, 8), new Subnet(IPAddress.Any, 16) }; - yield return new object[] { false, new Subnet(IPAddress.IPv6Any, 8), new Subnet(IPAddress.IPv6Any, 16) }; - yield return new object[] { false, new Subnet(IPAddress.Any, 16), new Subnet(IPAddress.Broadcast, 16) }; - yield return new object[] { false, new Subnet(IPAddress.Parse("::")), new Subnet(IPAddress.Parse("ab::"), 16) }; - } - - [Theory] - [MemberData(nameof(GetHashCode_Test_Values))] - public void GetHashCode_Test(bool expectedEqual, - Subnet left, - Subnet right) - { - // Arrange - // Act - var leftHash = left.GetHashCode(); - var rightHash = right.GetHashCode(); - - // Assert - Assert.Equal(expectedEqual, leftHash == rightHash); - } - - #endregion // end: GetHashCode - - #region Deconstruct - - public static IEnumerable Deconstruct_Values() - { - foreach (var ipAddress in IPv4Addresses()) - { - for (var i = 0; i <= 32; i++) + IEnumerable Ipv6AddressSubnets() { - yield return new object[] { new Subnet(ipAddress, i) }; + yield return new Subnet(IPAddress.Parse("abc:123::beef"), 16); + yield return new Subnet(IPAddress.Parse("abc:123::beef"), 128); + } + } + + [Theory] + [MemberData(nameof(ToString_Format_Test_Values))] + public void ToString_Format_Test(string expected, string format, IFormatProvider formatProvider, Subnet subnet) + { + // Arrange + // Act + var result = subnet.ToString(format, formatProvider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void ToString_UnknownFormat_Throws_FormatException_Test() + { + // Arrange + var range = new Subnet(IPAddress.Parse("192.168.1.1"), 16); + + // Act + // Assert + Assert.Throws(() => range.ToString("potato", CultureInfo.CurrentCulture)); + } + + #endregion // end: ToString(string, IFormatProvider) + + #endregion // end: Formatting + + #region GetHashCode + + public static IEnumerable GetHashCode_Test_Values() + { + // equal + yield return new object[] { true, new Subnet(IPAddress.Any, 16), new Subnet(IPAddress.Any, 16) }; + yield return new object[] { true, new Subnet(IPAddress.IPv6Any, 16), new Subnet(IPAddress.IPv6Any, 16) }; + + // reference equal + var ipv4Subnet = new Subnet(IPAddress.Any, 16); + yield return new object[] { true, ipv4Subnet, ipv4Subnet }; + + var ipv6Subnet = new Subnet(IPAddress.IPv6Any, 16); + yield return new object[] { true, ipv6Subnet, ipv6Subnet }; + + // expected different + yield return new object[] { false, ipv4Subnet, ipv6Subnet }; + yield return new object[] { false, new Subnet(IPAddress.Any, 8), new Subnet(IPAddress.Any, 16) }; + yield return new object[] { false, new Subnet(IPAddress.IPv6Any, 8), new Subnet(IPAddress.IPv6Any, 16) }; + yield return new object[] { false, new Subnet(IPAddress.Any, 16), new Subnet(IPAddress.Broadcast, 16) }; + yield return new object[] { false, new Subnet(IPAddress.Parse("::")), new Subnet(IPAddress.Parse("ab::"), 16) }; + } + + [Theory] + [MemberData(nameof(GetHashCode_Test_Values))] + public void GetHashCode_Test(bool expectedEqual, Subnet left, Subnet right) + { + // Arrange + // Act + var leftHash = left.GetHashCode(); + var rightHash = right.GetHashCode(); + + // Assert + Assert.Equal(expectedEqual, leftHash == rightHash); + } + + #endregion // end: GetHashCode + + #region Deconstruct + + public static IEnumerable Deconstruct_Values() + { + foreach (var ipAddress in IPv4Addresses()) + { + for (var i = 0; i <= 32; i++) + { + yield return new object[] { new Subnet(ipAddress, i) }; + } } - } - foreach (var ipAddress in IPv6Addresses()) - { - for (var i = 0; i <= 128; i++) + foreach (var ipAddress in IPv6Addresses()) { - yield return new object[] { new Subnet(ipAddress, i) }; + for (var i = 0; i <= 128; i++) + { + yield return new object[] { new Subnet(ipAddress, i) }; + } } - } - IEnumerable IPv4Addresses() - { - yield return IPAddress.Parse("192.168.1.1"); - yield return IPAddress.Parse("255.255.0.0"); - } + IEnumerable IPv4Addresses() + { + yield return IPAddress.Parse("192.168.1.1"); + yield return IPAddress.Parse("255.255.0.0"); + } + + IEnumerable IPv6Addresses() + { + yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); + yield return IPAddress.Parse("dead:beef::"); + } + } - IEnumerable IPv6Addresses() - { - yield return IPAddress.Parse("2001:0db8:85a3:0042:1000:8a2e:0370:7334"); - yield return IPAddress.Parse("dead:beef::"); - } - } + #region Deconstruct(IPAddress, IPAddress, IPAddress, int) - #region Deconstruct(IPAddress, IPAddress, IPAddress, int) + [Theory] + [MemberData(nameof(Deconstruct_Values))] + public void Deconstruct_IPAddress_IPAddress_IPAddress_Int_Test(Subnet subnet) + { + // Arrange + // Act + var (networkPrefixAddress, broadcastAddress, netmask, routingPrefix) = subnet; - [Theory] - [MemberData(nameof(Deconstruct_Values))] - public void Deconstruct_IPAddress_IPAddress_IPAddress_Int_Test(Subnet subnet) - { - // Arrange - // Act - var (networkPrefixAddress, broadcastAddress, netmask, routingPrefix) = subnet; + // Assert + Assert.Equal(subnet.NetworkPrefixAddress, networkPrefixAddress); + Assert.Equal(subnet.BroadcastAddress, broadcastAddress); + Assert.Equal(subnet.Netmask, netmask); + Assert.Equal(subnet.RoutingPrefix, routingPrefix); + } - // Assert - Assert.Equal(subnet.NetworkPrefixAddress, networkPrefixAddress); - Assert.Equal(subnet.BroadcastAddress, broadcastAddress); - Assert.Equal(subnet.Netmask, netmask); - Assert.Equal(subnet.RoutingPrefix, routingPrefix); - } + #endregion // end: Deconstruct(IPAddress, IPAddress, IPAddress, int) - #endregion // end: Deconstruct(IPAddress, IPAddress, IPAddress, int) + #region Deconstruct(IPAddress, int) - #region Deconstruct(IPAddress, int) + [Theory] + [MemberData(nameof(Deconstruct_Values))] + public void Deconstruct_IPAddress_Int_Test(Subnet subnet) + { + // Arrange + // Act + var (networkPrefixAddress, routingPrefix) = subnet; - [Theory] - [MemberData(nameof(Deconstruct_Values))] - public void Deconstruct_IPAddress_Int_Test(Subnet subnet) - { - // Arrange - // Act - var (networkPrefixAddress, routingPrefix) = subnet; + // Assert + Assert.Equal(subnet.NetworkPrefixAddress, networkPrefixAddress); + Assert.Equal(subnet.RoutingPrefix, routingPrefix); + } - // Assert - Assert.Equal(subnet.NetworkPrefixAddress, networkPrefixAddress); - Assert.Equal(subnet.RoutingPrefix, routingPrefix); - } + #endregion // end: Deconstruct(IPAddress, int) - #endregion // end: Deconstruct(IPAddress, int) + #endregion // end: Deconstruct - #endregion // end: Deconstruct - } + internal class SubnetEqualityComparer : IEqualityComparer + { + public static readonly SubnetEqualityComparer Instance = new SubnetEqualityComparer(); + + public bool Equals(Subnet x, Subnet y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (x is null && y is null) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + return x.Equals(y); + } + + public int GetHashCode(Subnet obj) + { + return obj is null ? -1 : obj.GetHashCode(); + } + } + } } diff --git a/src/Arcus.Tests/Utilities/IPAddressUtilitiesTests.cs b/src/Arcus.Tests/Utilities/IPAddressUtilitiesTests.cs index 4ae5b2c..2853816 100644 --- a/src/Arcus.Tests/Utilities/IPAddressUtilitiesTests.cs +++ b/src/Arcus.Tests/Utilities/IPAddressUtilitiesTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -7,23 +7,14 @@ using Arcus.Utilities; using Gulliver; using Xunit; -using Xunit.Abstractions; namespace Arcus.Tests.Utilities { +#if NET6_0_OR_GREATER +#pragma warning disable IDE0062 // Make local function static (IDE0062); purposely allowing non-static functions that could be static for .net4.8 compatibility +#endif public class IPAddressUtilitiesTests { - #region Setup / Teardown - - public IPAddressUtilitiesTests(ITestOutputHelper testOutputHelper) - { - this._testOutputHelper = testOutputHelper; - } - - private readonly ITestOutputHelper _testOutputHelper; - - #endregion - #region IPv4MaxAddress [Fact] @@ -118,8 +109,7 @@ public void IPv6MinAddress_Test() [InlineData(false, "ffff::")] [InlineData(true, "192.168.1.1")] [InlineData(true, "0.0.0.0")] - public void IsIPv4_Test(bool expected, - string input) + public void IsIPv4_Test(bool expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -143,8 +133,7 @@ public void IsIPv4_Test(bool expected, [InlineData(true, "::ffff:ab:cd")] [InlineData(false, "1234::ffff:222.1.41.90")] [InlineData(false, "1234::ffff:ab:cd")] - public void IsIPv4MappedIPv6_Test(bool expected, - string input) + public void IsIPv4MappedIPv6_Test(bool expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -166,8 +155,7 @@ public void IsIPv4MappedIPv6_Test(bool expected, [InlineData(true, "ffff::")] [InlineData(false, "192.168.1.1")] [InlineData(false, "0.0.0.0")] - public void IsIPv6_Test(bool expected, - string input) + public void IsIPv6_Test(bool expected, string input) { // Arrange _ = IPAddress.TryParse(input, out var address); @@ -186,39 +174,30 @@ public void IsIPv6_Test(bool expected, private static IEnumerable NonStandardAddressFamilies() { return Enum.GetValues(typeof(AddressFamily)) - .Cast() - .Except(new[] - { - AddressFamily.InterNetwork, - AddressFamily.InterNetworkV6 - }); + .Cast() + .Except(new[] { AddressFamily.InterNetwork, AddressFamily.InterNetworkV6 }); } public static IEnumerable InvalidAddressFamily_Values() { return Enum.GetValues(typeof(AddressFamily)) - .Cast() - .Where(addressFamily => addressFamily != AddressFamily.InterNetworkV6) - .Where(addressFamily => addressFamily != AddressFamily.InterNetwork) - .Select(e => new object[] {e}); + .Cast() + .Where(addressFamily => + addressFamily != AddressFamily.InterNetworkV6 && addressFamily != AddressFamily.InterNetwork + ) + .Distinct() + .Select(e => new object[] { e }); } public static IEnumerable ValidAddressFamily_Values() { - yield return new object[] {AddressFamily.InterNetwork}; - yield return new object[] {AddressFamily.InterNetworkV6}; + yield return new object[] { AddressFamily.InterNetwork }; + yield return new object[] { AddressFamily.InterNetworkV6 }; } private static IEnumerable GeneralPurposeIPv4Addresses() { - var addressStrings = new[] - { - "10.0.0.0", - "10.0.0.128", - "0.0.0.0", - "255.255.255.255", - "192.168.1.1" - }; + var addressStrings = new[] { "10.0.0.0", "10.0.0.128", "0.0.0.0", "255.255.255.255", "192.168.1.1" }; foreach (var addressString in addressStrings) { @@ -234,21 +213,21 @@ private static IEnumerable GeneralPurposeIPv4Addresses() private static IEnumerable GeneralPurposeIPv6Addresses() { var addressStrings = new[] - { - "::", - "::1", - "1::", - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", - "1234:ffff:ffff:ffff::", - "::ffff:ffff:ffff:abcd", - "1234::abcd", - "1234::ffff:abcd", - "1234:ffff::abcd", - "1234:ffff::ffff:abcd", - "1:2:3:4:5:6:7:8", - "a:b:c::", - "::a:b:c" - }; + { + "::", + "::1", + "1::", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "1234:ffff:ffff:ffff::", + "::ffff:ffff:ffff:abcd", + "1234::abcd", + "1234::ffff:abcd", + "1234:ffff::abcd", + "1234:ffff::ffff:abcd", + "1:2:3:4:5:6:7:8", + "a:b:c::", + "::a:b:c", + }; foreach (var addressString in addressStrings) { @@ -268,36 +247,33 @@ public static IEnumerable IsValidNetMask_Test_Values() // all valid netmask values for (var i = 0; i <= 32; i++) { - var netmaskBytes = Enumerable.Repeat((byte) 0xFF, 4) - .ToArray() - .ShiftBitsLeft(32 - i); + var netmaskBytes = Enumerable.Repeat((byte)0xFF, 4).ToArray().ShiftBitsLeft(32 - i); - yield return new object[] {true, new IPAddress(netmaskBytes)}; + yield return new object[] { true, new IPAddress(netmaskBytes) }; } - yield return new object[] {false, null}; + yield return new object[] { false, null }; var invalidNetmaskAddressStrings = new[] - { - "::", - "ffff::", - "255.255.0.255", - "255.255.0.255", - "255.0.255.255", - "0.255.255.255", - "0.0.0.255", - "0.0.0.1" - }; + { + "::", + "ffff::", + "255.255.0.255", + "255.255.0.255", + "255.0.255.255", + "0.255.255.255", + "0.0.0.255", + "0.0.0.1", + }; foreach (var s in invalidNetmaskAddressStrings) { - yield return new object[] {false, IPAddress.Parse(s)}; + yield return new object[] { false, IPAddress.Parse(s) }; } } [Theory] [MemberData(nameof(IsValidNetMask_Test_Values))] - public void IsValidNetMask_Test(bool expected, - IPAddress input) + public void IsValidNetMask_Test(bool expected, IPAddress input) { // Arrange // Act @@ -319,47 +295,39 @@ public static IEnumerable ParseFromHexString_Test_Values() { var asHex = AddressToHexString(address); - yield return new object[] {address, asHex.ToUpperInvariant(), address.AddressFamily}; -#pragma warning disable CA1308 // purposely testing lower case - yield return new object[] {address, asHex.ToLowerInvariant(), address.AddressFamily}; -#pragma warning restore CA1308 - - yield return new object[] {address, $"0x{asHex}".ToUpperInvariant(), address.AddressFamily}; - -#pragma warning disable CA1308 // purposely testing lower case - yield return new object[] {address, $"0x{asHex}".ToLowerInvariant(), address.AddressFamily}; -#pragma warning restore CA1308 + yield return new object[] { address, asHex.ToUpperInvariant(), address.AddressFamily }; + yield return new object[] { address, asHex.ToLowerInvariant(), address.AddressFamily }; + yield return new object[] { address, $"0x{asHex}".ToUpperInvariant(), address.AddressFamily }; + yield return new object[] { address, $"0x{asHex}".ToLowerInvariant(), address.AddressFamily }; // removed most significant zero bytes - var msbZeroTrim = new string(asHex.SkipWhile(c => c == '0') - .ToArray()); + var msbZeroTrim = new string(asHex.SkipWhile(c => c == '0').ToArray()); if (!string.IsNullOrEmpty(msbZeroTrim)) { - yield return new object[] {address, msbZeroTrim.ToUpperInvariant(), address.AddressFamily}; -#pragma warning disable CA1308 // purposely testing lower case - yield return new object[] {address, msbZeroTrim.ToLowerInvariant(), address.AddressFamily}; -#pragma warning restore CA1308 - - yield return new object[] {address, $"0x{msbZeroTrim}".ToUpperInvariant(), address.AddressFamily}; - -#pragma warning disable CA1308 // purposely testing lower case - yield return new object[] {address, $"0x{msbZeroTrim}".ToLowerInvariant(), address.AddressFamily}; -#pragma warning restore CA1308 + yield return new object[] { address, msbZeroTrim.ToUpperInvariant(), address.AddressFamily }; + yield return new object[] { address, msbZeroTrim.ToLowerInvariant(), address.AddressFamily }; + yield return new object[] { address, $"0x{msbZeroTrim}".ToUpperInvariant(), address.AddressFamily }; + yield return new object[] { address, $"0x{msbZeroTrim}".ToLowerInvariant(), address.AddressFamily }; } } - yield return new object[] {IPAddress.Parse("128.128.128.128"), "00000000080808080", AddressFamily.InterNetwork}; // extra zero msb + yield return new object[] { IPAddress.Parse("128.128.128.128"), "00000000080808080", AddressFamily.InterNetwork }; // extra zero msb // expected failures - yield return new object[] {null, null, AddressFamily.InterNetwork}; - yield return new object[] {null, null, AddressFamily.InterNetworkV6}; - yield return new object[] {null, AddressToHexString(IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), AddressFamily.InterNetwork}; + yield return new object[] { null, null, AddressFamily.InterNetwork }; + yield return new object[] { null, null, AddressFamily.InterNetworkV6 }; + yield return new object[] + { + null, + AddressToHexString(IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")), + AddressFamily.InterNetwork, + }; // non standard address family foreach (var addressFamily in NonStandardAddressFamilies()) { - yield return new object[] {null, "0", addressFamily}; + yield return new object[] { null, "0", addressFamily }; } IEnumerable Addresses() @@ -380,17 +348,13 @@ IEnumerable Addresses() string AddressToHexString(IPAddress address) { - return string.Concat(address.GetAddressBytes() - .Select(b => Convert.ToString(b, 16) - .PadLeft(2, '0'))); + return string.Concat(address.GetAddressBytes().Select(b => Convert.ToString(b, 16).PadLeft(2, '0'))); } } [Theory] [MemberData(nameof(ParseFromHexString_Test_Values))] - public void ParseFromHexString_Test(IPAddress expected, - string addressString, - AddressFamily addressFamily) + public void ParseFromHexString_Test(IPAddress expected, string addressString, AddressFamily addressFamily) { // Arrange if (expected == null) @@ -411,7 +375,7 @@ public void ParseFromHexString_NullInput_Throws_ArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressUtilities.ParseFromHexString(null, default)); } @@ -424,7 +388,7 @@ public void ParseFromHexString_EmptyOrWhitespaceInput_Throws_ArgumentException_T // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressUtilities.ParseFromHexString(input, default)); } @@ -435,7 +399,7 @@ public void ParseFromHexString_InvalidAddressFamily_Throws_ArgumentException_Tes // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressUtilities.ParseFromHexString("abc123", addressFamily)); } @@ -448,15 +412,13 @@ public void ParseFromHexString_NonHexInput_Throws_ArgumentException_Test(string // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressUtilities.ParseFromHexString(input, AddressFamily.InterNetwork)); } [Theory] [MemberData(nameof(ParseFromHexString_Test_Values))] - public void TryParseFromHexString_Test(IPAddress expected, - string addressString, - AddressFamily addressFamily) + public void TryParseFromHexString_Test(IPAddress expected, string addressString, AddressFamily addressFamily) { // Arrange // Act @@ -475,25 +437,25 @@ public static IEnumerable ParseIgnoreOctalInIPv4_Test_Values() { foreach (var address in Addresses()) { - yield return new object[] {address, address.ToString()}; + yield return new object[] { address, address.ToString() }; if (address.AddressFamily == AddressFamily.InterNetwork) { - yield return new object[] {address, AddressToQuads(address)}; + yield return new object[] { address, AddressToQuads(address) }; } } - yield return new object[] {IPAddress.Parse("0.0.0.192"), "192"}; - yield return new object[] {IPAddress.Parse("1.0.0.192"), "1.192"}; - yield return new object[] {IPAddress.Parse("1.255.0.192"), "1.255.192"}; + yield return new object[] { IPAddress.Parse("0.0.0.192"), "192" }; + yield return new object[] { IPAddress.Parse("1.0.0.192"), "1.192" }; + yield return new object[] { IPAddress.Parse("1.255.0.192"), "1.255.192" }; // octal case - yield return new object[] {IPAddress.Parse("7.7.7.0"), "007.007.7.0"}; + yield return new object[] { IPAddress.Parse("7.7.7.0"), "007.007.7.0" }; // expected failures - yield return new object[] {null, null}; - yield return new object[] {null, "potato"}; - yield return new object[] {null, "255.255.255.255.255"}; + yield return new object[] { null, null }; + yield return new object[] { null, "potato" }; + yield return new object[] { null, "255.255.255.255.255" }; IEnumerable Addresses() { @@ -515,17 +477,13 @@ IEnumerable Addresses() string AddressToQuads(IPAddress address) { - return string.Join(".", - address.GetAddressBytes() - .Select(b => Convert.ToString(b, 10) - .PadLeft(3, '0'))); + return string.Join(".", address.GetAddressBytes().Select(b => Convert.ToString(b, 10).PadLeft(3, '0'))); } } [Theory] [MemberData(nameof(ParseIgnoreOctalInIPv4_Test_Values))] - public void ParseIgnoreOctalInIPv4_Test(IPAddress expected, - string input) + public void ParseIgnoreOctalInIPv4_Test(IPAddress expected, string input) { // Arrange if (expected == null) @@ -546,7 +504,7 @@ public void ParseIgnoreOctalInIPv4_NullInput_ThrowsArgumentNullException_Test() // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressUtilities.ParseIgnoreOctalInIPv4(null)); } @@ -559,14 +517,13 @@ public void ParseIgnoreOctalInIPv4_EmptyOrWhitespaceInput_Throws_ArgumentExcepti // Arrange // Act // Assert - // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws(() => IPAddressUtilities.ParseIgnoreOctalInIPv4(input)); } [Theory] [MemberData(nameof(ParseIgnoreOctalInIPv4_Test_Values))] - public void TryParseIgnoreOctalInIPv4_Test(IPAddress expected, - string input) + public void TryParseIgnoreOctalInIPv4_Test(IPAddress expected, string input) { // Arrange var success = IPAddressUtilities.TryParseIgnoreOctalInIPv4(input, out var result); @@ -584,50 +541,39 @@ public static IEnumerable Parse_BytesArray_Test_Values() { foreach (var address in Addresses()) { - yield return new object[] - { - address, address.GetAddressBytes() - .ToArray(), - address.AddressFamily - }; + yield return new object[] { address, address.GetAddressBytes().ToArray(), address.AddressFamily }; } // underflow, add msb zeros - yield return new object[] {IPAddress.Parse("0.0.0.0"), Array.Empty(), AddressFamily.InterNetwork}; - yield return new object[] {IPAddress.Parse("::"), Array.Empty(), AddressFamily.InterNetworkV6}; + yield return new object[] { IPAddress.Parse("0.0.0.0"), Array.Empty(), AddressFamily.InterNetwork }; + yield return new object[] { IPAddress.Parse("::"), Array.Empty(), AddressFamily.InterNetworkV6 }; - yield return new object[] {IPAddress.Parse("0.0.0.255"), new byte[] {0x00, 0xff}, AddressFamily.InterNetwork}; - yield return new object[] {IPAddress.Parse("::acca"), new byte[] {0x00, 0x00, 0xac, 0xca}, AddressFamily.InterNetworkV6}; + yield return new object[] { IPAddress.Parse("0.0.0.255"), new byte[] { 0x00, 0xff }, AddressFamily.InterNetwork }; + yield return new object[] + { + IPAddress.Parse("::acca"), + new byte[] { 0x00, 0x00, 0xac, 0xca }, + AddressFamily.InterNetworkV6, + }; // ipv4 overflow - yield return new object[] - { - null, Enumerable.Repeat((byte) 0xff, 5) - .ToArray(), - AddressFamily.InterNetwork - }; + yield return new object[] { null, Enumerable.Repeat((byte)0xff, 5).ToArray(), AddressFamily.InterNetwork }; // ipv6 overflow - yield return new object[] - { - null, Enumerable.Repeat((byte) 0xff, 17) - .ToArray(), - AddressFamily.InterNetworkV6 - }; + yield return new object[] { null, Enumerable.Repeat((byte)0xff, 17).ToArray(), AddressFamily.InterNetworkV6 }; - yield return new object[] {null, null, AddressFamily.InterNetwork}; - yield return new object[] {null, null, AddressFamily.InterNetworkV6}; + yield return new object[] { null, null, AddressFamily.InterNetwork }; + yield return new object[] { null, null, AddressFamily.InterNetworkV6 }; // non standard address family foreach (var addressFamily in NonStandardAddressFamilies()) { - yield return new object[] {null, Array.Empty(), addressFamily}; + yield return new object[] { null, Array.Empty(), addressFamily }; } IEnumerable Addresses() { - foreach (var address in GeneralPurposeIPv4Addresses() - .Concat(GeneralPurposeIPv6Addresses())) + foreach (var address in GeneralPurposeIPv4Addresses().Concat(GeneralPurposeIPv6Addresses())) { yield return address; } @@ -636,9 +582,7 @@ IEnumerable Addresses() [Theory] [MemberData(nameof(Parse_BytesArray_Test_Values))] - public void Parse_ByteArray_Test(IPAddress expected, - byte[] bytes, - AddressFamily addressFamily) + public void Parse_ByteArray_Test(IPAddress expected, byte[] bytes, AddressFamily addressFamily) { // Arrange if (expected == null) @@ -660,28 +604,25 @@ public void Parse_Bytes_InvalidAddressFamily_Throws_ArgumentOutOfRangeException_ // Arrange // Assert // Assert - Assert.Throws(() => IPAddressUtilities.Parse(new byte[] {0x42}, addressFamily)); + Assert.Throws(() => IPAddressUtilities.Parse(new byte[] { 0x42 }, addressFamily)); } [Theory] [InlineData(17, AddressFamily.InterNetworkV6)] [InlineData(5, AddressFamily.InterNetwork)] - public void Parse_Bytes_InputTooLong_Throws_ArgumentOutOfRangeException_Test(int count, - AddressFamily addressFamily) + public void Parse_Bytes_InputTooLong_Throws_ArgumentOutOfRangeException_Test(int count, AddressFamily addressFamily) { // Arrange // Assert // Assert - Assert.Throws(() => IPAddressUtilities.Parse(Enumerable.Repeat((byte) 0x00, count) - .ToArray(), - addressFamily)); + Assert.Throws( + () => IPAddressUtilities.Parse(Enumerable.Repeat((byte)0x00, count).ToArray(), addressFamily) + ); } [Theory] [MemberData(nameof(Parse_BytesArray_Test_Values))] - public void TryParse_ByteArray_Test(IPAddress expected, - byte[] bytes, - AddressFamily addressFamily) + public void TryParse_ByteArray_Test(IPAddress expected, byte[] bytes, AddressFamily addressFamily) { // Arrange // Act @@ -778,10 +719,7 @@ public void IPv4BitCount_Value_Test() // Arrange // Act // Assert - Assert.Equal(IPAddress.Any.GetAddressBytes() - .Length - * 8, - IPAddressUtilities.IPv4BitCount); + Assert.Equal(IPAddressUtilities.IPv4BitCount, IPAddress.Any.GetAddressBytes().Length * 8); } [Fact] @@ -790,10 +728,7 @@ public void IPv6BitCount_Value_Test() // Arrange // Act // Assert - Assert.Equal(IPAddress.IPv6Any.GetAddressBytes() - .Length - * 8, - IPAddressUtilities.IPv6BitCount); + Assert.Equal(IPAddressUtilities.IPv6BitCount, IPAddress.IPv6Any.GetAddressBytes().Length * 8); } #endregion // end: BitCount @@ -806,9 +741,7 @@ public void IPv4ByteCount_Value_Test() // Arrange // Act // Assert - Assert.Equal(IPAddress.Any.GetAddressBytes() - .Length, - IPAddressUtilities.IPv4ByteCount); + Assert.Equal(IPAddressUtilities.IPv4ByteCount, IPAddress.Any.GetAddressBytes().Length); } [Fact] @@ -817,9 +750,7 @@ public void IPv6ByteCount_Value_Test() // Arrange // Act // Assert - Assert.Equal(IPAddress.IPv6Any.GetAddressBytes() - .Length, - IPAddressUtilities.IPv6ByteCount); + Assert.Equal(IPAddressUtilities.IPv6ByteCount, IPAddress.IPv6Any.GetAddressBytes().Length); } #endregion // end: ByteCount @@ -834,9 +765,7 @@ public void IPv6MaxAddress_Value_Test() var address = IPAddressUtilities.IPv6MaxAddress; // Assert - Assert.Equal(new IPAddress(Enumerable.Repeat((byte) 0xff, 16) - .ToArray()), - address); + Assert.Equal(new IPAddress(Enumerable.Repeat((byte)0xff, 16).ToArray()), address); Assert.Equal(AddressFamily.InterNetworkV6, address.AddressFamily); } @@ -848,9 +777,7 @@ public void IPv4MaxAddress_Value_Test() var address = IPAddressUtilities.IPv4MaxAddress; // Assert - Assert.Equal(new IPAddress(Enumerable.Repeat((byte) 0xff, 4) - .ToArray()), - address); + Assert.Equal(new IPAddress(Enumerable.Repeat((byte)0xff, 4).ToArray()), address); Assert.Equal(AddressFamily.InterNetwork, address.AddressFamily); } @@ -866,9 +793,7 @@ public void IPv6MinAddress_Value_Test() var address = IPAddressUtilities.IPv6MinAddress; // Assert - Assert.Equal(new IPAddress(Enumerable.Repeat((byte) 0x00, 16) - .ToArray()), - address); + Assert.Equal(new IPAddress(Enumerable.Repeat((byte)0x00, 16).ToArray()), address); Assert.Equal(AddressFamily.InterNetworkV6, address.AddressFamily); } @@ -880,9 +805,7 @@ public void IPv4MinAddress_Value_Test() var address = IPAddressUtilities.IPv4MinAddress; // Assert - Assert.Equal(new IPAddress(Enumerable.Repeat((byte) 0x00, 4) - .ToArray()), - address); + Assert.Equal(new IPAddress(Enumerable.Repeat((byte)0x00, 4).ToArray()), address); Assert.Equal(AddressFamily.InterNetwork, address.AddressFamily); } @@ -915,17 +838,17 @@ public static IEnumerable IsPrivate_Test_Values() { foreach (var subnet in SubnetUtilities.PrivateIPAddressRangesList) { - yield return new object[] {true, subnet.NetworkPrefixAddress}; - yield return new object[] {true, subnet.NetworkPrefixAddress.Increment(2)}; - yield return new object[] {true, subnet.BroadcastAddress}; - yield return new object[] {true, subnet.BroadcastAddress.Increment(-2)}; + yield return new object[] { true, subnet.NetworkPrefixAddress }; + yield return new object[] { true, subnet.NetworkPrefixAddress.Increment(2) }; + yield return new object[] { true, subnet.BroadcastAddress }; + yield return new object[] { true, subnet.BroadcastAddress.Increment(-2) }; } - yield return new object[] {false, IPAddressUtilities.IPv4MaxAddress}; - yield return new object[] {false, IPAddressUtilities.IPv4MinAddress}; + yield return new object[] { false, IPAddressUtilities.IPv4MaxAddress }; + yield return new object[] { false, IPAddressUtilities.IPv4MinAddress }; - yield return new object[] {false, IPAddressUtilities.IPv6MaxAddress}; - yield return new object[] {false, IPAddressUtilities.IPv6MinAddress}; + yield return new object[] { false, IPAddressUtilities.IPv6MaxAddress }; + yield return new object[] { false, IPAddressUtilities.IPv6MinAddress }; } [Theory] diff --git a/src/Arcus.Tests/Utilities/SubnetUtilitiesTests.cs b/src/Arcus.Tests/Utilities/SubnetUtilitiesTests.cs index 0fd7d76..f8eadd2 100644 --- a/src/Arcus.Tests/Utilities/SubnetUtilitiesTests.cs +++ b/src/Arcus.Tests/Utilities/SubnetUtilitiesTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -18,12 +18,12 @@ public void PrivateIPAddressRangesList_Test() { // Arrange var expectedSubnets = new[] - { - Subnet.Parse("10.0.0.0", 8), - Subnet.Parse("172.16.0.0", 12), - Subnet.Parse("192.168.0.0", 16), - Subnet.Parse("fd00::", 8) - }; + { + Subnet.Parse("10.0.0.0", 8), + Subnet.Parse("172.16.0.0", 12), + Subnet.Parse("192.168.0.0", 16), + Subnet.Parse("fd00::", 8), + }; // Act var list = SubnetUtilities.PrivateIPAddressRangesList; @@ -31,20 +31,19 @@ public void PrivateIPAddressRangesList_Test() // Assert Assert.IsAssignableFrom>(list); Assert.Equal(4, list.Count); - Assert.Equal(list.Count, - list.Distinct() - .Count()); + Assert.Equal(list.Count, list.Distinct().Count()); Assert.Contains(list, s => s.IsIPv4); Assert.Contains(list, s => s.IsIPv6); - Assert.All(list, - subnet => - { - Assert.NotNull(subnet); - Assert.Contains(subnet, - expectedSubnets); - }); + Assert.All( + list, + subnet => + { + Assert.NotNull(subnet); + Assert.Contains(subnet, expectedSubnets); + } + ); } #endregion end: PrivateIPAddressRangesList @@ -55,11 +54,7 @@ public void PrivateIPAddressRangesList_Test() public void LinkLocalIPAddressRangesList_Test() { // Arrange - var expectedSubnets = new[] - { - Subnet.Parse("169.254.0.0", 16), - Subnet.Parse("fe80::", 10) - }; + var expectedSubnets = new[] { Subnet.Parse("169.254.0.0", 16), Subnet.Parse("fe80::", 10) }; // Act var list = SubnetUtilities.LinkLocalIPAddressRangesList; @@ -67,20 +62,19 @@ public void LinkLocalIPAddressRangesList_Test() // Assert Assert.IsAssignableFrom>(list); Assert.Equal(2, list.Count); - Assert.Equal(list.Count, - list.Distinct() - .Count()); + Assert.Equal(list.Count, list.Distinct().Count()); Assert.Contains(list, s => s.IsIPv4); Assert.Contains(list, s => s.IsIPv6); - Assert.All(list, - subnet => - { - Assert.NotNull(subnet); - Assert.Contains(subnet, - expectedSubnets); - }); + Assert.All( + list, + subnet => + { + Assert.NotNull(subnet); + Assert.Contains(subnet, expectedSubnets); + } + ); } #endregion end: LinkLocalIPAddressRangesList @@ -90,68 +84,463 @@ public void LinkLocalIPAddressRangesList_Test() public static IEnumerable FewestConsecutiveSubnetsFor_Test_Values() { yield return new object[] - { - new[] {Subnet.Parse("128.64.20.3/32")}, - IPAddress.Parse("128.64.20.3"), IPAddress.Parse("128.64.20.3") - }; + { + new[] { Subnet.Parse("128.64.20.3/32") }, + IPAddress.Parse("128.64.20.3"), + IPAddress.Parse("128.64.20.3"), + }; yield return new object[] - { - new[] {"128.64.20.3/32", "128.64.20.4/30", "128.64.20.8/30", "128.64.20.12/32"}.Select(s => Subnet.Parse(s)), - IPAddress.Parse("128.64.20.3"), IPAddress.Parse("128.64.20.12") - }; + { + new[] { "128.64.20.3/32", "128.64.20.4/30", "128.64.20.8/30", "128.64.20.12/32" }.Select(s => Subnet.Parse(s)), + IPAddress.Parse("128.64.20.3"), + IPAddress.Parse("128.64.20.12"), + }; yield return new object[] - { - new[] {"128.64.20.3/32", "128.64.20.4/30", "128.64.20.8/30", "128.64.20.12/32"}.Select(s => Subnet.Parse(s)), - IPAddress.Parse("128.64.20.12"), IPAddress.Parse("128.64.20.3") - }; + { + new[] { "128.64.20.3/32", "128.64.20.4/30", "128.64.20.8/30", "128.64.20.12/32" }.Select(s => Subnet.Parse(s)), + IPAddress.Parse("128.64.20.12"), + IPAddress.Parse("128.64.20.3"), + }; yield return new object[] - { - new[] {"192.168.1.3/32", "192.168.1.4/31"}.Select(s => Subnet.Parse(s)), - IPAddress.Parse("192.168.1.3"), IPAddress.Parse("192.168.1.5") - }; + { + new[] { "192.168.1.3/32", "192.168.1.4/31" }.Select(s => Subnet.Parse(s)), + IPAddress.Parse("192.168.1.3"), + IPAddress.Parse("192.168.1.5"), + }; yield return new object[] - { - new[] {Subnet.Parse("2001:400:4402::/128")}, IPAddress.Parse("2001:400:4402::"), IPAddress.Parse("2001:400:4402::") - }; + { + new[] { Subnet.Parse("2001:400:4402::/128") }, + IPAddress.Parse("2001:400:4402::"), + IPAddress.Parse("2001:400:4402::"), + }; yield return new object[] - { - new[] {Subnet.Parse("2001:400:4402::/48")}, IPAddress.Parse("2001:400:4402::"), IPAddress.Parse("2001:400:4402:ffff:ffff:ffff:ffff:ffff") - }; + { + new[] { Subnet.Parse("2001:400:4402::/48") }, + IPAddress.Parse("2001:400:4402::"), + IPAddress.Parse("2001:400:4402:ffff:ffff:ffff:ffff:ffff"), + }; yield return new object[] - { - new[] {"2001:400:4402::ffff/128", "2001:400:4402::1:0/112", "2001:400:4402::2:0/111", "2001:400:4402::4:0/110", "2001:400:4402::8:0/109", "2001:400:4402::10:0/108", "2001:400:4402::20:0/107", "2001:400:4402::40:0/106", "2001:400:4402::80:0/105", "2001:400:4402::100:0/104", "2001:400:4402::200:0/103", "2001:400:4402::400:0/102", "2001:400:4402::800:0/101", "2001:400:4402::1000:0/100", "2001:400:4402::2000:0/99", "2001:400:4402::4000:0/98", "2001:400:4402::8000:0/97", "2001:400:4402::1:0:0/96", "2001:400:4402::2:0:0/95", "2001:400:4402::4:0:0/94", "2001:400:4402::8:0:0/93", "2001:400:4402::10:0:0/92", "2001:400:4402::20:0:0/91", "2001:400:4402::40:0:0/90", "2001:400:4402::80:0:0/89", "2001:400:4402::100:0:0/88", "2001:400:4402::200:0:0/87", "2001:400:4402::400:0:0/86", "2001:400:4402::800:0:0/85", "2001:400:4402::1000:0:0/84", "2001:400:4402::2000:0:0/83", "2001:400:4402::4000:0:0/82", "2001:400:4402::8000:0:0/81", "2001:400:4402:0:1::/80", "2001:400:4402:0:2::/79", "2001:400:4402:0:4::/78", "2001:400:4402:0:8::/77", "2001:400:4402:0:10::/76", "2001:400:4402:0:20::/75", "2001:400:4402:0:40::/74", "2001:400:4402:0:80::/73", "2001:400:4402:0:100::/72", "2001:400:4402:0:200::/71", "2001:400:4402:0:400::/70", "2001:400:4402:0:800::/69", "2001:400:4402:0:1000::/68", "2001:400:4402:0:2000::/67", "2001:400:4402:0:4000::/66", "2001:400:4402:0:8000::/65", "2001:400:4402:1::/64", "2001:400:4402:2::/63", "2001:400:4402:4::/62", "2001:400:4402:8::/61", "2001:400:4402:10::/60", "2001:400:4402:20::/59", "2001:400:4402:40::/58", "2001:400:4402:80::/57", "2001:400:4402:100::/56", "2001:400:4402:200::/55", "2001:400:4402:400::/54", "2001:400:4402:800::/53", "2001:400:4402:1000::/52", "2001:400:4402:2000::/51", "2001:400:4402:4000::/50", "2001:400:4402:8000::/49"}.Select(s => Subnet.Parse(s)), - IPAddress.Parse("2001:400:4402::ffff"), IPAddress.Parse("2001:400:4402:ffff:ffff:ffff:ffff:ffff") - }; + { + new[] + { + "2001:400:4402::ffff/128", + "2001:400:4402::1:0/112", + "2001:400:4402::2:0/111", + "2001:400:4402::4:0/110", + "2001:400:4402::8:0/109", + "2001:400:4402::10:0/108", + "2001:400:4402::20:0/107", + "2001:400:4402::40:0/106", + "2001:400:4402::80:0/105", + "2001:400:4402::100:0/104", + "2001:400:4402::200:0/103", + "2001:400:4402::400:0/102", + "2001:400:4402::800:0/101", + "2001:400:4402::1000:0/100", + "2001:400:4402::2000:0/99", + "2001:400:4402::4000:0/98", + "2001:400:4402::8000:0/97", + "2001:400:4402::1:0:0/96", + "2001:400:4402::2:0:0/95", + "2001:400:4402::4:0:0/94", + "2001:400:4402::8:0:0/93", + "2001:400:4402::10:0:0/92", + "2001:400:4402::20:0:0/91", + "2001:400:4402::40:0:0/90", + "2001:400:4402::80:0:0/89", + "2001:400:4402::100:0:0/88", + "2001:400:4402::200:0:0/87", + "2001:400:4402::400:0:0/86", + "2001:400:4402::800:0:0/85", + "2001:400:4402::1000:0:0/84", + "2001:400:4402::2000:0:0/83", + "2001:400:4402::4000:0:0/82", + "2001:400:4402::8000:0:0/81", + "2001:400:4402:0:1::/80", + "2001:400:4402:0:2::/79", + "2001:400:4402:0:4::/78", + "2001:400:4402:0:8::/77", + "2001:400:4402:0:10::/76", + "2001:400:4402:0:20::/75", + "2001:400:4402:0:40::/74", + "2001:400:4402:0:80::/73", + "2001:400:4402:0:100::/72", + "2001:400:4402:0:200::/71", + "2001:400:4402:0:400::/70", + "2001:400:4402:0:800::/69", + "2001:400:4402:0:1000::/68", + "2001:400:4402:0:2000::/67", + "2001:400:4402:0:4000::/66", + "2001:400:4402:0:8000::/65", + "2001:400:4402:1::/64", + "2001:400:4402:2::/63", + "2001:400:4402:4::/62", + "2001:400:4402:8::/61", + "2001:400:4402:10::/60", + "2001:400:4402:20::/59", + "2001:400:4402:40::/58", + "2001:400:4402:80::/57", + "2001:400:4402:100::/56", + "2001:400:4402:200::/55", + "2001:400:4402:400::/54", + "2001:400:4402:800::/53", + "2001:400:4402:1000::/52", + "2001:400:4402:2000::/51", + "2001:400:4402:4000::/50", + "2001:400:4402:8000::/49", + }.Select(s => Subnet.Parse(s)), + IPAddress.Parse("2001:400:4402::ffff"), + IPAddress.Parse("2001:400:4402:ffff:ffff:ffff:ffff:ffff"), + }; yield return new object[] - { - new[] {"0.0.0.1/32", "0.0.0.2/31", "0.0.0.4/30", "0.0.0.8/29", "0.0.0.16/28", "0.0.0.32/27", "0.0.0.64/26", "0.0.0.128/25", "0.0.1.0/24", "0.0.2.0/23", "0.0.4.0/22", "0.0.8.0/21", "0.0.16.0/20", "0.0.32.0/19", "0.0.64.0/18", "0.0.128.0/17", "0.1.0.0/16", "0.2.0.0/15", "0.4.0.0/14", "0.8.0.0/13", "0.16.0.0/12", "0.32.0.0/11", "0.64.0.0/10", "0.128.0.0/9", "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/2", "192.0.0.0/3", "224.0.0.0/4", "240.0.0.0/5", "248.0.0.0/6", "252.0.0.0/7", "254.0.0.0/8", "255.0.0.0/9", "255.128.0.0/10", "255.192.0.0/11", "255.224.0.0/12", "255.240.0.0/13", "255.248.0.0/14", "255.252.0.0/15", "255.254.0.0/16", "255.255.0.0/17", "255.255.128.0/18", "255.255.192.0/19", "255.255.224.0/20", "255.255.240.0/21", "255.255.248.0/22", "255.255.252.0/23", "255.255.254.0/24", "255.255.255.0/25", "255.255.255.128/26", "255.255.255.192/27", "255.255.255.224/28", "255.255.255.240/29", "255.255.255.248/30", "255.255.255.252/31", "255.255.255.254/32"}.Select(s => Subnet.Parse(s)), - IPAddress.Parse("0.0.0.1"), IPAddress.Parse("255.255.255.254") - }; + { + new[] + { + "0.0.0.1/32", + "0.0.0.2/31", + "0.0.0.4/30", + "0.0.0.8/29", + "0.0.0.16/28", + "0.0.0.32/27", + "0.0.0.64/26", + "0.0.0.128/25", + "0.0.1.0/24", + "0.0.2.0/23", + "0.0.4.0/22", + "0.0.8.0/21", + "0.0.16.0/20", + "0.0.32.0/19", + "0.0.64.0/18", + "0.0.128.0/17", + "0.1.0.0/16", + "0.2.0.0/15", + "0.4.0.0/14", + "0.8.0.0/13", + "0.16.0.0/12", + "0.32.0.0/11", + "0.64.0.0/10", + "0.128.0.0/9", + "1.0.0.0/8", + "2.0.0.0/7", + "4.0.0.0/6", + "8.0.0.0/5", + "16.0.0.0/4", + "32.0.0.0/3", + "64.0.0.0/2", + "128.0.0.0/2", + "192.0.0.0/3", + "224.0.0.0/4", + "240.0.0.0/5", + "248.0.0.0/6", + "252.0.0.0/7", + "254.0.0.0/8", + "255.0.0.0/9", + "255.128.0.0/10", + "255.192.0.0/11", + "255.224.0.0/12", + "255.240.0.0/13", + "255.248.0.0/14", + "255.252.0.0/15", + "255.254.0.0/16", + "255.255.0.0/17", + "255.255.128.0/18", + "255.255.192.0/19", + "255.255.224.0/20", + "255.255.240.0/21", + "255.255.248.0/22", + "255.255.252.0/23", + "255.255.254.0/24", + "255.255.255.0/25", + "255.255.255.128/26", + "255.255.255.192/27", + "255.255.255.224/28", + "255.255.255.240/29", + "255.255.255.248/30", + "255.255.255.252/31", + "255.255.255.254/32", + }.Select(s => Subnet.Parse(s)), + IPAddress.Parse("0.0.0.1"), + IPAddress.Parse("255.255.255.254"), + }; yield return new object[] - { - new[] {"::1/128", "::2/127", "::4/126", "::8/125", "::10/124", "::20/123", "::40/122", "::80/121", "::100/120", "::200/119", "::400/118", "::800/117", "::1000/116", "::2000/115", "::4000/114", "::8000/113", "::0.1.0.0/112", "::0.2.0.0/111", "::0.4.0.0/110", "::0.8.0.0/109", "::0.16.0.0/108", "::0.32.0.0/107", "::0.64.0.0/106", "::0.128.0.0/105", "::1.0.0.0/104", "::2.0.0.0/103", "::4.0.0.0/102", "::8.0.0.0/101", "::16.0.0.0/100", "::32.0.0.0/99", "::64.0.0.0/98", "::128.0.0.0/97", "::1:0:0/96", "::2:0:0/95", "::4:0:0/94", "::8:0:0/93", "::10:0:0/92", "::20:0:0/91", "::40:0:0/90", "::80:0:0/89", "::100:0:0/88", "::200:0:0/87", "::400:0:0/86", "::800:0:0/85", "::1000:0:0/84", "::2000:0:0/83", "::4000:0:0/82", "::8000:0:0/81", "::1:0:0:0/80", "::2:0:0:0/79", "::4:0:0:0/78", "::8:0:0:0/77", "::10:0:0:0/76", "::20:0:0:0/75", "::40:0:0:0/74", "::80:0:0:0/73", "::100:0:0:0/72", "::200:0:0:0/71", "::400:0:0:0/70", "::800:0:0:0/69", "::1000:0:0:0/68", "::2000:0:0:0/67", "::4000:0:0:0/66", "::8000:0:0:0/65", "0:0:0:1::/64", "0:0:0:2::/63", "0:0:0:4::/62", "0:0:0:8::/61", "0:0:0:10::/60", "0:0:0:20::/59", "0:0:0:40::/58", "0:0:0:80::/57", "0:0:0:100::/56", "0:0:0:200::/55", "0:0:0:400::/54", "0:0:0:800::/53", "0:0:0:1000::/52", "0:0:0:2000::/51", "0:0:0:4000::/50", "0:0:0:8000::/49", "0:0:1::/48", "0:0:2::/47", "0:0:4::/46", "0:0:8::/45", "0:0:10::/44", "0:0:20::/43", "0:0:40::/42", "0:0:80::/41", "0:0:100::/40", "0:0:200::/39", "0:0:400::/38", "0:0:800::/37", "0:0:1000::/36", "0:0:2000::/35", "0:0:4000::/34", "0:0:8000::/33", "0:1::/32", "0:2::/31", "0:4::/30", "0:8::/29", "0:10::/28", "0:20::/27", "0:40::/26", "0:80::/25", "0:100::/24", "0:200::/23", "0:400::/22", "0:800::/21", "0:1000::/20", "0:2000::/19", "0:4000::/18", "0:8000::/17", "1::/16", "2::/15", "4::/14", "8::/13", "10::/12", "20::/11", "40::/10", "80::/9", "100::/8", "200::/7", "400::/6", "800::/5", "1000::/4", "2000::/3", "4000::/2", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6", "fc00::/7", "fe00::/8", "ff00::/9", "ff80::/10", "ffc0::/11", "ffe0::/12", "fff0::/13", "fff8::/14", "fffc::/15", "fffe::/16", "ffff::/17", "ffff:8000::/18", "ffff:c000::/19", "ffff:e000::/20", "ffff:f000::/21", "ffff:f800::/22", "ffff:fc00::/23", "ffff:fe00::/24", "ffff:ff00::/25", "ffff:ff80::/26", "ffff:ffc0::/27", "ffff:ffe0::/28", "ffff:fff0::/29", "ffff:fff8::/30", "ffff:fffc::/31", "ffff:fffe::/32", "ffff:ffff::/33", "ffff:ffff:8000::/34", "ffff:ffff:c000::/35", "ffff:ffff:e000::/36", "ffff:ffff:f000::/37", "ffff:ffff:f800::/38", "ffff:ffff:fc00::/39", "ffff:ffff:fe00::/40", "ffff:ffff:ff00::/41", "ffff:ffff:ff80::/42", "ffff:ffff:ffc0::/43", "ffff:ffff:ffe0::/44", "ffff:ffff:fff0::/45", "ffff:ffff:fff8::/46", "ffff:ffff:fffc::/47", "ffff:ffff:fffe::/48", "ffff:ffff:ffff::/49", "ffff:ffff:ffff:8000::/50", "ffff:ffff:ffff:c000::/51", "ffff:ffff:ffff:e000::/52", "ffff:ffff:ffff:f000::/53", "ffff:ffff:ffff:f800::/54", "ffff:ffff:ffff:fc00::/55", "ffff:ffff:ffff:fe00::/56", "ffff:ffff:ffff:ff00::/57", "ffff:ffff:ffff:ff80::/58", "ffff:ffff:ffff:ffc0::/59", "ffff:ffff:ffff:ffe0::/60", "ffff:ffff:ffff:fff0::/61", "ffff:ffff:ffff:fff8::/62", "ffff:ffff:ffff:fffc::/63", "ffff:ffff:ffff:fffe::/64", "ffff:ffff:ffff:ffff::/65", "ffff:ffff:ffff:ffff:8000::/66", "ffff:ffff:ffff:ffff:c000::/67", "ffff:ffff:ffff:ffff:e000::/68", "ffff:ffff:ffff:ffff:f000::/69", "ffff:ffff:ffff:ffff:f800::/70", "ffff:ffff:ffff:ffff:fc00::/71", "ffff:ffff:ffff:ffff:fe00::/72", "ffff:ffff:ffff:ffff:ff00::/73", "ffff:ffff:ffff:ffff:ff80::/74", "ffff:ffff:ffff:ffff:ffc0::/75", "ffff:ffff:ffff:ffff:ffe0::/76", "ffff:ffff:ffff:ffff:fff0::/77", "ffff:ffff:ffff:ffff:fff8::/78", "ffff:ffff:ffff:ffff:fffc::/79", "ffff:ffff:ffff:ffff:fffe::/80", "ffff:ffff:ffff:ffff:ffff::/81", "ffff:ffff:ffff:ffff:ffff:8000::/82", "ffff:ffff:ffff:ffff:ffff:c000::/83", "ffff:ffff:ffff:ffff:ffff:e000::/84", "ffff:ffff:ffff:ffff:ffff:f000::/85", "ffff:ffff:ffff:ffff:ffff:f800::/86", "ffff:ffff:ffff:ffff:ffff:fc00::/87", "ffff:ffff:ffff:ffff:ffff:fe00::/88", "ffff:ffff:ffff:ffff:ffff:ff00::/89", "ffff:ffff:ffff:ffff:ffff:ff80::/90", "ffff:ffff:ffff:ffff:ffff:ffc0::/91", "ffff:ffff:ffff:ffff:ffff:ffe0::/92", "ffff:ffff:ffff:ffff:ffff:fff0::/93", "ffff:ffff:ffff:ffff:ffff:fff8::/94", "ffff:ffff:ffff:ffff:ffff:fffc::/95", "ffff:ffff:ffff:ffff:ffff:fffe::/96", "ffff:ffff:ffff:ffff:ffff:ffff::/97", "ffff:ffff:ffff:ffff:ffff:ffff:8000:0/98", "ffff:ffff:ffff:ffff:ffff:ffff:c000:0/99", "ffff:ffff:ffff:ffff:ffff:ffff:e000:0/100", "ffff:ffff:ffff:ffff:ffff:ffff:f000:0/101", "ffff:ffff:ffff:ffff:ffff:ffff:f800:0/102", "ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/103", "ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/104", "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/105", "ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/106", "ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107", "ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/108", "ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/109", "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/110", "ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/111", "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/113", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/114", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/115", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/116", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/117", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/118", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/119", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/120", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/121", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/122", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/123", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/124", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/125", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/126", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/127", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/128"}.Select(s => Subnet.Parse(s)), - IPAddress.Parse("::1"), IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe") - }; + { + new[] + { + "::1/128", + "::2/127", + "::4/126", + "::8/125", + "::10/124", + "::20/123", + "::40/122", + "::80/121", + "::100/120", + "::200/119", + "::400/118", + "::800/117", + "::1000/116", + "::2000/115", + "::4000/114", + "::8000/113", + "::0.1.0.0/112", + "::0.2.0.0/111", + "::0.4.0.0/110", + "::0.8.0.0/109", + "::0.16.0.0/108", + "::0.32.0.0/107", + "::0.64.0.0/106", + "::0.128.0.0/105", + "::1.0.0.0/104", + "::2.0.0.0/103", + "::4.0.0.0/102", + "::8.0.0.0/101", + "::16.0.0.0/100", + "::32.0.0.0/99", + "::64.0.0.0/98", + "::128.0.0.0/97", + "::1:0:0/96", + "::2:0:0/95", + "::4:0:0/94", + "::8:0:0/93", + "::10:0:0/92", + "::20:0:0/91", + "::40:0:0/90", + "::80:0:0/89", + "::100:0:0/88", + "::200:0:0/87", + "::400:0:0/86", + "::800:0:0/85", + "::1000:0:0/84", + "::2000:0:0/83", + "::4000:0:0/82", + "::8000:0:0/81", + "::1:0:0:0/80", + "::2:0:0:0/79", + "::4:0:0:0/78", + "::8:0:0:0/77", + "::10:0:0:0/76", + "::20:0:0:0/75", + "::40:0:0:0/74", + "::80:0:0:0/73", + "::100:0:0:0/72", + "::200:0:0:0/71", + "::400:0:0:0/70", + "::800:0:0:0/69", + "::1000:0:0:0/68", + "::2000:0:0:0/67", + "::4000:0:0:0/66", + "::8000:0:0:0/65", + "0:0:0:1::/64", + "0:0:0:2::/63", + "0:0:0:4::/62", + "0:0:0:8::/61", + "0:0:0:10::/60", + "0:0:0:20::/59", + "0:0:0:40::/58", + "0:0:0:80::/57", + "0:0:0:100::/56", + "0:0:0:200::/55", + "0:0:0:400::/54", + "0:0:0:800::/53", + "0:0:0:1000::/52", + "0:0:0:2000::/51", + "0:0:0:4000::/50", + "0:0:0:8000::/49", + "0:0:1::/48", + "0:0:2::/47", + "0:0:4::/46", + "0:0:8::/45", + "0:0:10::/44", + "0:0:20::/43", + "0:0:40::/42", + "0:0:80::/41", + "0:0:100::/40", + "0:0:200::/39", + "0:0:400::/38", + "0:0:800::/37", + "0:0:1000::/36", + "0:0:2000::/35", + "0:0:4000::/34", + "0:0:8000::/33", + "0:1::/32", + "0:2::/31", + "0:4::/30", + "0:8::/29", + "0:10::/28", + "0:20::/27", + "0:40::/26", + "0:80::/25", + "0:100::/24", + "0:200::/23", + "0:400::/22", + "0:800::/21", + "0:1000::/20", + "0:2000::/19", + "0:4000::/18", + "0:8000::/17", + "1::/16", + "2::/15", + "4::/14", + "8::/13", + "10::/12", + "20::/11", + "40::/10", + "80::/9", + "100::/8", + "200::/7", + "400::/6", + "800::/5", + "1000::/4", + "2000::/3", + "4000::/2", + "8000::/2", + "c000::/3", + "e000::/4", + "f000::/5", + "f800::/6", + "fc00::/7", + "fe00::/8", + "ff00::/9", + "ff80::/10", + "ffc0::/11", + "ffe0::/12", + "fff0::/13", + "fff8::/14", + "fffc::/15", + "fffe::/16", + "ffff::/17", + "ffff:8000::/18", + "ffff:c000::/19", + "ffff:e000::/20", + "ffff:f000::/21", + "ffff:f800::/22", + "ffff:fc00::/23", + "ffff:fe00::/24", + "ffff:ff00::/25", + "ffff:ff80::/26", + "ffff:ffc0::/27", + "ffff:ffe0::/28", + "ffff:fff0::/29", + "ffff:fff8::/30", + "ffff:fffc::/31", + "ffff:fffe::/32", + "ffff:ffff::/33", + "ffff:ffff:8000::/34", + "ffff:ffff:c000::/35", + "ffff:ffff:e000::/36", + "ffff:ffff:f000::/37", + "ffff:ffff:f800::/38", + "ffff:ffff:fc00::/39", + "ffff:ffff:fe00::/40", + "ffff:ffff:ff00::/41", + "ffff:ffff:ff80::/42", + "ffff:ffff:ffc0::/43", + "ffff:ffff:ffe0::/44", + "ffff:ffff:fff0::/45", + "ffff:ffff:fff8::/46", + "ffff:ffff:fffc::/47", + "ffff:ffff:fffe::/48", + "ffff:ffff:ffff::/49", + "ffff:ffff:ffff:8000::/50", + "ffff:ffff:ffff:c000::/51", + "ffff:ffff:ffff:e000::/52", + "ffff:ffff:ffff:f000::/53", + "ffff:ffff:ffff:f800::/54", + "ffff:ffff:ffff:fc00::/55", + "ffff:ffff:ffff:fe00::/56", + "ffff:ffff:ffff:ff00::/57", + "ffff:ffff:ffff:ff80::/58", + "ffff:ffff:ffff:ffc0::/59", + "ffff:ffff:ffff:ffe0::/60", + "ffff:ffff:ffff:fff0::/61", + "ffff:ffff:ffff:fff8::/62", + "ffff:ffff:ffff:fffc::/63", + "ffff:ffff:ffff:fffe::/64", + "ffff:ffff:ffff:ffff::/65", + "ffff:ffff:ffff:ffff:8000::/66", + "ffff:ffff:ffff:ffff:c000::/67", + "ffff:ffff:ffff:ffff:e000::/68", + "ffff:ffff:ffff:ffff:f000::/69", + "ffff:ffff:ffff:ffff:f800::/70", + "ffff:ffff:ffff:ffff:fc00::/71", + "ffff:ffff:ffff:ffff:fe00::/72", + "ffff:ffff:ffff:ffff:ff00::/73", + "ffff:ffff:ffff:ffff:ff80::/74", + "ffff:ffff:ffff:ffff:ffc0::/75", + "ffff:ffff:ffff:ffff:ffe0::/76", + "ffff:ffff:ffff:ffff:fff0::/77", + "ffff:ffff:ffff:ffff:fff8::/78", + "ffff:ffff:ffff:ffff:fffc::/79", + "ffff:ffff:ffff:ffff:fffe::/80", + "ffff:ffff:ffff:ffff:ffff::/81", + "ffff:ffff:ffff:ffff:ffff:8000::/82", + "ffff:ffff:ffff:ffff:ffff:c000::/83", + "ffff:ffff:ffff:ffff:ffff:e000::/84", + "ffff:ffff:ffff:ffff:ffff:f000::/85", + "ffff:ffff:ffff:ffff:ffff:f800::/86", + "ffff:ffff:ffff:ffff:ffff:fc00::/87", + "ffff:ffff:ffff:ffff:ffff:fe00::/88", + "ffff:ffff:ffff:ffff:ffff:ff00::/89", + "ffff:ffff:ffff:ffff:ffff:ff80::/90", + "ffff:ffff:ffff:ffff:ffff:ffc0::/91", + "ffff:ffff:ffff:ffff:ffff:ffe0::/92", + "ffff:ffff:ffff:ffff:ffff:fff0::/93", + "ffff:ffff:ffff:ffff:ffff:fff8::/94", + "ffff:ffff:ffff:ffff:ffff:fffc::/95", + "ffff:ffff:ffff:ffff:ffff:fffe::/96", + "ffff:ffff:ffff:ffff:ffff:ffff::/97", + "ffff:ffff:ffff:ffff:ffff:ffff:8000:0/98", + "ffff:ffff:ffff:ffff:ffff:ffff:c000:0/99", + "ffff:ffff:ffff:ffff:ffff:ffff:e000:0/100", + "ffff:ffff:ffff:ffff:ffff:ffff:f000:0/101", + "ffff:ffff:ffff:ffff:ffff:ffff:f800:0/102", + "ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/103", + "ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/104", + "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/105", + "ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/106", + "ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/107", + "ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/108", + "ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/109", + "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/110", + "ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/111", + "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/112", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/113", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/114", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/115", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/116", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/117", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/118", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/119", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/120", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/121", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/122", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/123", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/124", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/125", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/126", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/127", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/128", + }.Select(s => Subnet.Parse(s)), + IPAddress.Parse("::1"), + IPAddress.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe"), + }; } [Theory] [MemberData(nameof(FewestConsecutiveSubnetsFor_Test_Values))] - public void FewestConsecutiveSubnetsFor_Test(IEnumerable expected, - IPAddress left, - IPAddress right) + public void FewestConsecutiveSubnetsFor_Test(IEnumerable expected, IPAddress left, IPAddress right) { // Arrange // Act - var result = SubnetUtilities.FewestConsecutiveSubnetsFor(left, right) - .ToList(); + var result = SubnetUtilities.FewestConsecutiveSubnetsFor(left, right).ToList(); // Assert Assert.NotNull(result); @@ -170,14 +559,16 @@ public void FewestConsecutiveSubnetsFor_Test(IEnumerable expected, public static IEnumerable FewestConsecutiveSubnetsFor_MissMatchAddressFamilies_ThrowsInvalidOperationException_Test_Values() { - yield return new object[] {IPAddress.Any, IPAddress.IPv6Any}; - yield return new object[] {IPAddress.IPv6Any, IPAddress.Any}; + yield return new object[] { IPAddress.Any, IPAddress.IPv6Any }; + yield return new object[] { IPAddress.IPv6Any, IPAddress.Any }; } [Theory] [MemberData(nameof(FewestConsecutiveSubnetsFor_MissMatchAddressFamilies_ThrowsInvalidOperationException_Test_Values))] - public void FewestConsecutiveSubnetsFor_MissMatchAddressFamilies_ThrowsInvalidOperationException_Test(IPAddress alpha, - IPAddress beta) + public void FewestConsecutiveSubnetsFor_MissMatchAddressFamilies_ThrowsInvalidOperationException_Test( + IPAddress alpha, + IPAddress beta + ) { // Act // Assert @@ -186,15 +577,14 @@ public void FewestConsecutiveSubnetsFor_MissMatchAddressFamilies_ThrowsInvalidOp public static IEnumerable FewestConsecutiveSubnetsFor_Input_Null_ThrowsArgumentNullException_Test_Values() { - yield return new object[] {null, null}; - yield return new object[] {IPAddress.Any, null}; - yield return new object[] {null, IPAddress.Any}; + yield return new object[] { null, null }; + yield return new object[] { IPAddress.Any, null }; + yield return new object[] { null, IPAddress.Any }; } [Theory] [MemberData(nameof(FewestConsecutiveSubnetsFor_Input_Null_ThrowsArgumentNullException_Test_Values))] - public void FewestConsecutiveSubnetsFor_Input_Null_ThrowsArgumentNullException_Test(IPAddress alpha, - IPAddress beta) + public void FewestConsecutiveSubnetsFor_Input_Null_ThrowsArgumentNullException_Test(IPAddress alpha, IPAddress beta) { // Act // Assert @@ -210,12 +600,12 @@ public void LargestSubnet_Ambiguous_ReturnsOneOfLargest_Test() { // Arrange var subnets = new[] - { - new Subnet(IPAddress.Any, 24), - new Subnet(IPAddress.Any, 16), - new Subnet(IPAddress.Any, 16), - new Subnet(IPAddress.Any, 32) - }; + { + new Subnet(IPAddress.Any, 24), + new Subnet(IPAddress.Any, 16), + new Subnet(IPAddress.Any, 16), + new Subnet(IPAddress.Any, 32), + }; // Act var result = SubnetUtilities.LargestSubnet(subnets); @@ -254,10 +644,7 @@ public void LargestSubnet_Single_ReturnSingle_Test() // Arrange var expected = new Subnet(IPAddress.Any, 16); - var subnets = new[] - { - expected - }; + var subnets = new[] { expected }; // Act @@ -273,12 +660,7 @@ public void LargestSubnet_ReturnsLargest_Test() // Arrange var expected = new Subnet(IPAddress.Any, 16); - var subnets = new[] - { - expected, - new Subnet(IPAddress.Any, 24), - new Subnet(IPAddress.Any, 32) - }; + var subnets = new[] { expected, new Subnet(IPAddress.Any, 24), new Subnet(IPAddress.Any, 32) }; // Act @@ -297,12 +679,12 @@ public void SmallestSubnet_Ambiguous_ReturnsOneOfSmallest_Test() { // Arrange var subnets = new[] - { - new Subnet(IPAddress.Any, 24), - new Subnet(IPAddress.Any, 32), - new Subnet(IPAddress.Any, 32), - new Subnet(IPAddress.Any, 16) - }; + { + new Subnet(IPAddress.Any, 24), + new Subnet(IPAddress.Any, 32), + new Subnet(IPAddress.Any, 32), + new Subnet(IPAddress.Any, 16), + }; // Act var result = SubnetUtilities.SmallestSubnet(subnets); @@ -341,10 +723,7 @@ public void SmallestSubnet_Single_ReturnSingle_Test() // Arrange var expected = new Subnet(IPAddress.Any, 16); - var subnets = new[] - { - expected - }; + var subnets = new[] { expected }; // Act var result = SubnetUtilities.SmallestSubnet(subnets); @@ -359,12 +738,7 @@ public void SmallestSubnet_ReturnsSmallestSubnet_Test() // Arrange var expected = new Subnet(IPAddress.Any, 32); - var subnets = new[] - { - expected, - new Subnet(IPAddress.Any, 24), - new Subnet(IPAddress.Any, 16) - }; + var subnets = new[] { expected, new Subnet(IPAddress.Any, 24), new Subnet(IPAddress.Any, 16) }; // Act diff --git a/src/Arcus.Tests/XunitSerializers/IPAddressRangeXunitSerializer.cs b/src/Arcus.Tests/XunitSerializers/IPAddressRangeXunitSerializer.cs new file mode 100644 index 0000000..e02871e --- /dev/null +++ b/src/Arcus.Tests/XunitSerializers/IPAddressRangeXunitSerializer.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using System.Net; +using Xunit.Sdk; + +namespace Arcus.Tests.XunitSerializers +{ + /// + /// for + /// + public class IPAddressRangeXunitSerializer : IXunitSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// from xUnit Serialization support in v3 + public IPAddressRangeXunitSerializer() { } + + /// + public bool IsSerializable(Type type, object value, out string failureReason) + { + if (type == typeof(IPAddressRange) && value is IPAddressRange) + { + failureReason = null; + return true; + } + + failureReason = $"Type {type.FullName} is not supported by {nameof(IPAddressRangeXunitSerializer)}."; + return false; + } + + /// + public string Serialize(object value) + { + if (value is IPAddressRange ipAddressRange) + { + return ipAddressRange.ToString("G", null); + } + + throw new InvalidOperationException( + $"Invalid type for serialization: {value.GetType().FullName} is not supported by {nameof(IPAddressRangeXunitSerializer)}." + ); + } + + /// + public object Deserialize(Type type, string serializedValue) + { + if (type == typeof(IPAddressRange)) + { + var substrings = serializedValue.Split('-').Select(s => s.Trim()).ToList(); + + if (substrings.Count > 2) + { + throw new InvalidOperationException("Could not parse serialized IP Address range \"{serializedValue}\""); + } + + return new IPAddressRange(IPAddress.Parse(substrings[0]), IPAddress.Parse(substrings[1])); + } + + throw new ArgumentException( + $"Invalid type for deserialization: {type.FullName} is not supported by {nameof(IPAddressRangeXunitSerializer)}" + ); + } + } +} diff --git a/src/Arcus.Tests/XunitSerializers/SubnetXunitSerializer.cs b/src/Arcus.Tests/XunitSerializers/SubnetXunitSerializer.cs new file mode 100644 index 0000000..cf08228 --- /dev/null +++ b/src/Arcus.Tests/XunitSerializers/SubnetXunitSerializer.cs @@ -0,0 +1,56 @@ +using System; +using Xunit.Sdk; + +namespace Arcus.Tests.XunitSerializers +{ + /// + /// for + /// + public class SubnetXunitSerializer : IXunitSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// from xUnit Serialization support in v3 + public SubnetXunitSerializer() { } + + /// + public bool IsSerializable(Type type, object value, out string failureReason) + { + if (type == typeof(Subnet) && value is Subnet) + { + failureReason = null; + return true; + } + + failureReason = $"Type {type.FullName} is not supported by {nameof(SubnetXunitSerializer)}."; + return false; + } + + /// + public string Serialize(object value) + { + if (value is Subnet subnet) + { + return subnet.ToString("f", null); + } + + throw new InvalidOperationException( + $"Invalid type for serialization: {value.GetType().FullName} is not supported by {nameof(SubnetXunitSerializer)}." + ); + } + + /// + public object Deserialize(Type type, string serializedValue) + { + if (type == typeof(Subnet)) + { + return Subnet.Parse(serializedValue); + } + + throw new ArgumentException( + $"Invalid type for deserialization: {type.FullName} is not supported by {nameof(SubnetXunitSerializer)}" + ); + } + } +} diff --git a/src/Arcus.Tests/packages.lock.json b/src/Arcus.Tests/packages.lock.json new file mode 100644 index 0000000..635b62b --- /dev/null +++ b/src/Arcus.Tests/packages.lock.json @@ -0,0 +1,772 @@ +{ + "version": 1, + "dependencies": { + ".NETFramework,Version=v4.8": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.13.0, )", + "resolved": "17.13.0", + "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", + "dependencies": { + "Microsoft.CodeCoverage": "17.13.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "NSubstitute": { + "type": "Direct", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", + "dependencies": { + "Castle.Core": "5.1.1", + "System.Threading.Tasks.Extensions": "4.3.0" + } + }, + "NSubstitute.Analyzers.CSharp": { + "type": "Direct", + "requested": "[1.0.17, )", + "resolved": "1.0.17", + "contentHash": "Pwz0MD7CAM/G/fvJjM3ceOfI+S0IgjanHcK7evwyrW9qAWUG8fgiEXYfSX1/s3h2JUNDOw6ik0G8zp+RT61Y1g==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.2, )", + "resolved": "3.0.2", + "contentHash": "oXbusR6iPq0xlqoikjdLvzh+wQDkMv9If58myz9MEzldS4nIcp442Btgs2sWbYWV+caEluMe2pQCZ0hUZgPiow==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.12.0" + } + }, + "xunit.v3": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "gy0M8kElQONiemRfZtvFzJoM9eVhneENz+8N1WdW0xfMgdMD9IuM1wwaQgj2zfkMgMFixMk0kxSvimyYNoBAKw==", + "dependencies": { + "xunit.analyzers": "1.20.0", + "xunit.v3.assert": "[2.0.0]", + "xunit.v3.core": "[2.0.0]" + } + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==" + }, + "Gulliver": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==", + "dependencies": { + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Bcl.HashCode": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.12.0", + "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==", + "dependencies": { + "System.Collections.Immutable": "1.5.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.20.0", + "contentHash": "HElev2E9vFbPxwKRQtpCSSzLOu8M/N9EWBCB37v7SRx6z4Lbj19FxfLEig3v9jiI6s4b0l2uena91nEsTWl9jA==" + }, + "xunit.v3.assert": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "acgfaclCB+FX5s4tQLTmTW2mc5tsOMtIAKizyCnZyxjbJpc10vGNaMvwsE0Pw7wGplet3PJfXffsMO4pRDJIfA==", + "dependencies": { + "System.Collections.Immutable": "6.0.0", + "System.Memory": "4.5.5" + } + }, + "xunit.v3.common": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "0BG62sA0xS5j4WSGqLzv5HXBM41sw4opEe3DhAN0wa7l1N2lNAyuV6Vq5mJT/tIYmJFeICD7lv0c39AgBqiQ8Q==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "xunit.v3.core": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "Nwb8qyETji/1PP7jmgOy6u6RJC7h4jo5UmxLBswqrixNeONtB2qjHGyOl5/6IuzB4GsJpRA8Mdz2E+LxRqAukg==", + "dependencies": { + "Microsoft.Testing.Platform.MSBuild": "1.6.2", + "xunit.v3.extensibility.core": "[2.0.0]", + "xunit.v3.runner.inproc.console": "[2.0.0]" + } + }, + "xunit.v3.extensibility.core": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "78Ep8QNHHevCZ2ADr3wwDrSQUeq+xNmzdek4ssGLf+nEoK9KntWlJWQiO4BOKoc75OHUKbeG7JB2h3WW80NSDg==", + "dependencies": { + "xunit.v3.common": "[2.0.0]" + } + }, + "xunit.v3.runner.common": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "XHvSqGp2h1w81eKo53fQWFEGPIiDiZYRtTmFD9revpfc0xLmrDJXkQQVTqY25WD+gi2Ulg3UjCfazj1WBmRadQ==", + "dependencies": { + "xunit.v3.common": "[2.0.0]" + } + }, + "xunit.v3.runner.inproc.console": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "2WZzkcFJ1RgbjX0wXP56rcS4V2RPtGZuGToBOyxAZMcJ2f9T7X7mD5DK5ztyHIsmHz+IhdwklSbU1U3NmakGow==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", + "Microsoft.Testing.Platform": "1.6.2", + "xunit.v3.extensibility.core": "[2.0.0]", + "xunit.v3.runner.common": "[2.0.0]" + } + }, + "arcus": { + "type": "Project", + "dependencies": { + "Gulliver": "[2.0.0, )", + "Microsoft.Bcl.HashCode": "[6.0.0, )" + } + } + }, + ".NETFramework,Version=v4.8/win-x86": {}, + "net8.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.13.0, )", + "resolved": "17.13.0", + "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", + "dependencies": { + "Microsoft.CodeCoverage": "17.13.0", + "Microsoft.TestPlatform.TestHost": "17.13.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "NSubstitute": { + "type": "Direct", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", + "dependencies": { + "Castle.Core": "5.1.1" + } + }, + "NSubstitute.Analyzers.CSharp": { + "type": "Direct", + "requested": "[1.0.17, )", + "resolved": "1.0.17", + "contentHash": "Pwz0MD7CAM/G/fvJjM3ceOfI+S0IgjanHcK7evwyrW9qAWUG8fgiEXYfSX1/s3h2JUNDOw6ik0G8zp+RT61Y1g==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.2, )", + "resolved": "3.0.2", + "contentHash": "oXbusR6iPq0xlqoikjdLvzh+wQDkMv9If58myz9MEzldS4nIcp442Btgs2sWbYWV+caEluMe2pQCZ0hUZgPiow==" + }, + "xunit.v3": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "gy0M8kElQONiemRfZtvFzJoM9eVhneENz+8N1WdW0xfMgdMD9IuM1wwaQgj2zfkMgMFixMk0kxSvimyYNoBAKw==", + "dependencies": { + "xunit.analyzers": "1.20.0", + "xunit.v3.assert": "[2.0.0]", + "xunit.v3.core": "[2.0.0]" + } + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + } + }, + "Gulliver": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.13.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.20.0", + "contentHash": "HElev2E9vFbPxwKRQtpCSSzLOu8M/N9EWBCB37v7SRx6z4Lbj19FxfLEig3v9jiI6s4b0l2uena91nEsTWl9jA==" + }, + "xunit.v3.assert": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "acgfaclCB+FX5s4tQLTmTW2mc5tsOMtIAKizyCnZyxjbJpc10vGNaMvwsE0Pw7wGplet3PJfXffsMO4pRDJIfA==", + "dependencies": { + "System.Collections.Immutable": "6.0.0", + "System.Memory": "4.5.5" + } + }, + "xunit.v3.common": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "0BG62sA0xS5j4WSGqLzv5HXBM41sw4opEe3DhAN0wa7l1N2lNAyuV6Vq5mJT/tIYmJFeICD7lv0c39AgBqiQ8Q==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "xunit.v3.core": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "Nwb8qyETji/1PP7jmgOy6u6RJC7h4jo5UmxLBswqrixNeONtB2qjHGyOl5/6IuzB4GsJpRA8Mdz2E+LxRqAukg==", + "dependencies": { + "Microsoft.Testing.Platform.MSBuild": "1.6.2", + "xunit.v3.extensibility.core": "[2.0.0]", + "xunit.v3.runner.inproc.console": "[2.0.0]" + } + }, + "xunit.v3.extensibility.core": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "78Ep8QNHHevCZ2ADr3wwDrSQUeq+xNmzdek4ssGLf+nEoK9KntWlJWQiO4BOKoc75OHUKbeG7JB2h3WW80NSDg==", + "dependencies": { + "xunit.v3.common": "[2.0.0]" + } + }, + "xunit.v3.runner.common": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "XHvSqGp2h1w81eKo53fQWFEGPIiDiZYRtTmFD9revpfc0xLmrDJXkQQVTqY25WD+gi2Ulg3UjCfazj1WBmRadQ==", + "dependencies": { + "xunit.v3.common": "[2.0.0]" + } + }, + "xunit.v3.runner.inproc.console": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "2WZzkcFJ1RgbjX0wXP56rcS4V2RPtGZuGToBOyxAZMcJ2f9T7X7mD5DK5ztyHIsmHz+IhdwklSbU1U3NmakGow==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", + "Microsoft.Testing.Platform": "1.6.2", + "xunit.v3.extensibility.core": "[2.0.0]", + "xunit.v3.runner.common": "[2.0.0]" + } + }, + "arcus": { + "type": "Project", + "dependencies": { + "Gulliver": "[2.0.0, )" + } + } + }, + "net8.0/win-x86": { + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" + } + }, + "net9.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.13.0, )", + "resolved": "17.13.0", + "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", + "dependencies": { + "Microsoft.CodeCoverage": "17.13.0", + "Microsoft.TestPlatform.TestHost": "17.13.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "NSubstitute": { + "type": "Direct", + "requested": "[5.3.0, )", + "resolved": "5.3.0", + "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", + "dependencies": { + "Castle.Core": "5.1.1" + } + }, + "NSubstitute.Analyzers.CSharp": { + "type": "Direct", + "requested": "[1.0.17, )", + "resolved": "1.0.17", + "contentHash": "Pwz0MD7CAM/G/fvJjM3ceOfI+S0IgjanHcK7evwyrW9qAWUG8fgiEXYfSX1/s3h2JUNDOw6ik0G8zp+RT61Y1g==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.2, )", + "resolved": "3.0.2", + "contentHash": "oXbusR6iPq0xlqoikjdLvzh+wQDkMv9If58myz9MEzldS4nIcp442Btgs2sWbYWV+caEluMe2pQCZ0hUZgPiow==" + }, + "xunit.v3": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "gy0M8kElQONiemRfZtvFzJoM9eVhneENz+8N1WdW0xfMgdMD9IuM1wwaQgj2zfkMgMFixMk0kxSvimyYNoBAKw==", + "dependencies": { + "xunit.analyzers": "1.20.0", + "xunit.v3.assert": "[2.0.0]", + "xunit.v3.core": "[2.0.0]" + } + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + } + }, + "Gulliver": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.13.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.20.0", + "contentHash": "HElev2E9vFbPxwKRQtpCSSzLOu8M/N9EWBCB37v7SRx6z4Lbj19FxfLEig3v9jiI6s4b0l2uena91nEsTWl9jA==" + }, + "xunit.v3.assert": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "acgfaclCB+FX5s4tQLTmTW2mc5tsOMtIAKizyCnZyxjbJpc10vGNaMvwsE0Pw7wGplet3PJfXffsMO4pRDJIfA==", + "dependencies": { + "System.Collections.Immutable": "6.0.0", + "System.Memory": "4.5.5" + } + }, + "xunit.v3.common": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "0BG62sA0xS5j4WSGqLzv5HXBM41sw4opEe3DhAN0wa7l1N2lNAyuV6Vq5mJT/tIYmJFeICD7lv0c39AgBqiQ8Q==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "xunit.v3.core": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "Nwb8qyETji/1PP7jmgOy6u6RJC7h4jo5UmxLBswqrixNeONtB2qjHGyOl5/6IuzB4GsJpRA8Mdz2E+LxRqAukg==", + "dependencies": { + "Microsoft.Testing.Platform.MSBuild": "1.6.2", + "xunit.v3.extensibility.core": "[2.0.0]", + "xunit.v3.runner.inproc.console": "[2.0.0]" + } + }, + "xunit.v3.extensibility.core": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "78Ep8QNHHevCZ2ADr3wwDrSQUeq+xNmzdek4ssGLf+nEoK9KntWlJWQiO4BOKoc75OHUKbeG7JB2h3WW80NSDg==", + "dependencies": { + "xunit.v3.common": "[2.0.0]" + } + }, + "xunit.v3.runner.common": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "XHvSqGp2h1w81eKo53fQWFEGPIiDiZYRtTmFD9revpfc0xLmrDJXkQQVTqY25WD+gi2Ulg3UjCfazj1WBmRadQ==", + "dependencies": { + "xunit.v3.common": "[2.0.0]" + } + }, + "xunit.v3.runner.inproc.console": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "2WZzkcFJ1RgbjX0wXP56rcS4V2RPtGZuGToBOyxAZMcJ2f9T7X7mD5DK5ztyHIsmHz+IhdwklSbU1U3NmakGow==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", + "Microsoft.Testing.Platform": "1.6.2", + "xunit.v3.extensibility.core": "[2.0.0]", + "xunit.v3.runner.common": "[2.0.0]" + } + }, + "arcus": { + "type": "Project", + "dependencies": { + "Gulliver": "[2.0.0, )" + } + } + }, + "net9.0/win-x86": { + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" + } + } + } +} \ No newline at end of file diff --git a/src/Arcus.sln b/src/Arcus.sln index 9783f7c..586a232 100644 --- a/src/Arcus.sln +++ b/src/Arcus.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29409.12 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35707.178 d17.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus", "Arcus\Arcus.csproj", "{BF292912-886E-470A-8FD0-2521C5EE586C}" EndProject @@ -11,18 +11,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeQuality", "CodeQuality", "{4523123E-8A4A-443F-BC76-42FB24D96165}" ProjectSection(SolutionItems) = preProject - analyzers.ruleset = analyzers.ruleset - analyzers.tests.ruleset = analyzers.tests.ruleset - Arcus.sln.DotSettings = Arcus.sln.DotSettings + .csharpierrc.json = .csharpierrc.json + .editorconfig = .editorconfig + ..\.editorconfig = ..\.editorconfig + ..\dictionary.dic = ..\dictionary.dic stylecop.json = stylecop.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{19C5939E-F1E4-4747-AE48-F72BB4C891C4}" ProjectSection(SolutionItems) = preProject ..\acknowledgements.md = ..\acknowledgements.md + ..\CODE_OF_CONDUCT.md = ..\CODE_OF_CONDUCT.md ..\HUMANS.md = ..\HUMANS.md ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md + ..\SECURITY.md = ..\SECURITY.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git", "git", "{123A833E-759F-44C8-8B10-C453335445D9}" diff --git a/src/Arcus.sln.DotSettings b/src/Arcus.sln.DotSettings deleted file mode 100644 index f234647..0000000 --- a/src/Arcus.sln.DotSettings +++ /dev/null @@ -1,20 +0,0 @@ - - True - ..\Templates.DotSettings - True - ..\CodeConvention.DotSettings - True - 2 - True - 3 - True - True - True - True - True - True - True - True - True - True - \ No newline at end of file diff --git a/src/Arcus/AbstractIPAddressRange.cs b/src/Arcus/AbstractIPAddressRange.cs index 99f066f..b9d99c9 100644 --- a/src/Arcus/AbstractIPAddressRange.cs +++ b/src/Arcus/AbstractIPAddressRange.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; @@ -9,19 +9,20 @@ using Arcus.Math; using Arcus.Utilities; using Gulliver; -using JetBrains.Annotations; namespace Arcus { /// /// An implementation of built to work with IPv4 and IPv6 /// - [PublicAPI] public abstract class AbstractIPAddressRange : IIPAddressRange { /// - /// if the subnet describes a single ip address + /// Gets a value indicating whether if the subnet describes a single ip address /// + /// + /// if the subnet describes a single ip address + /// public bool IsSingleIP => this.Length == 1; /// @@ -47,8 +48,7 @@ public abstract class AbstractIPAddressRange : IIPAddressRange #region Deconstructors /// - public void Deconstruct(out IPAddress head, - out IPAddress tail) + public void Deconstruct(out IPAddress head, out IPAddress tail) { head = this.Head; tail = this.Tail; @@ -63,37 +63,39 @@ public void Deconstruct(out IPAddress head, /// /// AddressTuple for moving around a pair of objects as a unit /// - protected struct AddressTuple : IEquatable + protected readonly struct AddressTuple : IEquatable { /// /// Initializes a new instance of the struct. /// /// the head address /// the tail address - public AddressTuple([NotNull] IPAddress head, - [NotNull] IPAddress tail) + public AddressTuple(IPAddress head, IPAddress tail) { this.Head = head ?? throw new ArgumentNullException(nameof(head)); this.Tail = tail ?? throw new ArgumentNullException(nameof(tail)); } /// - /// Head + /// Gets head /// - [NotNull] + /// + /// Head + /// public IPAddress Head { get; } /// - /// Tail + /// Gets tail /// - [NotNull] + /// + /// Tail + /// public IPAddress Tail { get; } /// public bool Equals(AddressTuple other) { - return this.Head.Equals(other.Head) - && this.Tail.Equals(other.Tail); + return this.Head.Equals(other.Head) && this.Tail.Equals(other.Tail); } /// @@ -103,13 +105,7 @@ public override bool Equals(object obj) } /// - public override int GetHashCode() - { - unchecked - { - return (this.Head.GetHashCode() * 397) ^ this.Tail.GetHashCode(); - } - } + public override int GetHashCode() => HashCode.Combine(Head, Tail); /// /// Equals operation @@ -117,8 +113,7 @@ public override int GetHashCode() /// left operand /// right operand /// if and are equal - public static bool operator ==(AddressTuple left, - AddressTuple right) + public static bool operator ==(AddressTuple left, AddressTuple right) { return left.Equals(right); } @@ -129,8 +124,7 @@ public override int GetHashCode() /// left operand /// right operand /// if and are equal - public static bool operator !=(AddressTuple left, - AddressTuple right) + public static bool operator !=(AddressTuple left, AddressTuple right) { return !(left == right); } @@ -145,8 +139,7 @@ public override int GetHashCode() /// /// the range head (lowest valued ) /// the range tail (highest valued ) - protected AbstractIPAddressRange([NotNull] IPAddress head, - [NotNull] IPAddress tail) + protected AbstractIPAddressRange(IPAddress head, IPAddress tail) { #region defense @@ -162,12 +155,18 @@ protected AbstractIPAddressRange([NotNull] IPAddress head, if (!IPAddressUtilities.ValidAddressFamilies.Contains(head.AddressFamily)) { - throw new ArgumentException($"{nameof(head)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(head)); + throw new ArgumentException( + $"{nameof(head)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(head) + ); } if (!IPAddressUtilities.ValidAddressFamilies.Contains(tail.AddressFamily)) { - throw new ArgumentException($"{nameof(tail)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(tail)); + throw new ArgumentException( + $"{nameof(tail)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(tail) + ); } if (head.AddressFamily != tail.AddressFamily) @@ -198,7 +197,7 @@ BigInteger CalculateLength() unsignedLittleEndianBytes[differenceBytesLength - 1 - i] = differenceBytes[i]; } - return new BigInteger(unsignedLittleEndianBytes) + 1; // a rare but valid use of BigInteger; + return new BigInteger(unsignedLittleEndianBytes) + 1; // a rare but valid use of BigInteger } } @@ -223,7 +222,7 @@ public bool TryGetLength(out int length) if (actualLength <= int.MaxValue) { - length = (int) actualLength; + length = (int)actualLength; return true; } @@ -238,7 +237,7 @@ public bool TryGetLength(out long length) if (actualLength <= long.MaxValue) { - length = (long) actualLength; + length = (long)actualLength; return true; } @@ -254,16 +253,12 @@ public bool TryGetLength(out long length) public IEnumerator GetEnumerator() { // determine maximum possible address for iteration - var addressLimit = IPAddressMath.Min(this.Tail, - this.IsIPv4 - ? IPAddressUtilities.IPv4MaxAddress - : IPAddressUtilities.IPv6MaxAddress) - .GetAddressBytes(); + var addressLimit = IPAddressMath + .Min(this.Tail, this.IsIPv4 ? IPAddressUtilities.IPv4MaxAddress : IPAddressUtilities.IPv6MaxAddress) + .GetAddressBytes(); // determine the width of the bye array for the address address - var addressByteWidth = this.IsIPv4 - ? IPAddressUtilities.IPv4ByteCount - : IPAddressUtilities.IPv6ByteCount; + var addressByteWidth = this.IsIPv4 ? IPAddressUtilities.IPv4ByteCount : IPAddressUtilities.IPv6ByteCount; var currentAddressBytes = this.Head.GetAddressBytes(); @@ -286,7 +281,13 @@ public IEnumerator GetEnumerator() // copy appropriate portion of next address with prefixed 0x00 bytes currentAddressBytes = new byte[addressByteWidth]; - Array.Copy(nextAddressBytes, 0, currentAddressBytes, addressByteWidth - nextAddressByteWidth, nextAddressByteWidth); + Array.Copy( + nextAddressBytes, + 0, + currentAddressBytes, + addressByteWidth - nextAddressByteWidth, + nextAddressByteWidth + ); } } @@ -307,8 +308,7 @@ public override string ToString() } /// - public virtual string ToString(string format, - IFormatProvider formatProvider) + public virtual string ToString(string format, IFormatProvider formatProvider) { switch (format?.Trim()) { @@ -331,21 +331,17 @@ public virtual string ToString(string format, #region Contains /// - public bool Contains(IIPAddressRange that) + public bool Contains(IIPAddressRange addressRange) { - return ReferenceEquals(this, that) - || Equals(this, that) - || (that != null - && this.Contains(that.Head) - && this.Contains(that.Tail)); + return ReferenceEquals(this, addressRange) + || Equals(this, addressRange) + || (addressRange != null && this.Contains(addressRange.Head) && this.Contains(addressRange.Tail)); } /// public bool Contains(IPAddress address) { - return address != null - && address.AddressFamily == this.AddressFamily - && address.IsBetween(this.Head, this.Tail); + return address != null && address.AddressFamily == this.AddressFamily && address.IsBetween(this.Head, this.Tail); } #endregion // end: Contains @@ -353,43 +349,47 @@ public bool Contains(IPAddress address) #region Ovelap and Touches /// - public bool HeadOverlappedBy(IIPAddressRange that) + public bool HeadOverlappedBy(IIPAddressRange addressRange) { - return ReferenceEquals(this, that) - || Equals(this, that) - || (that != null - && that.Contains(this.Head)); + return ReferenceEquals(this, addressRange) + || Equals(this, addressRange) + || (addressRange != null && addressRange.Contains(this.Head)); } /// - public bool TailOverlappedBy(IIPAddressRange that) + public bool TailOverlappedBy(IIPAddressRange addressRange) { - return ReferenceEquals(this, that) - || Equals(this, that) - || (that != null - && that.Contains(this.Tail)); + return ReferenceEquals(this, addressRange) + || Equals(this, addressRange) + || (addressRange != null && addressRange.Contains(this.Tail)); } /// - public bool Overlaps(IIPAddressRange that) + public bool Overlaps(IIPAddressRange addressRange) { - return ReferenceEquals(this, that) - || Equals(this, that) - || (that != null - && (this.Contains(that) - || this.Contains(that.Head) - || this.Contains(that.Tail))); + return ReferenceEquals(this, addressRange) + || Equals(this, addressRange) + || ( + addressRange != null + && (this.Contains(addressRange) || this.Contains(addressRange.Head) || this.Contains(addressRange.Tail)) + ); } /// - public bool Touches(IIPAddressRange that) + public bool Touches(IIPAddressRange addressRange) { - return that != null - && this.AddressFamily == that.AddressFamily - && ((this.Tail.IsLessThan(this.Tail.AddressFamily.MaxIPAddress()) // prevent overflow - && Equals(this.Tail.Increment(), that.Head)) // this tail appears directly before that head - || (that.Tail.IsLessThan(that.Tail.AddressFamily.MaxIPAddress()) // prevent overflow - && Equals(that.Tail.Increment(), this.Head))); // that tail appears directly before this head + return addressRange != null + && this.AddressFamily == addressRange.AddressFamily + && ( + ( + this.Tail.IsLessThan(this.Tail.AddressFamily.MaxIPAddress()) // prevent overflow + && Equals(this.Tail.Increment(), addressRange.Head) + ) // this tail appears directly before that head + || ( + addressRange.Tail.IsLessThan(addressRange.Tail.AddressFamily.MaxIPAddress()) // prevent overflow + && Equals(addressRange.Tail.Increment(), this.Head) + ) + ); // that tail appears directly before this head } #endregion // end: Ovelap and Touches @@ -401,29 +401,33 @@ public bool Touches(IIPAddressRange that) /// public bool ContainsAnyPrivateAddresses() { - return SubnetUtilities.PrivateIPAddressRangesList.Any(subnet => subnet.Contains(this.Head) - || subnet.Contains(this.Tail)); + return SubnetUtilities.PrivateIPAddressRangesList.Any(subnet => + subnet.Contains(this.Head) || subnet.Contains(this.Tail) + ); } /// public bool ContainsAllPrivateAddresses() { - return SubnetUtilities.PrivateIPAddressRangesList.Any(subnet => subnet.Contains(this.Head) - && subnet.Contains(this.Tail)); + return SubnetUtilities.PrivateIPAddressRangesList.Any(subnet => + subnet.Contains(this.Head) && subnet.Contains(this.Tail) + ); } /// public bool ContainsAnyPublicAddresses() { - return SubnetUtilities.PrivateIPAddressRangesList.All(subnet => !subnet.Contains(this.Head) - || !subnet.Contains(this.Tail)); + return SubnetUtilities.PrivateIPAddressRangesList.All(subnet => + !subnet.Contains(this.Head) || !subnet.Contains(this.Tail) + ); } /// public bool ContainsAllPublicAddresses() { - return SubnetUtilities.PrivateIPAddressRangesList.All(subnet => !subnet.Contains(this.Head) - && !subnet.Contains(this.Tail)); + return SubnetUtilities.PrivateIPAddressRangesList.All(subnet => + !subnet.Contains(this.Head) && !subnet.Contains(this.Tail) + ); } #endregion end: Contains Any/All Public/Private Addresses diff --git a/src/Arcus/Arcus.csproj b/src/Arcus/Arcus.csproj index 65d8cc9..965bd59 100644 --- a/src/Arcus/Arcus.csproj +++ b/src/Arcus/Arcus.csproj @@ -1,90 +1,114 @@  - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - Sandia National Laboratories - Copyright 2018 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software. - Arcus is a C# manipulation library for calculating, parsing, formatting, converting, and comparing both IPv4 and Pv6 addresses and subnets. It accounts for 128-bit numbers on 32-bit platforms. - true - false - true - true - true + netstandard2.0;net8.0;net9.0 + true + bin\ + $(OutputPath)$(AssemblyName).xml + + + + $(VersionFromCI) true - latest + + + 0.0.0-build + false + + + + true + + + true + + + + Arcus + Arcus + Arcus is a C# manipulation library for calculating, parsing, formatting, converting, and comparing both IPv4 and Pv6 addresses and subnets. It accounts for 128-bit numbers on 32-bit platforms. + true en - icon.png - https://raw.githubusercontent.com/sandialabs/Arcus/master/src/Arcus/icon.png en-US - Apache-2.0 - https://github.com/sandialabs/arcus - - Arcus is a C# manipulation library for calculating, parsing, formatting, converting, and comparing both IPv4 and Pv6 addresses and subnets. It accounts for 128-bit numbers on 32-bit platforms. + Apache-2.0 + Sandia National Laboratories + The Production Tools Team + Copyright 2025 National Technology & Engineering Solutions of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software + README.md IP; IP Address; IP Calculator; IPv4; IPv6; ipaddress; ipaddresses; ipv4; ipv4 address; ipv4 calculator; ipv4 network; ipv4 subnetting; ipv4 support; ipv6; ipv6 address; ipv6 calulator; ipv6 network; ipv6 subnetting; ipv6 support; netmask; networking; route prefix; subnet + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true + https://github.com/sandialabs/Arcus.git + https://github.com/sandialabs/Arcus + icon.png + https://raw.githubusercontent.com/sandialabs/Arcus/main/src/Arcus/icon.png git - https://github.com/sandialabs/arcus/ + true snupkg - netstandard1.3 - Arcus - 2.2.1 - + + stylecop.json + - - - - + + + - - - ..\analyzers.ruleset - - - - bin\ - bin\Arcus.xml - - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - stylecop.json - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers + runtime; build; native; contentfiles; analyzers; buildtransitive all - runtime; build; native; contentfiles; analyzers - - + + + + + + + + + + + diff --git a/src/Arcus/Comparers/DefaultAddressFamilyComparer.cs b/src/Arcus/Comparers/DefaultAddressFamilyComparer.cs index 5ea22ef..f3519e6 100644 --- a/src/Arcus/Comparers/DefaultAddressFamilyComparer.cs +++ b/src/Arcus/Comparers/DefaultAddressFamilyComparer.cs @@ -10,9 +10,13 @@ namespace Arcus.Comparers /// public class DefaultAddressFamilyComparer : Comparer { + /// + /// Default instance of using + /// + public static readonly DefaultAddressFamilyComparer Instance = new DefaultAddressFamilyComparer(); + /// - public override int Compare(AddressFamily x, - AddressFamily y) + public override int Compare(AddressFamily x, AddressFamily y) { return x.CompareTo(y); } diff --git a/src/Arcus/Comparers/DefaultIIPAddressRangeComparer.cs b/src/Arcus/Comparers/DefaultIIPAddressRangeComparer.cs new file mode 100644 index 0000000..65c0d22 --- /dev/null +++ b/src/Arcus/Comparers/DefaultIIPAddressRangeComparer.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Net; + +namespace Arcus.Comparers +{ + /// + /// Default + /// Compares by and then by range length ordinal + /// + public class DefaultIIPAddressRangeComparer : Comparer + { + /// + /// Default instance of using + /// + public static readonly DefaultIIPAddressRangeComparer Instance = new DefaultIIPAddressRangeComparer(); + + private readonly IComparer _ipAddressComparer; + + /// + /// Initializes a new instance of the class. + /// + /// comparer used to compare + /// is . + public DefaultIIPAddressRangeComparer(IComparer ipAddressComparer) + { + this._ipAddressComparer = ipAddressComparer ?? throw new ArgumentNullException(nameof(ipAddressComparer)); + } + + /// + /// Initializes a new instance of the class. + /// Defaults to use the DefaultIIPAddressComparer + /// + public DefaultIIPAddressRangeComparer() + : this(DefaultIPAddressComparer.Instance) { } + + /// + public override int Compare(IIPAddressRange x, IIPAddressRange y) + { + if (ReferenceEquals(x, y)) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + var headComparison = this._ipAddressComparer.Compare(x.Head, y.Head); + return headComparison != 0 ? headComparison : x.Length.CompareTo(y.Length); + } + } +} diff --git a/src/Arcus/Comparers/DefaultIPAddressComparer.cs b/src/Arcus/Comparers/DefaultIPAddressComparer.cs index 8035d85..c08ad78 100644 --- a/src/Arcus/Comparers/DefaultIPAddressComparer.cs +++ b/src/Arcus/Comparers/DefaultIPAddressComparer.cs @@ -3,7 +3,6 @@ using System.Net; using System.Net.Sockets; using Gulliver; -using JetBrains.Annotations; namespace Arcus.Comparers { @@ -14,6 +13,11 @@ namespace Arcus.Comparers /// public class DefaultIPAddressComparer : Comparer { + /// + /// Default instance of using + /// + public static readonly DefaultIPAddressComparer Instance = new DefaultIPAddressComparer(); + private readonly IComparer _addressFamilyComparer; /// @@ -21,25 +25,25 @@ public class DefaultIPAddressComparer : Comparer /// /// the comparer /// is . - public DefaultIPAddressComparer([NotNull] IComparer addressFamilyComparer) + public DefaultIPAddressComparer(IComparer addressFamilyComparer) { if (addressFamilyComparer == null) { throw new ArgumentNullException(nameof(addressFamilyComparer)); } - this._addressFamilyComparer = addressFamilyComparer ?? throw new ArgumentNullException(nameof(addressFamilyComparer)); + this._addressFamilyComparer = + addressFamilyComparer ?? throw new ArgumentNullException(nameof(addressFamilyComparer)); } /// /// Initializes a new instance of the class. /// public DefaultIPAddressComparer() - : this(new DefaultAddressFamilyComparer()) { } + : this(DefaultAddressFamilyComparer.Instance) { } /// - public override int Compare(IPAddress x, - IPAddress y) + public override int Compare(IPAddress x, IPAddress y) { if (ReferenceEquals(x, y)) { @@ -59,8 +63,8 @@ public override int Compare(IPAddress x, var addressFamilyComparison = this._addressFamilyComparer.Compare(x.AddressFamily, y.AddressFamily); return addressFamilyComparison == 0 - ? ByteArrayUtils.CompareUnsignedBigEndian(x.GetAddressBytes(), y.GetAddressBytes()) - : addressFamilyComparison; + ? ByteArrayUtils.CompareUnsignedBigEndian(x.GetAddressBytes(), y.GetAddressBytes()) + : addressFamilyComparison; } } } diff --git a/src/Arcus/Comparers/DefaultIPAddressRangeComparer.cs b/src/Arcus/Comparers/DefaultIPAddressRangeComparer.cs index 1319d82..9a5d51c 100644 --- a/src/Arcus/Comparers/DefaultIPAddressRangeComparer.cs +++ b/src/Arcus/Comparers/DefaultIPAddressRangeComparer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Net; -using JetBrains.Annotations; namespace Arcus.Comparers { @@ -9,18 +8,24 @@ namespace Arcus.Comparers /// Default /// Compares by and then by range length ordinal /// + [Obsolete("Use DefaultIIPAddressRangeComparer")] public class DefaultIPAddressRangeComparer : Comparer { - private readonly IComparer _ipAddressComparer; + private readonly DefaultIIPAddressRangeComparer _comparer; /// /// Initializes a new instance of the class. /// /// comparer used to compare /// is . - public DefaultIPAddressRangeComparer([NotNull] IComparer ipAddressComparer) + public DefaultIPAddressRangeComparer(IComparer ipAddressComparer) { - this._ipAddressComparer = ipAddressComparer ?? throw new ArgumentNullException(nameof(ipAddressComparer)); + if (ipAddressComparer is null) + { + throw new ArgumentNullException(nameof(ipAddressComparer)); + } + + this._comparer = new DefaultIIPAddressRangeComparer(ipAddressComparer); } /// @@ -28,31 +33,12 @@ public DefaultIPAddressRangeComparer([NotNull] IComparer ipAddressCom /// Defaults to use the DefaultIPAddressComparer /// public DefaultIPAddressRangeComparer() - : this(new DefaultIPAddressComparer()) { } + : this(DefaultIPAddressComparer.Instance) { } /// - public override int Compare(IIPAddressRange x, - IIPAddressRange y) + public override int Compare(IIPAddressRange x, IIPAddressRange y) { - if (ReferenceEquals(x, y)) - { - return 0; - } - - if (x == null) - { - return -1; - } - - if (y == null) - { - return 1; - } - - var headComparison = this._ipAddressComparer.Compare(x.Head, y.Head); - return headComparison != 0 - ? headComparison - : x.Length.CompareTo(y.Length); + return _comparer.Compare(x, y); } } } diff --git a/src/Arcus/Converters/IPAddressConverters.cs b/src/Arcus/Converters/IPAddressConverters.cs index 596af51..091dad3 100644 --- a/src/Arcus/Converters/IPAddressConverters.cs +++ b/src/Arcus/Converters/IPAddressConverters.cs @@ -8,7 +8,6 @@ using System.Text.RegularExpressions; using Arcus.Utilities; using Gulliver; -using JetBrains.Annotations; namespace Arcus.Converters { @@ -28,7 +27,7 @@ public static class IPAddressConverters /// the route prefix /// is . /// not a valid netmask - public static int NetmaskToCidrRoutePrefix([NotNull] this IPAddress netmask) + public static int NetmaskToCidrRoutePrefix(this IPAddress netmask) { #region defense @@ -49,7 +48,7 @@ public static int NetmaskToCidrRoutePrefix([NotNull] this IPAddress netmask) for (var i = 0; i < IPAddressUtilities.IPv4BitCount; i++) { - var bitMask = (byte) (0x80 >> (i % 8)); // bit mask built from current bit position in byte + var bitMask = (byte)(0x80 >> (i % 8)); // bit mask built from current bit position in byte if ((netmaskBytes[i / 8] & bitMask) != 0) // if set bits are common { @@ -68,7 +67,7 @@ public static int NetmaskToCidrRoutePrefix([NotNull] this IPAddress netmask) #region string conversion /// - /// IPv6 to to Base85 (will return empty string for non ipv6 addresses) AKA Ascii85 + /// IPv6 to Base85 (will return empty string for non ipv6 addresses) AKA Ascii85 /// from RFC 1924 ( http://tools.ietf.org/html/rfc1924 ) /// /// The RFC is an April Fools Day Joke, but we implemented it anyhow @@ -76,27 +75,24 @@ public static int NetmaskToCidrRoutePrefix([NotNull] this IPAddress netmask) /// /// the ip address to convert /// Ascii85/Base85 representation of IPv6 Address, or an empty string on failure - [CanBeNull] - public static string ToBase85String([CanBeNull] this IPAddress ipAddress) + public static string ToBase85String(this IPAddress ipAddress) { - if (ipAddress == null - || !ipAddress.IsIPv6()) + if (ipAddress == null || !ipAddress.IsIPv6()) { return null; } - return string.Concat(GetBase85Chars(ipAddress) - .Reverse()) - .PadLeft(20, '0'); + return string.Concat(GetBase85Chars(ipAddress).Reverse()).PadLeft(20, '0'); +#if NET6_0_OR_GREATER + static +#endif IEnumerable GetBase85Chars(IPAddress input) { const string alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; // get little endian unsigned byte value - var addressBytes = input.GetAddressBytes() - .Reverse() - .ToList(); + var addressBytes = input.GetAddressBytes().Reverse().ToList(); addressBytes.Add(0x00); @@ -106,9 +102,8 @@ IEnumerable GetBase85Chars(IPAddress input) do { bigInteger = BigInteger.DivRem(bigInteger, 85, out var charIndex); - yield return alphabet[(int) charIndex]; - } - while (bigInteger > 0); + yield return alphabet[(int)charIndex]; + } while (bigInteger > 0); } } @@ -118,8 +113,7 @@ IEnumerable GetBase85Chars(IPAddress input) /// the ip address to convert /// dotted quad version of the given address /// is . - [CanBeNull] - public static string ToDottedQuadString([CanBeNull] this IPAddress ipAddress) + public static string ToDottedQuadString(this IPAddress ipAddress) { if (ipAddress == null) { @@ -134,45 +128,37 @@ public static string ToDottedQuadString([CanBeNull] this IPAddress ipAddress) // TODO candidate for clean up / simplification var bytes = ipAddress.GetAddressBytes(); // get the bytes of the ip address - var leadingBytes = bytes.Take(12) - .ToArray(); // capture the non ipv4 bytes - - var hextets = Enumerable.Range(0, 6) - .Select(i => - { - var index = i * 2; - return new byte[] - { - leadingBytes[index + 1], - leadingBytes[index], - 0x0, - 0x0 - }; - }) // get bytes in pairs with leading 0's to force unsigned form - .Select(bs => BitConverter.ToInt32(bs, 0)) // convert byte pairs to 16 bit integers - .Select(i => $"{i:x}"); // combine bytes to a hex string + var leadingBytes = bytes.Take(12).ToArray(); // capture the non ipv4 bytes + + var hextets = Enumerable + .Range(0, 6) + .Select(i => + { + var index = i * 2; + return new byte[] { leadingBytes[index + 1], leadingBytes[index], 0x0, 0x0 }; + }) // get bytes in pairs with leading 0's to force unsigned form + .Select(bs => BitConverter.ToInt32(bs, 0)) // convert byte pairs to 16 bit integers + .Select(i => $"{i:x}"); // combine bytes to a hex string var hextetString = string.Join(":", hextets); // join the hextets on a colon var longestMatch = new Regex(@"((:|\b)0\b)+") // find 0's surrounded by colons or word breaks - .Matches(hextetString) // match across the hextet string - .Cast() - .Select(match => match.Value) // get the match value - .OrderByDescending(s => s?.StartsWith("0", StringComparison.OrdinalIgnoreCase) == true - ? s.Length + 1 - : s.Length) // order by length accounting for matches at beginning of string - .FirstOrDefault(); // find the longest span of 0 valued hextets, or null if one does not exist + .Matches(hextetString) // match across the hextet string + .Cast() + .Select(match => match.Value) // get the match value + .OrderByDescending(s => + s?.StartsWith("0", StringComparison.OrdinalIgnoreCase) == true ? s.Length + 1 : s.Length + ) // order by length accounting for matches at beginning of string + .FirstOrDefault(); // find the longest span of 0 valued hextets, or null if one does not exist if (longestMatch != null) { // get the first index of the longest match var index = hextetString.IndexOf(longestMatch, StringComparison.Ordinal); - hextetString = hextetString.Remove(index, longestMatch.Length) - .Insert(index, ":"); // replace first occurrence with a ":" char + hextetString = hextetString.Remove(index, longestMatch.Length).Insert(index, ":"); // replace first occurrence with a ":" char } - var followingBytes = bytes.Skip(12) - .ToArray(); // capture IPv4 bytes (last 4) + var followingBytes = bytes.Skip(12).ToArray(); // capture IPv4 bytes (last 4) var ipv4Address = new IPAddress(followingBytes).ToString(); return hextetString + ":" + ipv4Address; @@ -184,11 +170,9 @@ public static string ToDottedQuadString([CanBeNull] this IPAddress ipAddress) /// the ip address to convert /// Hex version of the given IP Address /// is . - [CanBeNull] - public static string ToHexString([CanBeNull] this IPAddress ipAddress) + public static string ToHexString(this IPAddress ipAddress) { - return ipAddress?.GetAddressBytes() - .ToString("HC", CultureInfo.InvariantCulture); + return ipAddress?.GetAddressBytes().ToString("HC", CultureInfo.InvariantCulture); } /// @@ -197,11 +181,9 @@ public static string ToHexString([CanBeNull] this IPAddress ipAddress) /// The ip address to convert /// an integral representation of the IP address /// is . - [CanBeNull] - public static string ToNumericString([CanBeNull] this IPAddress ipAddress) + public static string ToNumericString(this IPAddress ipAddress) { - return ipAddress?.GetAddressBytes() - .ToString("IBE", CultureInfo.InvariantCulture); + return ipAddress?.GetAddressBytes().ToString("IBE", CultureInfo.InvariantCulture); } /// @@ -210,8 +192,7 @@ public static string ToNumericString([CanBeNull] this IPAddress ipAddress) /// the address to expand /// the expanded for of IPv4/IPv6, or ToString() otherwise /// is . - [CanBeNull] - public static string ToUncompressedString([CanBeNull] this IPAddress ipAddress) + public static string ToUncompressedString(this IPAddress ipAddress) { if (ipAddress == null) { @@ -230,8 +211,7 @@ public static string ToUncompressedString([CanBeNull] this IPAddress ipAddress) string IPv4ToString() { - var octets = ipAddress.GetAddressBytes() - .Select(b => $"{b:D3}"); + var octets = ipAddress.GetAddressBytes().Select(b => $"{b:D3}"); return string.Join(".", octets); // join padded strings with '.' character } @@ -240,9 +220,10 @@ string IPv6ToString() { var addressBytes = ipAddress.GetAddressBytes(); - var hextets = Enumerable.Range(0, IPAddressUtilities.IPv6HextetCount) - .Select(i => i * 2) - .Select(i => $"{addressBytes[i]:x2}{addressBytes[i + 1]:x2}"); + var hextets = Enumerable + .Range(0, IPAddressUtilities.IPv6HextetCount) + .Select(i => i * 2) + .Select(i => $"{addressBytes[i]:x2}{addressBytes[i + 1]:x2}"); return string.Join(":", hextets); } diff --git a/src/Arcus/IIPAddressRange.cs b/src/Arcus/IIPAddressRange.cs index 754aed6..8baf422 100644 --- a/src/Arcus/IIPAddressRange.cs +++ b/src/Arcus/IIPAddressRange.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Numerics; -using JetBrains.Annotations; namespace Arcus { @@ -12,46 +11,66 @@ namespace Arcus /// A range must contain a head (the first address) and a tail (the last address) inclusive /// The tail should NEVER appear numerically previous to a head /// - [PublicAPI] - public interface IIPAddressRange : IFormattable, - IEnumerable + public interface IIPAddressRange : IFormattable, IEnumerable { + // TODO future versions of IIPAddressRange should not directly implement IEnumerable and instead a spawn an IEnumerable on demand + /// - /// The address family of the Address Range + /// Gets the address family of the Address Range /// Typically Internetwork or InternetworkV6 /// + /// + /// The address family of the Address Range + /// Typically Internetwork or InternetworkV6 + /// AddressFamily AddressFamily { get; } /// - /// The length of a + /// Gets the length of a /// + /// + /// The length of a + /// BigInteger Length { get; } /// - /// The head of a + /// Gets the head of a /// - [NotNull] + /// + /// The head of a + /// IPAddress Head { get; } /// - /// The tail of a + /// Gets the tail of a /// - [NotNull] + /// + /// The tail of a + /// IPAddress Tail { get; } /// - /// if the subnet describes a single ip address + /// Gets a value indicating whether if the subnet describes a single ip address /// + /// + /// if the subnet describes a single ip address + /// bool IsSingleIP { get; } /// - /// Determine if IIPAddress Range is IPv4 + /// Gets a value indicating whether determine if IIPAddress Range is IPv4 /// + /// + /// Determine if IIPAddress Range is IPv4 + /// bool IsIPv4 { get; } /// - /// Determine if IIPAddress Range is IPv6 + /// Gets a value indicating whether determine if IIPAddress Range is IPv6 /// + /// + /// Determine if IIPAddress Range is IPv6 + /// bool IsIPv6 { get; } /// @@ -75,8 +94,7 @@ public interface IIPAddressRange : IFormattable, /// /// the head /// the tail - void Deconstruct([NotNull] out IPAddress head, - [NotNull] out IPAddress tail); + void Deconstruct(out IPAddress head, out IPAddress tail); #endregion // end: Deconstructors @@ -85,42 +103,48 @@ void Deconstruct([NotNull] out IPAddress head, /// /// determine if a contains another /// - /// the secondary operand - bool Contains(IIPAddressRange that); + /// the secondary operand + /// true if the is contained within + bool Contains(IIPAddressRange addressRange); /// /// determine if a contains an /// /// the secondary operand + /// true if the is contained within bool Contains(IPAddress address); #region Ovelap and Touches /// - /// Return if the head of this is within the range of + /// Return if the head of this is within the range of /// - /// the secondary operand - bool HeadOverlappedBy(IIPAddressRange that); + /// the secondary operand + /// if the head of this is within the range of + bool HeadOverlappedBy(IIPAddressRange addressRange); /// - /// Return if the tail of this is within the range of + /// Return if the tail of this is within the range of /// - /// the secondary operand - bool TailOverlappedBy(IIPAddressRange that); + /// the secondary operand + /// if the tail of this is within the range of + bool TailOverlappedBy(IIPAddressRange addressRange); /// /// return if this overlaps that (totally contained within, or contains either the head or the /// tail) /// - /// the secondary operand - bool Overlaps(IIPAddressRange that); + /// the secondary operand + /// if overlaps + bool Overlaps(IIPAddressRange addressRange); /// /// if tail of one item is consecutively in order of head of other item /// eg in sequence with no gaps in between /// - /// the secondary operand - bool Touches(IIPAddressRange that); + /// the secondary operand + /// if is mathematically consecutive + bool Touches(IIPAddressRange addressRange); #endregion // end: Ovelap and Touches @@ -131,28 +155,27 @@ void Deconstruct([NotNull] out IPAddress head, /// /// Determines if the range contains any private addresses /// - /// iff the range contains any private addresses + /// if, and only if, the range contains any private addresses bool ContainsAnyPrivateAddresses(); /// /// Determines if the range contains all private addresses /// - /// iff the range contains all private addresses + /// if, and only if, the range contains all private addresses bool ContainsAllPrivateAddresses(); /// /// Determines if the range contains any public addresses /// - /// iff the range contains any public addresses + /// if, and only if, the range contains any public addresses bool ContainsAnyPublicAddresses(); /// /// Determines if the range contains all public addresses /// - /// iff the range contains all public addresses + /// if, and only if, the range contains all public addresses bool ContainsAllPublicAddresses(); #endregion end: Contains Any/All Public/Private Addresses - } } diff --git a/src/Arcus/IPAddressRange.cs b/src/Arcus/IPAddressRange.cs index 1a71485..5729660 100644 --- a/src/Arcus/IPAddressRange.cs +++ b/src/Arcus/IPAddressRange.cs @@ -5,446 +5,422 @@ using System.Runtime.Serialization; using Arcus.Comparers; using Arcus.Math; -using JetBrains.Annotations; namespace Arcus { - /// - /// A basic implementation of a IIPAddressRange us to represent an inclusive range of arbitrary IP Addresses of the - /// same address family - /// - [PublicAPI] - [Serializable] - public class IPAddressRange : AbstractIPAddressRange, - IEquatable, - IComparable, - IComparable, - ISerializable - { - #region From Interface ISerializable - - /// - /// is - public void GetObjectData([NotNull] SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - info.AddValue(nameof(Head), Head.GetAddressBytes()); - info.AddValue(nameof(Tail), Tail.GetAddressBytes()); - } - - #endregion - - #region From Interface IComparable - - /// - public int CompareTo(object obj) - { - if (ReferenceEquals(null, obj)) - { - return 1; - } - - if (ReferenceEquals(this, obj)) - { - return 0; - } - - return obj is IPAddressRange other - ? this.CompareTo(other) - : throw new ArgumentException($"Object must be of type {nameof(IPAddressRange)}"); - } - - #endregion - - #region From Interface IComparable - - /// - public int CompareTo(IPAddressRange other) - { - return new DefaultIPAddressRangeComparer().Compare(this, other); - } - - #endregion - - #region From Interface IEquatable - - /// - public bool Equals(IPAddressRange other) - { - return !ReferenceEquals(null, other) - && (ReferenceEquals(this, other) - || (Equals(Head, other.Head) - && Equals(Tail, other.Tail))); - } - - #endregion - - /// - public override bool Equals(object obj) - { - return !ReferenceEquals(null, obj) - && (ReferenceEquals(this, obj) - || (obj.GetType() == GetType() - && this.Equals((IPAddressRange)obj))); - } - - /// - public override int GetHashCode() - { - unchecked - { - return (Head.GetHashCode() * 397) ^ Tail.GetHashCode(); - } - } - - #region operators - - /// - /// Compares two objects for equality - /// - /// left hand operand - /// right hand operand - /// when both sides are equal - public static bool operator ==(IPAddressRange left, - IPAddressRange right) - { - return Equals(left, right); - } - - /// - /// Compares two objects for non-equality - /// - /// left hand operand - /// right hand operand - /// when both sides are not equal - public static bool operator !=(IPAddressRange left, - IPAddressRange right) - { - return !Equals(left, right); - } - - /// - /// Compares two objects for being less than - /// - /// - /// left hand operand - /// right hand operand - /// when is less than - public static bool operator <(IPAddressRange left, - IPAddressRange right) - { - return Comparer.Default.Compare(left, right) < 0; - } - - /// - /// Compares two objects for being greater than - /// - /// - /// left hand operand - /// right hand operand - /// when is greater than - public static bool operator >(IPAddressRange left, - IPAddressRange right) - { - return Comparer.Default.Compare(left, right) > 0; - } - - /// - /// Compares two objects for being less than or equal - /// - /// - /// left hand operand - /// right hand operand - /// - /// when is less than or equal to - /// - public static bool operator <=(IPAddressRange left, - IPAddressRange right) - { - return Comparer.Default.Compare(left, right) <= 0; - } - - /// - /// Compares two objects for being greater than or equal to - /// - /// - /// left hand operand - /// right hand operand - /// - /// when is greater than or equal to - /// - public static bool operator >=(IPAddressRange left, - IPAddressRange right) - { - return Comparer.Default.Compare(left, right) >= 0; - } - - #endregion end operators - - #region Ctor - - /// - /// Initializes a new instance of the class. - /// - /// the - public IPAddressRange([NotNull] IPAddress address) - : base(address, address) - { - // nothing more to do - } - - /// - /// Initializes a new instance of the class. - /// - /// head - /// tail - public IPAddressRange([NotNull] IPAddress head, - [NotNull] IPAddress tail) - : base(head, tail) - { - // nothing more to do - } - - /// Initializes a new instance of the class. - /// serialization info - /// serialization context - /// is - protected IPAddressRange([NotNull] SerializationInfo info, -#pragma warning disable CA1801 - /* CA1801 Parameter context of method .ctor is never used. Remove the parameter or use it in the method body. - * parameter "StreamingContext context" is required for proper desearilization - */ - StreamingContext context) -#pragma warning restore CA1801 - : this(new IPAddress((byte[])(info ?? throw new ArgumentNullException(nameof(info))).GetValue(nameof(Head), typeof(byte[]))), - new IPAddress((byte[])info.GetValue(nameof(Tail), typeof(byte[])))) - { } - - #endregion // end: Ctor - - #region static methods - - /// - /// Attempt collapse the given input of ranges into fewer ranges thus optimizing - /// Ranges that overlap, or butt against each other may be collapsed into a single range - /// - /// ranges to collapse - /// resulting ranges post collapse - public static bool TryCollapseAll(IEnumerable ranges, - out IEnumerable result) - { - var rangeList = (ranges ?? Enumerable.Empty()).ToList(); - - // item null check - if (rangeList.Any(r => r == null)) - { - result = Enumerable.Empty(); - return false; - } - - // no ranges provided - if (!rangeList.Any()) // no ranges - { - result = Enumerable.Empty(); - return true; // assume success - } - - // all families don't match match - if (rangeList.Any(r => r.AddressFamily - != rangeList.First() - .AddressFamily)) - { - result = Enumerable.Empty(); - return false; - } + /// + /// A basic implementation of a IIPAddressRange us to represent an inclusive range of arbitrary IP Addresses of the + /// same address family + /// + [Serializable] + public class IPAddressRange + : AbstractIPAddressRange, +#if NETSTANDARD2_0 + ISerializable, +#endif + IEquatable, + IComparable, + IComparable + { +#if NETSTANDARD2_0 + #region From Interface ISerializable + + /// + /// is + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info is null) + { + throw new ArgumentNullException(nameof(info)); + } - // sort range list, has to be done post validation check, as invalid cannot be sorted - rangeList = rangeList.OrderBy(r => r) - .ToList(); + info.AddValue(nameof(Head), Head.GetAddressBytes()); + info.AddValue(nameof(Tail), Tail.GetAddressBytes()); + } - var resultList = new List - { - rangeList.First() // start with first item in the sorted list of ranges - }; + #endregion +#endif - // iterate over items in range list, no need to iterate over first as it is included by default - foreach (var range in rangeList.Skip(1)) - { - var last = resultList.Last(); // take end of result to process over (is first in first iteration) + #region From Interface IComparable - // can be assumed that assume that the range head is greater than or equal to last head because everything is ordered + /// + public int CompareTo(object obj) + { + if (obj is null) + { + return 1; + } - // if TryMerge succeeded then overlap exists, merge overlap and re-assign to end of results - if (TryMerge(last, range, out var merge)) + if (obj is IPAddressRange other) { - resultList[resultList.Count - 1] = merge; // overwrite with merged values - continue; + return CompareTo(other); } - // could not merge, simply add to end and iterate to next - resultList.Add(range); - } - - result = resultList; - return true; - } - - /// - /// Rebuild the initial range as an of ranges excluding the excluded ranges - /// excluded ranges are expected to each be sub ranges of the initial range - /// an attempt will be made to TryCollapseAll the excluded - /// - /// the initial to exclude from - /// - /// the various to exclude from the - /// - /// - /// the resulting - /// unexpected invalid operation. - public static bool TryExcludeAll(IPAddressRange initialRange, - IEnumerable excludedRanges, - out IEnumerable result) - { - // TODO the logical flow here is confusing, see if it can be cleaned up a bit - - if (initialRange == null - || excludedRanges == null) - { - result = Enumerable.Empty(); - return false; - } + throw new ArgumentException("Object is not an IPAddressRange"); + } - var excludedRangesList = excludedRanges as IList ?? excludedRanges.ToList(); + #endregion - // item null check - if (excludedRangesList.Any(r => r == null)) - { - result = Enumerable.Empty(); - return false; - } + #region From Interface IComparable + + /// + public int CompareTo(IPAddressRange other) + { + if (other is null) + { + return 1; + } - // no rangers to exclude, out copy of original - if (!excludedRangesList.Any()) - { - result = new List - { - new IPAddressRange(initialRange.Head, initialRange.Tail) - }; + return DefaultIIPAddressRangeComparer.Instance.Compare(this, other); + } - return true; - } + #endregion + + #region From Interface IEquatable + + /// + public virtual bool Equals(IPAddressRange other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return DefaultIIPAddressRangeComparer.Instance.Compare(this, other) == 0; + } + + #endregion + + /// + public override bool Equals(object obj) + { + if (obj is IPAddressRange other) + { + return Equals(other); + } - // all families don't match match - if (excludedRangesList.Any(r => r.AddressFamily != initialRange.AddressFamily)) - { - result = Enumerable.Empty(); return false; - } - - // results is initialized with a *copy* of initialRange - var resultList = new List - { - new IPAddressRange(initialRange.Head, initialRange.Tail) - }; - - foreach (var exclusion in excludedRangesList) - { - var last = resultList.Last(); - var lastIndex = resultList.Count - 1; - - // exclusion has tail, remove last element from result - //if (exclusion.Contains(initialRange.Tail)) - //{ - // result.RemoveAt(endOfList); - // break; // can't loop any more - //} - - if (exclusion.Contains(last)) + } + + /// + public override int GetHashCode() => HashCode.Combine(Head, Tail); + + #region operators + + /// + /// Determines whether two instances are equal. + /// + /// The first instance. + /// The second instance. + /// true if both instances are equal; otherwise, false. + public static bool operator ==(IPAddressRange left, IPAddressRange right) + { + if (ReferenceEquals(left, right)) { - resultList.RemoveAt(lastIndex); - break; // can't loop any more + return true; } - if (exclusion.Contains(initialRange.Tail)) + if (left is null || right is null) { - //resultList[lastIndex].Tail = exclusion.Head.Increment(-1); - var head = resultList[lastIndex] - .Head; - var tail = exclusion.Head.Increment(-1); - resultList[lastIndex] = new IPAddressRange(head, tail); - continue; // can't loop any more + return false; } - // exclusion contains head - if (exclusion.Contains(last.Head)) + return left.Equals(right); + } + + /// + /// Determines whether two instances are not equal. + /// + /// The first instance. + /// The second instance. + /// true if the instances are not equal; otherwise, false. + public static bool operator !=(IPAddressRange left, IPAddressRange right) => !(left == right); + + /// + /// Compares two instances to determine if the first is less than the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is less than the second; otherwise, false. + public static bool operator <(IPAddressRange left, IPAddressRange right) + { + if (left is null) { - // push head one point beyond tail of exclusion - //resultList[lastIndex].Head = exclusion.Tail.Increment(); - var head = exclusion.Tail.Increment(); - var tail = resultList[lastIndex] - .Tail; - resultList[lastIndex] = new IPAddressRange(head, tail); - continue; // next iteration + return !(right is null); // null is less than any non-null instance } - // exclusion is within last, carve exclusion to two pieces - if (!last.Overlaps(exclusion)) + return left.CompareTo(right) < 0; + } + + /// + /// Compares two instances to determine if the first is greater than the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is greater than the second; otherwise, false. + public static bool operator >(IPAddressRange left, IPAddressRange right) + { + if (left is null) { - throw new InvalidOperationException("An unexpected overlap check operation occured"); // should not have made it here + return false; } - // resultList[lastIndex].Tail = exclusion.Head.Increment(-1); - resultList[lastIndex] = new IPAddressRange(resultList[lastIndex] - .Head, - exclusion.Head.Increment(-1)); - - // carve out a piece at starting at exclusion head, ending at exclusion tail - // thus meaning one less than head at previous, and one more at head at next - resultList.Add(new IPAddressRange(exclusion.Tail.Increment(), initialRange.Tail)); - } - - result = resultList; - return true; - } - - /// - /// Merge two touching or overlapping address ranges - /// - /// the left operand - /// the right operand - /// the resulting - public static bool TryMerge(IPAddressRange left, - IPAddressRange right, - out IPAddressRange mergedRange) - { - if (left == null - || right == null - || left.AddressFamily != right.AddressFamily) - { - mergedRange = null; - return false; - } + return left.CompareTo(right) > 0; + } + + /// + /// Compares two instances to determine if the first is less than or equal to the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is less than or equal to the second; otherwise, false. + public static bool operator <=(IPAddressRange left, IPAddressRange right) => left < right || left == right; + + /// + /// Compares two instances to determine if the first is greater than or equal to the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is greater than or equal to the second; otherwise, false. + public static bool operator >=(IPAddressRange left, IPAddressRange right) => left > right || left == right; + + #endregion operators + + #region Ctor + + /// + /// Initializes a new instance of the class. + /// + /// the + public IPAddressRange(IPAddress address) + : base(address, address) + { + // nothing more to do + } + + /// + /// Initializes a new instance of the class. + /// + /// head + /// tail + public IPAddressRange(IPAddress head, IPAddress tail) + : base(head, tail) + { + // nothing more to do + } + + /// Initializes a new instance of the class. + /// serialization info + /// serialization context + /// is + protected IPAddressRange(SerializationInfo info, StreamingContext context) + : this( + new IPAddress( + (byte[])(info ?? throw new ArgumentNullException(nameof(info))).GetValue(nameof(Head), typeof(byte[])) + ), + new IPAddress((byte[])info.GetValue(nameof(Tail), typeof(byte[]))) + ) { } + + #endregion // end: Ctor + + #region static methods + + /// + /// Attempt collapse the given input of ranges into fewer ranges thus optimizing + /// Ranges that overlap, or butt against each other may be collapsed into a single range + /// + /// ranges to collapse + /// resulting ranges post collapse + /// true on success + public static bool TryCollapseAll(IEnumerable ranges, out IEnumerable result) + { + var rangeList = (ranges ?? Enumerable.Empty()).ToList(); + + // item null check + if (rangeList.Contains(null)) + { + result = Enumerable.Empty(); + return false; + } + + // no ranges provided + if (!rangeList.Any()) // no ranges + { + result = Enumerable.Empty(); + return true; // assume success + } - // overlap or touch occurs - if (left.Overlaps(right) - || left.Touches(right)) - { - var newHead = IPAddressMath.Min(left.Head, right.Head); - var newTail = IPAddressMath.Max(left.Tail, right.Tail); - mergedRange = new IPAddressRange(newHead, newTail); + // all families don't match match + if (rangeList.Any(r => r.AddressFamily != rangeList[0].AddressFamily)) + { + result = Enumerable.Empty(); + return false; + } + // sort range list, has to be done post validation check, as invalid cannot be sorted + rangeList = rangeList.OrderBy(r => r).ToList(); + + var resultList = new List + { + rangeList[0], // start with first item in the sorted list of ranges + }; + + // iterate over items in range list, no need to iterate over first as it is included by default + foreach (var range in rangeList.Skip(1)) + { + var last = resultList[resultList.Count - 1]; // take end of result to process over (is first in first iteration) + + // can be assumed that assume that the range head is greater than or equal to last head because everything is ordered + + // if TryMerge succeeded then overlap exists, merge overlap and re-assign to end of results + if (TryMerge(last, range, out var merge)) + { + resultList[resultList.Count - 1] = merge; // overwrite with merged values + continue; + } + + // could not merge, simply add to end and iterate to next + resultList.Add(range); + } + + result = resultList; + return true; + } + + /// + /// Rebuild the initial range as an of ranges excluding the excluded ranges + /// excluded ranges are expected to each be sub ranges of the initial range + /// an attempt will be made to TryCollapseAll the excluded + /// + /// the initial to exclude from + /// + /// the various to exclude from the + /// + /// + /// the resulting + /// unexpected invalid operation. + /// true on success + public static bool TryExcludeAll( + IPAddressRange initialRange, + IEnumerable excludedRanges, + out IEnumerable result + ) + { + // TODO the logical flow here is confusing, see if it can be cleaned up a bit + + if (initialRange is null || excludedRanges is null) + { + result = Enumerable.Empty(); + return false; + } + + var excludedRangesList = excludedRanges as IList ?? excludedRanges.ToList(); + + // item null check + if (excludedRangesList.Any(r => r is null)) + { + result = Enumerable.Empty(); + return false; + } + + // no rangers to exclude, out copy of original + if (!excludedRangesList.Any()) + { + result = new List { new IPAddressRange(initialRange.Head, initialRange.Tail) }; + + return true; + } + + // all families don't match match + if (excludedRangesList.Any(r => r.AddressFamily != initialRange.AddressFamily)) + { + result = Enumerable.Empty(); + return false; + } + + // results is initialized with a *copy* of initialRange + var resultList = new List { new IPAddressRange(initialRange.Head, initialRange.Tail) }; + + foreach (var exclusion in excludedRangesList) + { + var last = resultList[resultList.Count - 1]; + var lastIndex = resultList.Count - 1; + + if (exclusion.Contains(last)) + { + resultList.RemoveAt(lastIndex); + break; // can't loop any more + } + + if (exclusion.Contains(initialRange.Tail)) + { + var head = resultList[lastIndex].Head; + var tail = exclusion.Head.Increment(-1); + resultList[lastIndex] = new IPAddressRange(head, tail); + continue; // can't loop any more + } + + // exclusion contains head + if (exclusion.Contains(last.Head)) + { + // push head one point beyond tail of exclusion + var head = exclusion.Tail.Increment(); + var tail = resultList[lastIndex].Tail; + resultList[lastIndex] = new IPAddressRange(head, tail); + continue; // next iteration + } + + // exclusion is within last, carve exclusion to two pieces + if (!last.Overlaps(exclusion)) + { + throw new InvalidOperationException("An unexpected overlap check operation occurred"); // should not have made it here + } + + resultList[lastIndex] = new IPAddressRange(resultList[lastIndex].Head, exclusion.Head.Increment(-1)); + + // carve out a piece at starting at exclusion head, ending at exclusion tail + // thus meaning one less than head at previous, and one more at head at next + resultList.Add(new IPAddressRange(exclusion.Tail.Increment(), initialRange.Tail)); + } + + result = resultList; return true; - } + } + + /// + /// Merge two touching or overlapping address ranges + /// + /// the left operand + /// the right operand + /// the resulting + /// true on success + public static bool TryMerge(IPAddressRange left, IPAddressRange right, out IPAddressRange mergedRange) + { + if (left is null || right is null || left.AddressFamily != right.AddressFamily) + { + mergedRange = null; + return false; + } + + // overlap or touch occurs + if (left.Overlaps(right) || left.Touches(right)) + { + var newHead = IPAddressMath.Min(left.Head, right.Head); + var newTail = IPAddressMath.Max(left.Tail, right.Tail); + mergedRange = new IPAddressRange(newHead, newTail); - mergedRange = null; - return false; - } + return true; + } + + mergedRange = null; + return false; + } - #endregion // end: static methods - } + #endregion // end: static methods + } } diff --git a/src/Arcus/MacAddress.cs b/src/Arcus/MacAddress.cs index 78823a3..3cbf0d8 100644 --- a/src/Arcus/MacAddress.cs +++ b/src/Arcus/MacAddress.cs @@ -5,650 +5,628 @@ using System.Runtime.Serialization; using System.Text.RegularExpressions; using Gulliver; -using JetBrains.Annotations; namespace Arcus { - /// Representation of a 48-bit MAC Address (EUI-48 & MAC-48) - /// - /// - /// see IEEE "Guidelines for Use of Extended Unique Identifier (EUI), Organizationally Unique Identifier (OUI), and - /// Company ID (CID)": - /// https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/eui.pdf - /// - /// - [PublicAPI] - [Serializable] - public class MacAddress : IEquatable, - IComparable, - IComparable, - IFormattable, - ISerializable - { - /// - /// MAC Address Regular Expression pattern for matching: - /// - /// - /// - /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, - /// separated by a dash ("-") - /// - /// - /// - /// six groups of two hexadecimal digits separated by colons (":") - /// - /// - /// six groups of two hexadecimal digits separated by spaces (" ") - /// - /// - /// 12 hexadecimal digits with no separation - /// - /// - /// Cisco three groups of four hexadecimal digits separated by dots (".") - /// - /// - /// - /// - /// this regular expression pattern is expected to be used in matches ignoring case - /// - public const string AllFormatMacAddressPattern = - @"^(?:[0-9A-F]{2}([-: ]?))(?:[0-9A-F]{2}\1){4}[0-9A-F]{2}$|^(?:[0-9A-F]{4}\.){2}[0-9A-F]{4}$|^(?:[0-9A-F]{3}\.){3}[0-9A-F]{3}$"; - - /// - /// MAC Address Regular Expression pattern for matching the "common" six groups of two uppercase hexadecimal digits - /// separated by colons(":") - /// - /// - /// this regular expression pattern is expected to be used in matches ignoring case - /// - public const string CommonFormatMacAddressPattern = @"^(?:[\dA-F]{2}:){5}[\dA-F]{2}$"; - - /// A MAC Address that represents the default or case. Equal to FF:FF:FF:FF:FF:FF. - public static readonly MacAddress DefaultMacAddress = new MacAddress(Enumerable.Repeat((byte)0xFF, 6)); - - /// - /// MAC Address Regular Expression matching - /// - /// - /// - /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, - /// separated by a dash ("-") - /// - /// - /// - /// six groups of two hexadecimal digits separated by colons (":") - /// - /// - /// six groups of two hexadecimal digits separated by spaces (" ") - /// - /// - /// 12 hexadecimal digits with no separation - /// - /// - /// Cisco three groups of four hexadecimal digits separated by dots (".") - /// - /// - /// - /// - /// see for pattern. - /// - public static readonly Regex AllFormatMacAddressRegularExpression = - new Regex(AllFormatMacAddressPattern, - RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - - /// - /// MAC Address Regular Expression for matching the "common" six groups of two hexadecimal digits separated by colons - /// (":"). uses - /// - /// - /// see for pattern. - /// - public static readonly Regex CommonFormatMacAddressRegularExpression = - new Regex(CommonFormatMacAddressPattern, - RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - - /// The inner byte representation fo the MAC Address - private readonly byte[] _address; - - /// Checks if the MAC Address is unicast - /// returns if the MAC Address is unicast - public bool IsUnicast => (byte)(this._address[0] & 1) == 0; - - /// Checks if the MAC Address is multicast - /// returns if the MAC Address is multicast - public bool IsMulticast => (byte)(this._address[0] & 1) != 0; - - /// Checks if the MAC Address is globally unique (OUI enforced) - /// returns if the MAC Address is globally unique - public bool IsGloballyUnique => (byte)(this._address[0] & (1 << 1)) == 0; - - /// Checks if the MAC Address is locally administered - /// returns if the MAC Address is locally administered - public bool IsLocallyAdministered => (byte)(this._address[0] & (1 << 1)) != 0; - - /// Checks if the MAC Address is the EUI-48 default - /// returns if all bits of the MAC Address are set - public bool IsDefault => this.Equals(DefaultMacAddress); - - /// Checks if the MAC Address is "unusable" - /// returns if all the OUI bits of the MAC Address are unset - public bool IsUnusable => this._address.Take(3) - .Any(b => b != 0); - - #region From Interface IComparable - - /// - public int CompareTo(object obj) - { - return ReferenceEquals(null, obj) - ? 1 - : ReferenceEquals(this, obj) - ? 0 - : obj is MacAddress other - ? this.CompareTo(other) - : throw new ArgumentException($"Object must be of type {nameof(MacAddress)}"); - } - - #endregion - - #region From Interface IComparable - - /// - public int CompareTo(MacAddress other) - { - return ReferenceEquals(null, other) - ? 1 - : ByteArrayUtils.CompareUnsignedBigEndian(this._address, other._address); - } - - #endregion - - #region From Interface IEquatable - - /// - public bool Equals(MacAddress other) - { - return !ReferenceEquals(null, other) - && (ReferenceEquals(this, other) - || ByteArrayUtils.CompareUnsignedBigEndian(this._address, other._address) == 0); - } - - #endregion - - #region From Interface IFormattable - - /// - /// - /// Express as a string - /// The following formats are provided - /// - /// - /// g, empty string, or - /// - /// General format of uppercase hexadecimal encoded bytes separated by colons. eg - /// AA:BB:CC:DD:EE:FF - /// - /// - /// - /// H - /// formats as contiguous upper case hexadecimal digits. eg AABBCCDDEEFF - /// - /// - /// h - /// formats as contiguous lower case hexadecimal digits. eg aabbccddeeff - /// - /// - /// c - /// - /// Cisco three groups of four uppercase hexadecimal digits separated by dots format. eg - /// AAAA.BBBB.CCCC - /// - /// - /// - /// s - /// hexadecimal encoded bytes separated by a space character. eg AA BB CC DD EE FF - /// - /// - /// d - /// hexadecimal encoded bytes separated by a dash character. eg AA-BB-CC-DD-EE-FF - /// - /// - /// i formatted as a big-endian integer value - /// - /// - /// - /// the format specifier - /// the format provider - /// a byte array represented as a string - /// Invalid base conversion. - /// provided format is not supported. - public string ToString(string format, - IFormatProvider formatProvider) - { - if (formatProvider == null) - { - formatProvider = CultureInfo.InvariantCulture; - } - - switch (format?.Trim()) - { - case null: // general formats - case "": - case "g": - return DelimitedBaseConverter(this._address, 16, ":") - .ToUpperInvariant(); - case "x": // lower hexadecimal - return DelimitedBaseConverter(this._address, 16); - case "X": // upper hexadecimal contiguous - return DelimitedBaseConverter(this._address, 16) - .ToUpperInvariant(); - case "c": // Cisco - return AsCisco(); - case "s": // space delimited - return DelimitedBaseConverter(this._address, 16, " ") - .ToUpperInvariant(); - case "i": // integer value - return this._address.ToString("I", formatProvider); - case "d": // dash delimited - return DelimitedBaseConverter(this._address, 16, "-") - .ToUpperInvariant(); - default: - throw new FormatException($"The \"{format}\" format string is not supported."); - } - - string AsCisco() - { - return string.Join(".", - Enumerable.Range(0, 3) - .Select(i => - { - var j = i * 2; - return $"{this._address[j]:X2}{this._address[j + 1]:X2}"; - })); - } - - string DelimitedBaseConverter(byte[] input, - int @base, - string delimiter = "", - int? paddingWidth = null, - char paddingChar = '0') - { - if (!new[] { 2, 8, 10, 16 }.Contains(@base)) + /// Representation of a 48-bit MAC Address (EUI-48 & MAC-48) + /// + /// + /// see IEEE "Guidelines for Use of Extended Unique Identifier (EUI), Organizationally Unique Identifier (OUI), and + /// Company ID (CID)": + /// https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/eui.pdf + /// + /// + [Obsolete( + "Candidate for removal in future major version. This, or something like it, may be more appropriate in a separate package" + )] + [Serializable] + public class MacAddress : +#if NETSTANDARD2_0 + ISerializable, +#endif + IEquatable, IComparable, IComparable, IFormattable + { + /// + /// MAC Address Regular Expression pattern for matching: + /// + /// + /// + /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, + /// separated by a dash ("-") + /// + /// + /// + /// six groups of two hexadecimal digits separated by colons (":") + /// + /// + /// six groups of two hexadecimal digits separated by spaces (" ") + /// + /// + /// 12 hexadecimal digits with no separation + /// + /// + /// Cisco three groups of four hexadecimal digits separated by dots (".") + /// + /// + /// + /// + /// this regular expression pattern is expected to be used in matches ignoring case + /// + public const string AllFormatMacAddressPattern = + @"^(?:[0-9A-F]{2}([-: ]?))(?:[0-9A-F]{2}\1){4}[0-9A-F]{2}$|^(?:[0-9A-F]{4}\.){2}[0-9A-F]{4}$|^(?:[0-9A-F]{3}\.){3}[0-9A-F]{3}$"; + + /// + /// MAC Address Regular Expression pattern for matching the "common" six groups of two uppercase hexadecimal digits + /// separated by colons(":") + /// + /// + /// this regular expression pattern is expected to be used in matches ignoring case + /// + public const string CommonFormatMacAddressPattern = @"^(?:[\dA-F]{2}:){5}[\dA-F]{2}$"; + + /// A MAC Address that represents the default or case. Equal to FF:FF:FF:FF:FF:FF. + public static readonly MacAddress DefaultMacAddress = new MacAddress(Enumerable.Repeat((byte)0xFF, 6)); + + /// + /// MAC Address Regular Expression matching + /// + /// + /// + /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, + /// separated by a dash ("-") + /// + /// + /// + /// six groups of two hexadecimal digits separated by colons (":") + /// + /// + /// six groups of two hexadecimal digits separated by spaces (" ") + /// + /// + /// 12 hexadecimal digits with no separation + /// + /// + /// Cisco three groups of four hexadecimal digits separated by dots (".") + /// + /// + /// + /// + /// see for pattern. + /// + public static readonly Regex AllFormatMacAddressRegularExpression = new Regex( + AllFormatMacAddressPattern, + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + ); + + /// + /// MAC Address Regular Expression for matching the "common" six groups of two hexadecimal digits separated by colons + /// (":"). uses + /// + /// + /// see for pattern. + /// + public static readonly Regex CommonFormatMacAddressRegularExpression = new Regex( + CommonFormatMacAddressPattern, + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + ); + + /// The inner byte representation fo the MAC Address + private readonly byte[] _address; + + /// Gets a value indicating whether checks if the MAC Address is unicast + /// returns if the MAC Address is unicast + public bool IsUnicast => (byte)(this._address[0] & 1) == 0; + + /// Gets a value indicating whether checks if the MAC Address is multicast + /// returns if the MAC Address is multicast + public bool IsMulticast => (byte)(this._address[0] & 1) != 0; + + /// Gets a value indicating whether checks if the MAC Address is globally unique (OUI enforced) + /// returns if the MAC Address is globally unique + public bool IsGloballyUnique => (byte)(this._address[0] & (1 << 1)) == 0; + + /// Gets a value indicating whether checks if the MAC Address is locally administered + /// returns if the MAC Address is locally administered + public bool IsLocallyAdministered => (byte)(this._address[0] & (1 << 1)) != 0; + + /// Gets a value indicating whether checks if the MAC Address is the EUI-48 default + /// returns if all bits of the MAC Address are set + public bool IsDefault => this.Equals(DefaultMacAddress); + + /// Gets a value indicating whether checks if the MAC Address is "unusable" + /// returns if all the OUI bits of the MAC Address are unset + public bool IsUnusable => this._address.Take(3).Any(b => b != 0); + + #region From Interface IComparable + + /// + public int CompareTo(object obj) + { + if (ReferenceEquals(null, obj)) { - throw new - ArgumentException($"{nameof(@base)} must be 2, 8, 10, or 16 (binary, octal, decimal, or hexadecimal respectively)", - nameof(@base)); + return 1; + } + else if (ReferenceEquals(this, obj)) + { + return 0; + } + else if (obj is MacAddress other) + { + return this.CompareTo(other); + } + + throw new ArgumentException($"Object must be of type {nameof(MacAddress)}"); + } + + #endregion + + #region From Interface IComparable + + /// + public int CompareTo(MacAddress other) + { + return ReferenceEquals(null, other) ? 1 : ByteArrayUtils.CompareUnsignedBigEndian(this._address, other._address); + } + + #endregion + + #region From Interface IEquatable + + /// + public bool Equals(MacAddress other) + { + return !ReferenceEquals(null, other) + && ( + ReferenceEquals(this, other) || ByteArrayUtils.CompareUnsignedBigEndian(this._address, other._address) == 0 + ); + } + + #endregion + + #region From Interface IFormattable + + /// + /// + /// Express as a string + /// The following formats are provided + /// + /// + /// g, empty string, or + /// + /// General format of uppercase hexadecimal encoded bytes separated by colons. eg + /// AA:BB:CC:DD:EE:FF + /// + /// + /// + /// H + /// formats as contiguous upper case hexadecimal digits. eg AABBCCDDEEFF + /// + /// + /// h + /// formats as contiguous lower case hexadecimal digits. eg aabbccddeeff + /// + /// + /// c + /// + /// Cisco three groups of four uppercase hexadecimal digits separated by dots format. eg + /// AAAA.BBBB.CCCC + /// + /// + /// + /// s + /// hexadecimal encoded bytes separated by a space character. eg AA BB CC DD EE FF + /// + /// + /// d + /// hexadecimal encoded bytes separated by a dash character. eg AA-BB-CC-DD-EE-FF + /// + /// + /// i formatted as a big-endian integer value + /// + /// + /// + /// the format specifier + /// the format provider + /// a byte array represented as a string + /// Invalid base conversion. + /// provided format is not supported. + public string ToString(string format, IFormatProvider formatProvider) + { + if (formatProvider == null) + { + formatProvider = CultureInfo.InvariantCulture; + } + + switch (format?.Trim()) + { + case null: // general formats + case "": + case "g": + return DelimitedBaseConverter(this._address, 16, ":").ToUpperInvariant(); + case "x": // lower hexadecimal + return DelimitedBaseConverter(this._address, 16); + case "X": // upper hexadecimal contiguous + return DelimitedBaseConverter(this._address, 16).ToUpperInvariant(); + case "c": // Cisco + return AsCisco(); + case "s": // space delimited + return DelimitedBaseConverter(this._address, 16, " ").ToUpperInvariant(); + case "i": // integer value + return this._address.ToString("I", formatProvider); + case "d": // dash delimited + return DelimitedBaseConverter(this._address, 16, "-").ToUpperInvariant(); + default: + throw new FormatException($"The \"{format}\" format string is not supported."); + } + + string AsCisco() + { + return string.Join( + ".", + Enumerable + .Range(0, 3) + .Select(i => + { + var j = i * 2; + return $"{this._address[j]:X2}{this._address[j + 1]:X2}"; + }) + ); + } + + string DelimitedBaseConverter( + byte[] input, + int @base, + string delimiter = "", + int? paddingWidth = null, + char paddingChar = '0' + ) + { + if (!new[] { 2, 8, 10, 16 }.Contains(@base)) + { + throw new ArgumentException( + $"{nameof(@base)} must be 2, 8, 10, or 16 (binary, octal, decimal, or hexadecimal respectively)", + nameof(@base) + ); + } + + if (paddingWidth.HasValue && paddingWidth.Value < 0) + { + throw new ArgumentException($"{nameof(paddingWidth)} may not be negative", nameof(paddingWidth)); + } + + // use padding width if defined, otherwise use a width of 8 for binary, 3 for octal and decimal, and 2 for hexadecimal + var width = + paddingWidth + ?? ( + @base == 2 ? 8 + : @base == 8 || @base == 10 ? 3 + : 2 + ); + + return string.Join( + delimiter ?? string.Empty, + input.Select(b => Convert.ToString(b, @base).PadLeft(width, paddingChar)) + ); + } + } + + #endregion + +#if NETSTANDARD2_0 + #region From Interface ISerializable + + /// + /// is + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + info.AddValue(nameof(this._address), this._address); + } + + #endregion +#endif + + /// Gets a copy of the underlying big-endian bytes of the MAC Address + /// The bytes of the MAC Address + public byte[] GetAddressBytes() + { + return this._address.ToArray(); + } + + /// Gets the Organizationally Unique Identifier (OUI) of the address + /// A copy of the first 3-bytes (24-bits) of the MAC Address + public byte[] GetOuiBytes() + { + return this._address.Take(3).ToArray(); + } + + /// Gets the Company ID (CID) of the address + /// A copy of the last 3-bytes (24-bits) of the MAC Address + public byte[] GetCidBytes() + { + return this._address.Skip(3).Take(3).ToArray(); + } + + /// + public override string ToString() + { + return this.ToString("g", CultureInfo.InvariantCulture); + } + + /// + public override bool Equals(object obj) + { + return !ReferenceEquals(null, obj) + && (ReferenceEquals(this, obj) || (obj.GetType() == GetType() && this.Equals((MacAddress)obj))); + } + + /// + public override int GetHashCode() => + HashCode.Combine(_address[0], _address[1], _address[2], _address[3], _address[4], _address[5]); + + #region Ctors + + /// Initializes a new instance of the class. + /// serialization info + /// serialization context + /// is + protected MacAddress(SerializationInfo info, StreamingContext context) + : this((byte[])(info ?? throw new ArgumentNullException(nameof(info))).GetValue(nameof(_address), typeof(byte[]))) + { } + + /// Initializes a new instance of the class. + /// a collection of bytes to be copied as the byte of the MAC Address + /// must be exactly 6 bytes long + /// is + public MacAddress(IEnumerable bytes) + { + if (bytes is null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + var address = bytes.ToArray(); // explicit copy + + if (address.Length != 6) + { + throw new ArgumentException("must be exactly 6 bytes long", nameof(bytes)); + } + + this._address = address; + } + + #endregion end: Ctors + + #region factory + + /// Attempt to extract hex characters from string and attempt to identify exactly 6 hextets to make a MAC Address + /// the string input + /// a mac address + /// could not create 6 hextets + /// A time-out occurred + private static MacAddress ExtractHextetsAndCreateMacAddress(string input) + { + // match and gather all hex characters + const string hexCharactersPattern = @"([\dA-F]+)+"; + var matchValues = new Regex( + hexCharactersPattern, + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + ); + + // remove all non hexadecimal characters from the input, upper case the result + var matches = matchValues.Matches(input).OfType().Select(match => match.Value); + + var join = string.Concat(matches); + + if (join.Length != 12) + { + throw new ArgumentException("expecting 6 hextets", nameof(input)); + } + + var bytes = Enumerable + .Range(0, 6) + .Select(i => join.Substring(2 * i, 2)) + .Select(sb => byte.Parse(sb, NumberStyles.HexNumber, CultureInfo.InvariantCulture)); + + return new MacAddress(bytes); + } + + #region Parse + + #region Parse string + + /// + /// Parse a string input, given one of the following formats, in order to create a MAC Address + /// + /// + /// + /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, + /// separated by a dash ("-") + /// + /// + /// + /// six groups of two hexadecimal digits separated by colons (":") + /// + /// + /// six groups of two hexadecimal digits separated by spaces (" ") + /// + /// + /// 12 hexadecimal digits with no separation + /// + /// + /// Cisco three groups of four hexadecimal digits separated by dots (".") + /// + /// + /// + /// the input to parse + /// a + /// is or whitespace. + /// A time-out occurred + public static MacAddress Parse(string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(input)); } - if (paddingWidth.HasValue - && paddingWidth.Value < 0) + if (!AllFormatMacAddressRegularExpression.IsMatch(input)) { - throw new ArgumentException($"{nameof(paddingWidth)} may not be negative", nameof(paddingWidth)); + throw new ArgumentException("does not match known MAC Address formats", nameof(input)); } - // use padding width if defined, otherwise use a width of 8 for binary, 3 for octal and decimal, and 2 for hexadecimal - var width = paddingWidth - ?? (@base == 2 - ? 8 - : @base == 8 || @base == 10 - ? 3 - : 2); - - return string.Join(delimiter ?? string.Empty, - input.Select(b => Convert.ToString(b, @base) - .PadLeft(width, paddingChar))); - } - } - - #endregion - - #region From Interface ISerializable - - /// - /// is - public void GetObjectData([NotNull] SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - info.AddValue(nameof(this._address), this._address); - } - - #endregion - - /// Gets a copy of the underlying big-endian bytes of the MAC Address - /// The bytes of the MAC Address - [NotNull] - public byte[] GetAddressBytes() - { - return this._address.ToArray(); - } - - /// Gets the Organizationally Unique Identifier (OUI) of the address - /// A copy of the first 3-bytes (24-bits) of the MAC Address - [NotNull] - public byte[] GetOuiBytes() - { - return this._address.Take(3) - .ToArray(); - } - - /// Gets the Company ID (CID) of the address - /// A copy of the last 3-bytes (24-bits) of the MAC Address - [NotNull] - public byte[] GetCidBytes() - { - return this._address.Skip(3) - .Take(3) - .ToArray(); - } - - /// - public override string ToString() - { - return this.ToString("g", CultureInfo.InvariantCulture); - } - - /// - public override bool Equals(object obj) - { - return !ReferenceEquals(null, obj) - && (ReferenceEquals(this, obj) - || (obj.GetType() == GetType() && this.Equals((MacAddress)obj))); - } - - /// - public override int GetHashCode() - { - return this._address.Aggregate(0, - (i, - b) => unchecked((i * 17) + b)); - } - - #region Ctors - - /// Initializes a new instance of the class. - /// serialization info - /// serialization context - /// is - protected MacAddress([NotNull] SerializationInfo info, -#pragma warning disable CA1801 - /* CA1801 Parameter context of method .ctor is never used. Remove the parameter or use it in the method body. - * parameter "StreamingContext context" is required for proper desearilization - */ - StreamingContext context) -#pragma warning restore CA1801 - : this((byte[])(info ?? throw new ArgumentNullException(nameof(info))).GetValue(nameof(_address), - typeof(byte[]))) - { } - - /// Initializes a new instance of the class. - /// a collection of bytes to be copied as the byte of the MAC Address - /// must be exactly 6 bytes long - /// is - public MacAddress([NotNull] IEnumerable bytes) - { - if (bytes is null) - { - throw new ArgumentNullException(nameof(bytes)); - } - - var address = bytes.ToArray(); // explicit copy - - if (address.Length != 6) - { - throw new ArgumentException("must be exactly 6 bytes long", nameof(bytes)); - } - - this._address = address; - } - - #endregion end: Ctors - - #region factory - - /// Attempt to extract hex characters from string and attempt to identify exactly 6 hextets to make a MAC Address - /// the string input - /// a mac address - /// could not create 6 hextets - /// A time-out occurred - private static MacAddress ExtractHextetsAndCreateMacAddress(string input) - { - // match and gather all hex characters - const string hexCharactersPattern = @"([\dA-F]+)+"; - var matchValues = new Regex(hexCharactersPattern, - RegexOptions.Compiled - | RegexOptions.CultureInvariant - | RegexOptions.IgnoreCase); - - // remove all non hexadecimal characters from the input, upper case the result - var matches = matchValues.Matches(input) - .OfType() - .Select(match => match.Value); - - var join = string.Join(string.Empty, matches); - - if (join.Length != 12) - { - throw new ArgumentException("expecting 6 hextets", nameof(input)); - } - - var bytes = Enumerable.Range(0, 6) - .Select(i => join.Substring(2 * i, 2)) - .Select(sb => byte.Parse(sb, NumberStyles.HexNumber, CultureInfo.InvariantCulture)); - - return new MacAddress(bytes); - } - - #region Parse - - #region Parse string - - /// - /// Parse a string input, given one of the following formats, in order to create a MAC Address - /// - /// - /// - /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, - /// separated by a dash ("-") - /// - /// - /// - /// six groups of two hexadecimal digits separated by colons (":") - /// - /// - /// six groups of two hexadecimal digits separated by spaces (" ") - /// - /// - /// 12 hexadecimal digits with no separation - /// - /// - /// Cisco three groups of four hexadecimal digits separated by dots (".") - /// - /// - /// - /// the input to parse - /// a - /// is or whitespace. - /// A time-out occurred - [NotNull] - public static MacAddress Parse([NotNull] string input) - { - if (string.IsNullOrWhiteSpace(input)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(input)); - } - - if (!AllFormatMacAddressRegularExpression.IsMatch(input)) - { - throw new ArgumentException("does not match known MAC Address formats", nameof(input)); - } - - return ExtractHextetsAndCreateMacAddress(input); - } - - /// - /// Try to parse a string input, given one of the following formats, in order to create a MAC Address - /// - /// - /// - /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, - /// separated by a dash ("-") - /// - /// - /// - /// six groups of two hexadecimal digits separated by colons (":") - /// - /// - /// six groups of two hexadecimal digits separated by spaces (" ") - /// - /// - /// 12 hexadecimal digits with no separation - /// - /// - /// Cisco three groups of four hexadecimal digits separated by dots (".") - /// - /// - /// - /// the input to parse - /// a created on successful parse - /// iff the the parse was successful - public static bool TryParse(string input, - [CanBeNull] out MacAddress macAddress) - { - try - { - macAddress = Parse(input); - return true; - } -#pragma warning disable CA1031 // Modify 'TryParseAny' to catch a more specific exception type, or rethrow the exception. - catch // explicitly catching all for the sake of standard bool Try*(out) pattern -#pragma warning restore CA1031 - { - macAddress = null; - return false; - } - } - - #endregion end: Parse string - - #region ParseAny string - - /// Parse a string disregarding all non hexadecimal digits in order to create a MAC Address - /// the input to parse - /// a - /// is or whitespace. - /// - /// A time-out occurred. For more information about time-outs, see the Remarks - /// section. - /// - [NotNull] - public static MacAddress ParseAny([NotNull] string input) - { - if (string.IsNullOrWhiteSpace(input)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(input)); - } - - return ExtractHextetsAndCreateMacAddress(input); - } - - /// Try to parse a string disregarding all non hexadecimal digits in order to create a MAC Address - /// the input to parse - /// a created on successful parse - /// if the the parse was successful - public static bool TryParseAny(string input, - [CanBeNull] out MacAddress macAddress) - { - try - { - macAddress = ParseAny(input); - return true; - } -#pragma warning disable CA1031 // Modify 'TryParseAny' to catch a more specific exception type, or rethrow the exception. - catch // explicitly catching all for the sake of standard bool Try*(out) pattern -#pragma warning restore CA1031 - { - macAddress = null; - return false; - } - } - - #endregion end: ParseAny - - #endregion end: Parse - - #endregion end: factory - - #region operators - - /// Check equality of and - /// the left operand - /// the right operand - /// iff both and are equal - public static bool operator ==(MacAddress left, - MacAddress right) - { - return Equals(left, right); - } - - /// Check inequality of and - /// the left operand - /// the right operand - /// iff both and are not equal - public static bool operator !=(MacAddress left, - MacAddress right) - { - return !Equals(left, right); - } - - /// Check if is less than - /// the left operand - /// the right operand - /// iff is less than - public static bool operator <(MacAddress left, - MacAddress right) - { - return Comparer.Default.Compare(left, right) < 0; - } - - /// Check if is greater than - /// the left operand - /// the right operand - /// iff is greater than - public static bool operator >(MacAddress left, - MacAddress right) - { - return Comparer.Default.Compare(left, right) > 0; - } - - /// Check if is less than or equal to - /// the left operand - /// the right operand - /// - /// iff is less than or equal to - /// - public static bool operator <=(MacAddress left, - MacAddress right) - { - return Comparer.Default.Compare(left, right) <= 0; - } - - /// Check if is greater than or equal to - /// the left operand - /// the right operand - /// - /// iff is greater than or equal to - /// - public static bool operator >=(MacAddress left, - MacAddress right) - { - return Comparer.Default.Compare(left, right) >= 0; - } - - #endregion end: operators - } + return ExtractHextetsAndCreateMacAddress(input); + } + + /// + /// Try to parse a string input, given one of the following formats, in order to create a MAC Address + /// + /// + /// + /// IEEE 802 format for printing EUI-48 & MAC-48 addresses in six groups of two hexadecimal digits, + /// separated by a dash ("-") + /// + /// + /// + /// six groups of two hexadecimal digits separated by colons (":") + /// + /// + /// six groups of two hexadecimal digits separated by spaces (" ") + /// + /// + /// 12 hexadecimal digits with no separation + /// + /// + /// Cisco three groups of four hexadecimal digits separated by dots (".") + /// + /// + /// + /// the input to parse + /// a created on successful parse + /// if, and only if, the parse was successful + public static bool TryParse(string input, out MacAddress macAddress) + { + try + { + macAddress = Parse(input); + return true; + } + catch // explicitly catching all for the sake of standard bool Try*(out) pattern + { + macAddress = null; + return false; + } + } + + #endregion end: Parse string + + #region ParseAny string + + /// Parse a string disregarding all non hexadecimal digits in order to create a MAC Address + /// the input to parse + /// a + /// is or whitespace. + /// + /// A time-out occurred. For more information about time-outs, see the Remarks + /// section. + /// + public static MacAddress ParseAny(string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(input)); + } + + return ExtractHextetsAndCreateMacAddress(input); + } + + /// Try to parse a string disregarding all non hexadecimal digits in order to create a MAC Address + /// the input to parse + /// a created on successful parse + /// if the parse was successful + public static bool TryParseAny(string input, out MacAddress macAddress) + { + try + { + macAddress = ParseAny(input); + return true; + } + catch // explicitly catching all for the sake of standard bool Try*(out) pattern + { + macAddress = null; + return false; + } + } + + #endregion end: ParseAny + + #endregion end: Parse + + #endregion end: factory + + #region operators + + /// Check equality of and + /// the left operand + /// the right operand + /// if, and only if, both and are equal + public static bool operator ==(MacAddress left, MacAddress right) + { + return Equals(left, right); + } + + /// Check inequality of and + /// the left operand + /// the right operand + /// if, and only if, both and are not equal + public static bool operator !=(MacAddress left, MacAddress right) + { + return !Equals(left, right); + } + + /// Check if is less than + /// the left operand + /// the right operand + /// if, and only if, is less than + public static bool operator <(MacAddress left, MacAddress right) + { + return Comparer.Default.Compare(left, right) < 0; + } + + /// Check if is greater than + /// the left operand + /// the right operand + /// if, and only if, is greater than + public static bool operator >(MacAddress left, MacAddress right) + { + return Comparer.Default.Compare(left, right) > 0; + } + + /// Check if is less than or equal to + /// the left operand + /// the right operand + /// + /// if, and only if, is less than or equal to + /// + public static bool operator <=(MacAddress left, MacAddress right) + { + return Comparer.Default.Compare(left, right) <= 0; + } + + /// Check if is greater than or equal to + /// the left operand + /// the right operand + /// + /// if, and only if, is greater than or equal to + /// + public static bool operator >=(MacAddress left, MacAddress right) + { + return Comparer.Default.Compare(left, right) >= 0; + } + + #endregion end: operators + } } diff --git a/src/Arcus/Math/IPAddressMath.cs b/src/Arcus/Math/IPAddressMath.cs index 687d232..93b4690 100644 --- a/src/Arcus/Math/IPAddressMath.cs +++ b/src/Arcus/Math/IPAddressMath.cs @@ -3,7 +3,6 @@ using System.Net; using Arcus.Utilities; using Gulliver; -using JetBrains.Annotations; using static System.Net.Sockets.AddressFamily; namespace Arcus.Math @@ -25,9 +24,7 @@ public static class IPAddressMath /// Increment caused address underflow /// Increment caused address overflow /// is . - [NotNull] - public static IPAddress Increment([NotNull] this IPAddress input, - long delta = 1) + public static IPAddress Increment(this IPAddress input, long delta = 1) { #region defense @@ -38,7 +35,10 @@ public static IPAddress Increment([NotNull] this IPAddress input, if (!IPAddressUtilities.ValidAddressFamilies.Contains(input.AddressFamily)) { - throw new ArgumentException($"must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(input)); + throw new ArgumentException( + $"must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(input) + ); } #endregion // end: defense @@ -53,9 +53,7 @@ public static IPAddress Increment([NotNull] this IPAddress input, throw new InvalidOperationException("could not increment address"); } - var addressByteWidth = input.IsIPv4() - ? IPAddressUtilities.IPv4ByteCount - : IPAddressUtilities.IPv6ByteCount; + var addressByteWidth = input.IsIPv4() ? IPAddressUtilities.IPv4ByteCount : IPAddressUtilities.IPv6ByteCount; if (byteResult.Length > addressByteWidth) { @@ -72,9 +70,8 @@ public static IPAddress Increment([NotNull] this IPAddress input, /// the to increment /// the resulting post increment /// the amount to increment by - public static bool TryIncrement([CanBeNull] IPAddress input, - [CanBeNull] out IPAddress address, - long delta = 1) + /// true on success + public static bool TryIncrement(IPAddress input, out IPAddress address, long delta = 1) { if (input == null) { @@ -87,9 +84,7 @@ public static bool TryIncrement([CanBeNull] IPAddress input, address = input.Increment(delta); return true; } -#pragma warning disable CA1031 // catch is purposely general catch -#pragma warning restore CA1031 { address = null; return false; @@ -105,12 +100,10 @@ public static bool TryIncrement([CanBeNull] IPAddress input, /// /// first operand /// second operand - public static bool IsEqualTo([CanBeNull] this IPAddress left, - [CanBeNull] IPAddress right) + /// true if is logically equal to + public static bool IsEqualTo(this IPAddress left, IPAddress right) { - return ReferenceEquals(left, right) - || (!ReferenceEquals(left, null) - && left.Equals(right)); + return ReferenceEquals(left, right) || (!ReferenceEquals(left, null) && left.Equals(right)); } /// @@ -118,15 +111,16 @@ public static bool IsEqualTo([CanBeNull] this IPAddress left, /// /// first operand /// second operand - public static bool IsGreaterThan([CanBeNull] this IPAddress left, - [CanBeNull] IPAddress right) + /// true if is logically greater than + public static bool IsGreaterThan(this IPAddress left, IPAddress right) { - if (ReferenceEquals(left, right) - || (!ReferenceEquals(left, null) - && left.Equals(right)) + if ( + ReferenceEquals(left, right) + || (!ReferenceEquals(left, null) && left.Equals(right)) || left == null || right == null - || left.AddressFamily != right.AddressFamily) + || left.AddressFamily != right.AddressFamily + ) { return false; } @@ -139,12 +133,15 @@ public static bool IsGreaterThan([CanBeNull] this IPAddress left, /// /// first operand /// second operand - public static bool IsGreaterThanOrEqualTo([CanBeNull] this IPAddress left, - [CanBeNull] IPAddress right) + /// true if is logically greater than or equal to + public static bool IsGreaterThanOrEqualTo(this IPAddress left, IPAddress right) { return ReferenceEquals(left, right) - || (!ReferenceEquals(left, null) && left.Equals(right)) - || (left?.AddressFamily == right?.AddressFamily && ByteArrayUtils.CompareUnsignedBigEndian(left?.GetAddressBytes(), right?.GetAddressBytes()) >= 0); + || (!ReferenceEquals(left, null) && left.Equals(right)) + || ( + left?.AddressFamily == right?.AddressFamily + && ByteArrayUtils.CompareUnsignedBigEndian(left?.GetAddressBytes(), right?.GetAddressBytes()) >= 0 + ); } /// @@ -152,15 +149,11 @@ public static bool IsGreaterThanOrEqualTo([CanBeNull] this IPAddress left, /// /// first operand /// second operand - /// true if alpha is less than beta + /// true if is logically less than /// Address families must be InterNetwork or InternetworkV6 - public static bool IsLessThan([CanBeNull] this IPAddress left, - [CanBeNull] IPAddress right) + public static bool IsLessThan(this IPAddress left, IPAddress right) { - if (ReferenceEquals(left, right) - || left == null - || right == null - || left.AddressFamily != right.AddressFamily) + if (ReferenceEquals(left, right) || left == null || right == null || left.AddressFamily != right.AddressFamily) { return false; } @@ -173,16 +166,19 @@ public static bool IsLessThan([CanBeNull] this IPAddress left, /// /// first operand /// second operand - public static bool IsLessThanOrEqualTo([CanBeNull] this IPAddress left, - [CanBeNull] IPAddress right) + /// true if is logically less than or equal to + public static bool IsLessThanOrEqualTo(this IPAddress left, IPAddress right) { return ReferenceEquals(left, right) - || (!ReferenceEquals(left, null) && left.Equals(right)) - || (left?.AddressFamily == right?.AddressFamily && ByteArrayUtils.CompareUnsignedBigEndian(left?.GetAddressBytes(), right?.GetAddressBytes()) <= 0); + || (!ReferenceEquals(left, null) && left.Equals(right)) + || ( + left?.AddressFamily == right?.AddressFamily + && ByteArrayUtils.CompareUnsignedBigEndian(left?.GetAddressBytes(), right?.GetAddressBytes()) <= 0 + ); } /// - /// Determine if the tested IP Address occurs numerically between the given high and low IP addresses + /// Determine if the occurs numerically between the given high and low IP addresses /// Inclusivity contingent on inclusive bit /// /// IP address to test @@ -192,10 +188,8 @@ public static bool IsLessThanOrEqualTo([CanBeNull] this IPAddress left, /// is . /// is . /// is . - public static bool IsBetween([NotNull] this IPAddress input, - [NotNull] IPAddress low, - [NotNull] IPAddress high, - bool inclusive = true) + /// true if is between and + public static bool IsBetween(this IPAddress input, IPAddress low, IPAddress high, bool inclusive = true) { #region defense @@ -214,8 +208,7 @@ public static bool IsBetween([NotNull] this IPAddress input, throw new ArgumentNullException(nameof(high)); } - if (low.AddressFamily != high.AddressFamily - || input.AddressFamily != low.AddressFamily) + if (low.AddressFamily != high.AddressFamily || input.AddressFamily != low.AddressFamily) { throw new InvalidOperationException("address families do not match"); } @@ -229,17 +222,14 @@ public static bool IsBetween([NotNull] this IPAddress input, #endregion // end: defense - if (ReferenceEquals(input, low) - || input.Equals(low) - || ReferenceEquals(input, high) - || input.Equals(high)) + if (ReferenceEquals(input, low) || input.Equals(low) || ReferenceEquals(input, high) || input.Equals(high)) { return inclusive; } var inputBytes = input.GetAddressBytes(); return ByteArrayUtils.CompareUnsignedBigEndian(inputBytes, lowAddressBytes) > 0 - && ByteArrayUtils.CompareUnsignedBigEndian(inputBytes, highAddressBytes) < 0; + && ByteArrayUtils.CompareUnsignedBigEndian(inputBytes, highAddressBytes) < 0; } /// @@ -251,9 +241,7 @@ public static bool IsBetween([NotNull] this IPAddress input, /// is . /// is . /// Address families must match - [NotNull] - public static IPAddress Max([NotNull] IPAddress left, - [NotNull] IPAddress right) + public static IPAddress Max(IPAddress left, IPAddress right) { #region defense @@ -274,9 +262,7 @@ public static IPAddress Max([NotNull] IPAddress left, #endregion // end: defense - return left.IsGreaterThan(right) - ? left - : right; + return left.IsGreaterThan(right) ? left : right; } /// @@ -288,9 +274,7 @@ public static IPAddress Max([NotNull] IPAddress left, /// is . /// is . /// Address families must match - [NotNull] - public static IPAddress Min([NotNull] IPAddress left, - [NotNull] IPAddress right) + public static IPAddress Min(IPAddress left, IPAddress right) { #region defense @@ -311,9 +295,7 @@ public static IPAddress Min([NotNull] IPAddress left, #endregion // end: defense - return left.IsLessThan(right) - ? left - : right; + return left.IsLessThan(right) ? left : right; } #endregion @@ -327,7 +309,7 @@ public static IPAddress Min([NotNull] IPAddress left, /// true if the address is the maximum value /// Address families must be InterNetwork or InternetworkV6 /// is . - public static bool IsAtMax([NotNull] this IPAddress address) + public static bool IsAtMax(this IPAddress address) { #region defense @@ -356,7 +338,7 @@ public static bool IsAtMax([NotNull] this IPAddress address) /// true if the address is the minimum value /// Address families must be InterNetwork or InternetworkV6 /// is . - public static bool IsAtMin([NotNull] this IPAddress address) + public static bool IsAtMin(this IPAddress address) { #region defense diff --git a/src/Arcus/Subnet.cs b/src/Arcus/Subnet.cs index 4dbaa7d..57fe020 100644 --- a/src/Arcus/Subnet.cs +++ b/src/Arcus/Subnet.cs @@ -12,1216 +12,1217 @@ using Arcus.Math; using Arcus.Utilities; using Gulliver; -using JetBrains.Annotations; namespace Arcus { - /// - /// An IPv4 or IPv6 subnetwork representation - the work horse and original intention of the Arcus library - /// - [PublicAPI] - [Serializable] - public class Subnet : AbstractIPAddressRange, - IEquatable, - IComparable, - IComparable, - ISerializable - { - /// - /// Pattern that passes on valid IPv4 octet partials - /// - private const string Ipv4OctetPartialPattern = @"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){0,2}(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)?)?$"; - - /// - /// Pattern for rough shape of a subnet string - /// - private const string RoughSubnetStringPattern = @"^([\da-fA-F:.]+)(?:/([\d]+))?$"; - - /// - /// Regex that passes on valid IPv4 octet partials - /// - private static Regex _ipv4OctetPartialRegex = new Regex(Ipv4OctetPartialPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant); - - /// - /// Regex for rough shape of a subnet string - /// - private static Regex _roughSubnetRegex = new Regex(RoughSubnetStringPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - - /// - /// the number of usable addresses in the subnet (ignores Broadcast and Network addresses) - /// - public BigInteger UsableHostAddressCount => Length >= 2 - ? Length - 2 - : 0; - - /// - /// the broadcast of the subnet (highest order ip address) - /// - [NotNull] - public IPAddress BroadcastAddress => Tail; - - /// - /// the calculated Netmask of the subnet, only valid for IPv4 based subnets will be on IPv6 - /// subnets - /// - [CanBeNull] - public IPAddress Netmask { get; } - - /// - /// the network prefix of the subnet (lowest order IP address) - /// - [NotNull] - public IPAddress NetworkPrefixAddress => Head; - - /// - /// the routing prefix used to specify the ip address - /// - public int RoutingPrefix { get; } - - #region From Interface IComparable - - /// - public int CompareTo(object obj) - { - if (ReferenceEquals(null, obj)) - { - return 1; - } - - if (ReferenceEquals(this, obj)) - { - return 0; - } - - return obj is Subnet other - ? this.CompareTo(other) - : throw new ArgumentException($"Object must be of type {nameof(Subnet)}"); - } - - #endregion - - #region From Interface IComparable - - /// - /// Compares the current object with another object of the same type. - /// - /// - /// A value that indicates the relative order of the objects being compared. The return value has the following - /// meanings: Value Meaning Less than zero This object is less than the parameter.Zero This - /// object is equal to . Greater than zero This object is greater than - /// . - /// - /// An object to compare with this object. - public int CompareTo(Subnet other) - { - return new DefaultIPAddressRangeComparer().Compare(this, other); - } - - #endregion - - #region Formatting - - /// - public override string ToString(string format, - IFormatProvider formatProvider) - { - if (formatProvider == null) - { - formatProvider = CultureInfo.InvariantCulture; - } - - switch (format?.Trim()) - { - // unspecified - case null: - case "": - - // general formats - case "g": - case "G": - return $"{this.NetworkPrefixAddress}/{this.RoutingPrefix}"; - - // "friendly" formats - case "f": - case "F": - return IsSingleIP - ? $"{this.NetworkPrefixAddress}" - : $"{this.NetworkPrefixAddress}/{this.RoutingPrefix}"; - - // range formats - case "r": - case "R": - return $"{this.NetworkPrefixAddress} - {this.BroadcastAddress}"; - - // delegate to base - default: - return base.ToString(format, formatProvider); - } - } - - #endregion // end: Formatting - - #region set based operations - - /// - /// check if a given subnet falls within the specified subnet - /// - /// the subnet to test - /// true if the passed subnet is contained within this - public bool Contains(Subnet subnet) - { - return subnet != null && Contains(subnet.NetworkPrefixAddress) && Contains(subnet.BroadcastAddress); - } - - /// - /// check if the given subnets overlaps this - /// - /// the subnet to check the overlap of - /// true if there is an overlap - public bool Overlaps(Subnet subnet) - { - return subnet != null && (subnet.Contains(this) || this.Contains(subnet)); - } - - #endregion // end: set based operations - - #region Ctor - - /// - /// Initializes a new instance of the class. - /// Construct the smallest possible subnet that would contain both IP addresses - /// typically the address specified are the Network and Broadcast addresses - /// (lower and higher bounds) but this is not necessary. - /// Addresses *MUST* be the same address family (either Internetwork or InternetworkV6) - /// - /// a address to be contained within the subnet - /// another address to be contained within the subnet - public Subnet([NotNull] IPAddress lowAddress, - [NotNull] IPAddress highAddress) - : base(CtorFactory(lowAddress, highAddress)) - { - var result = NormalizeAndCreateNetMask(Head, Tail); // TODO the execution of this call is logically redundant as it is used in the call of the base constructor - this.RoutingPrefix = result.Prefix; - this.Netmask = IsIPv4 - ? result.Mask - : null; // only set netmask for IPv4 based subnets - } - - private static AddressTuple CtorFactory(IPAddress lowAddress, - IPAddress highAddress) - { - #region Defense - - if (lowAddress == null) - { - throw new ArgumentNullException(nameof(lowAddress)); - } - - if (highAddress == null) - { - throw new ArgumentNullException(nameof(highAddress)); - } - - if (lowAddress.AddressFamily != highAddress.AddressFamily) - { - throw new ArgumentException($"{nameof(lowAddress)} and {nameof(highAddress)} must have matching address families"); - } - - if (!IPAddressUtilities.ValidAddressFamilies.Contains(lowAddress.AddressFamily)) - { - throw new ArgumentException($"{nameof(lowAddress)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(lowAddress)); - } - - if (!IPAddressUtilities.ValidAddressFamilies.Contains(highAddress.AddressFamily)) - { - throw new ArgumentException($"{nameof(highAddress)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(highAddress)); - } - - if (!highAddress.IsGreaterThanOrEqualTo(lowAddress)) - { - throw new InvalidOperationException($"{nameof(highAddress)} must be greater or equal to {nameof(lowAddress)}"); - } - - #endregion // end: Defense - - var result = NormalizeAndCreateNetMask(lowAddress, highAddress); - return new AddressTuple(result.Head, result.Tail); - } - - /// - /// Initializes a new instance of the class. - /// - /// the ip address - /// the routing prefix - /// IP Address must be IPv4 or IPv6 - /// Routing prefix is out of range - public Subnet([NotNull] IPAddress address, - int routingPrefix) - : base(CtorFactory(address, routingPrefix)) - { - var result = NormalizeAndCreateNetMask(Head, routingPrefix); // TODO the execution of this call is logically redundant as it is used in the call of the base constructor - this.RoutingPrefix = routingPrefix; - this.Netmask = IsIPv4 - ? result.Mask - : null; // only set netmask for IPv4 based subnets - } - - private static AddressTuple CtorFactory([NotNull] IPAddress address, - int routingPrefix) - { - #region Defense - - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } - - if (!IPAddressUtilities.ValidAddressFamilies.Contains(address.AddressFamily)) - { - throw new ArgumentException($"{nameof(address)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(address)); - } - - if (routingPrefix < 0) - { - throw new ArgumentOutOfRangeException(nameof(routingPrefix)); - } - - if (address.IsIPv4() - && routingPrefix > IPAddressUtilities.IPv4BitCount) - { - throw new ArgumentOutOfRangeException(nameof(routingPrefix)); - } - - if (address.IsIPv6() - && routingPrefix > IPAddressUtilities.IPv6BitCount) - { - throw new ArgumentOutOfRangeException(nameof(routingPrefix)); - } - - #endregion // end: Defense - - var result = NormalizeAndCreateNetMask(address, routingPrefix); - return new AddressTuple(result.Head, result.Tail); - } - - /// - /// Initializes a new instance of the class. - /// contains only a single ip address - /// - /// the ip address - public Subnet([NotNull] IPAddress address) - : base(address, address) - { - this.RoutingPrefix = IsIPv4 - ? IPAddressUtilities.IPv4BitCount - : IPAddressUtilities.IPv6BitCount; - - if (IsIPv6) - { - return; - } - - var netmaskBytes = Enumerable.Repeat((byte)0xff, IPAddressUtilities.IPv4ByteCount) - .ToArray(); - - this.Netmask = new IPAddress(netmaskBytes); - } - - /// Initializes a new instance of the class. - /// serialization info - /// serialization context - /// is - protected Subnet([NotNull] SerializationInfo info, -#pragma warning disable CA1801 - /* CA1801 Parameter context of method .ctor is never used. Remove the parameter or use it in the method body. - * parameter "StreamingContext context" is required for proper desearilization - */ - StreamingContext context) -#pragma warning restore CA1801 - : this(new IPAddress((byte[])(info ?? throw new ArgumentNullException(nameof(info))).GetValue(nameof(BroadcastAddress), typeof(byte[]))), - (int)info.GetValue(nameof(RoutingPrefix), typeof(int))) - { } - - #endregion // end: Ctor - - #region static factory methods - - #region FromNetMask - - /// - /// Create a subnet from an IP Address and netmask - /// - /// the ip address - /// the net mask - /// The created subnet - /// ipAddress - /// netmask - /// the given IP Address is not IPv4 - /// the given netmask is invalid - public static Subnet FromNetMask([NotNull] IPAddress address, - [NotNull] IPAddress netmask) - { - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } - - if (netmask == null) - { - throw new ArgumentNullException(nameof(netmask)); - } - - if (!address.IsIPv4()) - { - throw new ArgumentException($"{nameof(address)} must be IPv4", nameof(address)); - } - - if (!netmask.IsValidNetMask()) - { - throw new ArgumentException($"{nameof(netmask)} must be IPv4", nameof(netmask)); - } - - try - { - return new Subnet(address, netmask.NetmaskToCidrRoutePrefix()); - } - catch (Exception e) - { - throw new Exception("could not instantiate subnet", e); - } - } - - /// - /// Try to create a subnet from an IP Address and netmask - /// - /// the ip address - /// the net mask - /// the created subnet or on failure - /// on success - public static bool TryFromNetMask(IPAddress address, - IPAddress netmask, - out Subnet subnet) - { - try - { - subnet = FromNetMask(address, netmask); - return true; - } -#pragma warning disable CA1031 // catch is purposely general - catch -#pragma warning restore CA1031 - { - subnet = null; - return false; - } - } - - #endregion // end: FromNetMask - - /// - /// Construct the smallest possible subnet that would contain both IP addresses encoded as bytes typically the address - /// specified are the Network and Broadcast addresses (lower and higher bounds) but this is not necessary. Addresses - /// *MUST* be the same address family (either Internetwork or InternetworkV6) - /// - /// the lower address array - /// the high address array - public static Subnet FromBytes([NotNull] byte[] lowAddressBytes, - [NotNull] byte[] highAddressBytes) - { - if (lowAddressBytes == null) - { - throw new ArgumentNullException(nameof(lowAddressBytes)); - } - - if (highAddressBytes == null) - { - throw new ArgumentNullException(nameof(highAddressBytes)); - } - - IPAddress lowAddress; - try - { - lowAddress = new IPAddress(lowAddressBytes); - } - catch (ArgumentException e) - { - throw new ArgumentException($"could not convert {nameof(lowAddressBytes)} to an IPAddress", nameof(lowAddressBytes), e); - } - - IPAddress highAddress; - try - { - highAddress = new IPAddress(highAddressBytes); - } - catch (ArgumentException e) - { - throw new ArgumentException($"could not convert {nameof(highAddressBytes)} to an IPAddress", nameof(highAddressBytes), e); - } - - try - { - return new Subnet(lowAddress, highAddress); - } - catch (Exception e) - { - throw new Exception("could not instantiate subnet", e); - } - } - - /// - /// Construct the smallest possible subnet that would contain both IP addresses encoded as bytes typically the address - /// specified are the Network and Broadcast addresses (lower and higher bounds) but this is not necessary. Addresses - /// *MUST* be the same address family (either Internetwork or InternetworkV6) - /// - /// the lower address array - /// the high address array - /// the created subnet or on failure - /// on success - public static bool TryFromBytes(byte[] lowAddressBytes, - byte[] highAddressBytes, - out Subnet subnet) - { - try - { - subnet = FromBytes(lowAddressBytes, highAddressBytes); - return true; - } -#pragma warning disable CA1031 // catch is purposely general - catch -#pragma warning restore CA1031 - { - subnet = null; - return false; - } - } - - #region Parse / TryParse - - #region subnet string - - /// - /// Unsafe parsing of a string into a subnet - /// - /// the string to parse a subnet from - /// the parsed subnet - /// could not parse input - public static Subnet Parse([NotNull] string subnetString) - { - if (subnetString == null) - { - throw new ArgumentNullException(nameof(subnetString)); - } - - if (string.IsNullOrWhiteSpace(subnetString)) - { - throw new ArgumentException("a non empty value is expected", nameof(subnetString)); - } - - var matches = _roughSubnetRegex.Matches(subnetString); - - if (matches.Count != 1) - { - throw new FormatException("unexpected format"); - } - - var match = matches[0]; - - var addressString = match.Groups[1] - .Value; - var routePrefixString = match.Groups[2] - .Value; - - if (!IPAddress.TryParse(addressString, out var address)) // attempt to parse IP address portion - { - throw new FormatException($"cannot parse ip address \"{addressString}\""); - } - - int routingPrefix; - if (!string.IsNullOrWhiteSpace(routePrefixString)) // attempt to parse routing prefix in there is a match - { - if (!int.TryParse(routePrefixString, out routingPrefix)) - { - throw new FormatException($"cannot parse routing prefix \"{routePrefixString}\""); - } - } - else // no routing prefix match, assume it is a single address - { - routingPrefix = address.IsIPv4() - ? IPAddressUtilities.IPv4BitCount - : IPAddressUtilities.IPv6BitCount; - } - - if (address.IsIPv4() - && routingPrefix > IPAddressUtilities.IPv4BitCount) - { - throw new ArgumentException("routing prefix must be 32 or less for an IPv4 subnet", nameof(subnetString)); - } - - if (address.IsIPv6() - && routingPrefix > IPAddressUtilities.IPv6BitCount) - { - throw new ArgumentException("routing prefix must be 128 or less for an IPv6 subnet", nameof(subnetString)); - } - - try - { - return new Subnet(address, routingPrefix); - } -#pragma warning disable CA1031 // catch is purposely general - catch (Exception e) -#pragma warning restore CA1031 - { - throw new Exception("could not instantiate subnet", e); - } - } - - /// - /// Attempt to parse a string into a subnet - /// - /// the string to parse - /// the created subnet or on failure - /// on success - public static bool TryParse(string subnetString, - out Subnet subnet) - { - try - { - // ReSharper disable once AssignNullToNotNullAttribute - subnet = Parse(subnetString); - return true; - } -#pragma warning disable CA1031 // catch is purposely general - catch -#pragma warning restore CA1031 - { - subnet = null; - return false; - } - } - - #endregion // end: subnet string - - #region address, routing prefix - - /// - /// Unsafe parsing of a string address and routing prefix into a subnet - /// - /// the address string - /// the subnet routing prefix - /// - /// has an invalid - /// - /// is . - /// is less than 0 - /// - /// is is out of range of the provided - /// - /// - public static Subnet Parse([NotNull] string addressString, - int routingPrefix) - { - if (addressString == null) - { - throw new ArgumentNullException(nameof(addressString)); - } - - if (!IPAddress.TryParse(addressString, out var address)) - { - throw new FormatException($"could not parse {nameof(addressString)} value \"{addressString}\""); - } - - if (!IPAddressUtilities.ValidAddressFamilies.Contains(address.AddressFamily)) - { - throw new ArgumentException($"must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(addressString)); - } - - if (routingPrefix < 0) - { - throw new ArgumentOutOfRangeException(nameof(routingPrefix)); - } - - if (address.IsIPv4() - && routingPrefix > IPAddressUtilities.IPv4BitCount) - { - throw new ArgumentOutOfRangeException(nameof(routingPrefix)); - } - - if (address.IsIPv6() - && routingPrefix > IPAddressUtilities.IPv6BitCount) - { - throw new ArgumentOutOfRangeException(nameof(routingPrefix)); - } - - try - { - return new Subnet(address, routingPrefix); - } - catch (Exception e) - { - throw new Exception("could not instantiate subnet", e); - } - } - - /// - /// Try to parse as an for a Subnet with the given routing - /// prefix - /// - /// the address string - /// the subnet routing prefix - /// the created subnet or on failure - /// on success - public static bool TryParse(string addressString, - int routingPrefix, - out Subnet subnet) - { - try - { - // ReSharper disable once AssignNullToNotNullAttribute - subnet = Parse(addressString, routingPrefix); - return true; - } -#pragma warning disable CA1031 // catch is purposely general - catch -#pragma warning restore CA1031 - { - subnet = null; - return false; - } - } - - #endregion // end: address, routing prefix - - #region string string - - /// - /// Unsafe parsing of two string as a new subnet - /// - /// the low address string - /// the high address string - public static Subnet Parse([NotNull] string lowAddressString, - [NotNull] string highAddressString) - { - if (lowAddressString == null) - { - throw new ArgumentNullException(nameof(lowAddressString)); - } - - if (highAddressString == null) - { - throw new ArgumentNullException(nameof(highAddressString)); - } - - if (!IPAddress.TryParse(lowAddressString, out var lowAddress)) - { - throw new FormatException($"could not parse {nameof(lowAddressString)} value \"{lowAddressString}\""); - } - - if (!IPAddress.TryParse(highAddressString, out var highAddress)) - { - throw new FormatException($"could not parse {nameof(highAddressString)} value \"{highAddressString}\""); - } - - if (lowAddress.AddressFamily != highAddress.AddressFamily) - { - throw new ArgumentException($"{nameof(lowAddressString)} and {nameof(highAddressString)} must have matching address families"); - } - - if (!IPAddressUtilities.ValidAddressFamilies.Contains(lowAddress.AddressFamily)) - { - throw new ArgumentException($"{nameof(lowAddressString)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(lowAddressString)); - } - - if (!IPAddressUtilities.ValidAddressFamilies.Contains(highAddress.AddressFamily)) - { - throw new ArgumentException($"{nameof(highAddressString)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(highAddressString)); - } - - if (!highAddress.IsGreaterThanOrEqualTo(lowAddress)) - { - throw new InvalidOperationException($"{nameof(highAddressString)} must be greater or equal to {nameof(lowAddressString)}"); - } - - try - { - return new Subnet(lowAddress, highAddress); - } - catch (Exception e) - { - throw new Exception("could not instantiate subnet", e); - } - } - - /// - /// Try parse two strings as addresses - /// - /// the low address string - /// the high address string - /// the created subnet or on failure - /// on success - public static bool TryParse(string lowAddressString, - string highAddressString, - out Subnet subnet) - { - try - { - // ReSharper disable AssignNullToNotNullAttribute - subnet = Parse(lowAddressString, highAddressString); - - // ReSharper restore AssignNullToNotNullAttribute - return true; - } -#pragma warning disable CA1031 // catch is purposely general - catch -#pragma warning restore CA1031 - { - subnet = null; - return false; - } - } - - #endregion // end: string string - - #region From Partial - - /// - /// Try to convert a partial IPv4 address into a subnet based on found provided partial octets - /// - /// the partial IP address to parse - /// the subnet created - /// true on success - public static bool TryIPv4FromPartial(string input, - out Subnet subnet) - { - if (string.IsNullOrWhiteSpace(input) - || !_ipv4OctetPartialRegex.IsMatch(input)) - { - subnet = null; - return false; - } - - input = input.TrimEnd('.'); - var octetCount = input.Count(c => c == '.') + 1; - - subnet = Parse(input + string.Concat(Enumerable.Repeat(".0", IPAddressUtilities.IPv4OctetCount - octetCount)), octetCount * 8); - return true; - } - - /// - /// Given a IPv6 cidr-like or IPv6 like string build a collection of all possible valid subnets that could be intended - /// by the input - /// - /// the partial ipv6 cidr or address - /// a collection of all possible matching subnets on success, or an empty collection on failure - /// on success - [Obsolete("the needs for this method are very specialized and may not be what the developer is expecting; this is likely to be replaced by a host of other more explicit and useful methods")] - public static bool TryIPv6FromPartial(string input, - out IEnumerable subnets) - { - // try to discover possible garbage or incomplete input - int hextetCount; - if (string.IsNullOrWhiteSpace(input) - || input.Equals(":", StringComparison.OrdinalIgnoreCase) - || input.Contains(":::") - || DoubleColonsAppearsMultipleTimes(input) - || (hextetCount = input.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries) - .Length) - > IPAddressUtilities.IPv6HextetCount - || (hextetCount >= IPAddressUtilities.IPv6HextetCount && input.EndsWith(":", StringComparison.OrdinalIgnoreCase)) - ) // too many hextets - { + /// + /// An IPv4 or IPv6 subnetwork representation - the work horse and original intention of the Arcus library + /// + [Serializable] + public class Subnet : AbstractIPAddressRange, +#if NETSTANDARD2_0 + ISerializable, +#endif + IEquatable, IComparable, IComparable + { + /// + /// Pattern that passes on valid IPv4 octet partials + /// + private const string Ipv4OctetPartialPattern = + @"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){0,2}(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)?)?$"; + + /// + /// Pattern for rough shape of a subnet string + /// + private const string RoughSubnetStringPattern = @"^([\da-fA-F:.]+)(?:/([\d]+))?$"; + + /// + /// Regex that passes on valid IPv4 octet partials + /// + private static readonly Regex IPv4OctetPartialRegex = new Regex( + Ipv4OctetPartialPattern, + RegexOptions.Compiled | RegexOptions.CultureInvariant + ); + + /// + /// Regex for rough shape of a subnet string + /// + private static readonly Regex RoughSubnetRegex = new Regex( + RoughSubnetStringPattern, + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase + ); + + /// + /// Gets the number of usable addresses in the subnet (ignores Broadcast and Network addresses) + /// + /// + /// the number of usable addresses in the subnet (ignores Broadcast and Network addresses) + /// + public BigInteger UsableHostAddressCount => Length >= 2 ? Length - 2 : 0; + + /// + /// Gets the broadcast of the subnet (highest order ip address) + /// + /// + /// the broadcast of the subnet (highest order ip address) + /// + public IPAddress BroadcastAddress => Tail; + + /// + /// Gets the calculated Netmask of the subnet, only valid for IPv4 based subnets will be on IPv6 + /// subnets + /// + /// + /// the calculated Netmask of the subnet, only valid for IPv4 based subnets will be on IPv6 + /// subnets + /// + public IPAddress Netmask { get; } + + /// + /// Gets the network prefix of the subnet (lowest order IP address) + /// + /// + /// the network prefix of the subnet (lowest order IP address) + /// + public IPAddress NetworkPrefixAddress => Head; + + /// + /// Gets the routing prefix used to specify the ip address + /// + /// + /// the routing prefix used to specify the ip address + /// + public int RoutingPrefix { get; } + + #region From Interface IComparable + + /// + public int CompareTo(object obj) + { + if (obj is null) + { + return 1; + } + + if (obj is Subnet other) + { + return CompareTo(other); + } + + throw new ArgumentException("Object is not an Subnet"); + } + + #endregion + + #region From Interface IComparable + + /// + public int CompareTo(Subnet other) + { + if (other is null) + { + return 1; + } + + return DefaultIIPAddressRangeComparer.Instance.Compare(this, other); + } + + #endregion + + #region Formatting + + /// + public override string ToString(string format, IFormatProvider formatProvider) + { + if (formatProvider is null) + { + formatProvider = CultureInfo.InvariantCulture; + } + + switch (format?.Trim()) + { + // unspecified + case null: + case "": + + // general formats + case "g": + case "G": + return $"{this.NetworkPrefixAddress}/{this.RoutingPrefix}"; + + // "friendly" formats + case "f": + case "F": + return IsSingleIP ? $"{this.NetworkPrefixAddress}" : $"{this.NetworkPrefixAddress}/{this.RoutingPrefix}"; + + // range formats + case "r": + case "R": + return $"{this.NetworkPrefixAddress} - {this.BroadcastAddress}"; + + // delegate to base + default: + return base.ToString(format, formatProvider); + } + } + + #endregion // end: Formatting + + #region set based operations + + /// + /// check if a given subnet falls within the specified subnet + /// + /// the subnet to test + /// true if the passed subnet is contained within this + public bool Contains(Subnet subnet) + { + return subnet != null && Contains(subnet.NetworkPrefixAddress) && Contains(subnet.BroadcastAddress); + } + + /// + /// check if the given subnets overlaps this + /// + /// the subnet to check the overlap of + /// true if there is an overlap + public bool Overlaps(Subnet subnet) + { + return subnet != null && (subnet.Contains(this) || this.Contains(subnet)); + } + + #endregion // end: set based operations + + #region Ctor + + /// + /// Initializes a new instance of the class. + /// Construct the smallest possible subnet that would contain both IP addresses + /// typically the address specified are the Network and Broadcast addresses + /// (lower and higher bounds) but this is not necessary. + /// Addresses *MUST* be the same address family (either Internetwork or InternetworkV6) + /// + /// a address to be contained within the subnet + /// another address to be contained within the subnet + public Subnet(IPAddress lowAddress, IPAddress highAddress) + : base(CtorFactory(lowAddress, highAddress)) + { + var result = NormalizeAndCreateNetMask(Head, Tail); // TODO the execution of this call is logically redundant as it is used in the call of the base constructor + this.RoutingPrefix = result.Prefix; + this.Netmask = IsIPv4 ? result.Mask : null; // only set netmask for IPv4 based subnets + } + + private static AddressTuple CtorFactory(IPAddress lowAddress, IPAddress highAddress) + { + #region Defense + + if (lowAddress is null) + { + throw new ArgumentNullException(nameof(lowAddress)); + } + + if (highAddress is null) + { + throw new ArgumentNullException(nameof(highAddress)); + } + + if (lowAddress.AddressFamily != highAddress.AddressFamily) + { + throw new ArgumentException( + $"{nameof(lowAddress)} and {nameof(highAddress)} must have matching address families" + ); + } + + if (!IPAddressUtilities.ValidAddressFamilies.Contains(lowAddress.AddressFamily)) + { + throw new ArgumentException( + $"{nameof(lowAddress)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(lowAddress) + ); + } + + if (!IPAddressUtilities.ValidAddressFamilies.Contains(highAddress.AddressFamily)) + { + throw new ArgumentException( + $"{nameof(highAddress)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(highAddress) + ); + } + + if (!highAddress.IsGreaterThanOrEqualTo(lowAddress)) + { + throw new InvalidOperationException($"{nameof(highAddress)} must be greater or equal to {nameof(lowAddress)}"); + } + + #endregion // end: Defense + + var result = NormalizeAndCreateNetMask(lowAddress, highAddress); + return new AddressTuple(result.Head, result.Tail); + } + + /// + /// Initializes a new instance of the class. + /// + /// the ip address + /// the routing prefix + /// IP Address must be IPv4 or IPv6 + /// Routing prefix is out of range + public Subnet(IPAddress address, int routingPrefix) + : base(CtorFactory(address, routingPrefix)) + { + var result = NormalizeAndCreateNetMask(Head, routingPrefix); // TODO the execution of this call is logically redundant as it is used in the call of the base constructor + this.RoutingPrefix = routingPrefix; + this.Netmask = IsIPv4 ? result.Mask : null; // only set netmask for IPv4 based subnets + } + + private static AddressTuple CtorFactory(IPAddress address, int routingPrefix) + { + #region Defense + + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (!IPAddressUtilities.ValidAddressFamilies.Contains(address.AddressFamily)) + { + throw new ArgumentException( + $"{nameof(address)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(address) + ); + } + + if (routingPrefix < 0) + { + throw new ArgumentOutOfRangeException(nameof(routingPrefix)); + } + + if (address.IsIPv4() && routingPrefix > IPAddressUtilities.IPv4BitCount) + { + throw new ArgumentOutOfRangeException(nameof(routingPrefix)); + } + + if (address.IsIPv6() && routingPrefix > IPAddressUtilities.IPv6BitCount) + { + throw new ArgumentOutOfRangeException(nameof(routingPrefix)); + } + + #endregion // end: Defense + + var result = NormalizeAndCreateNetMask(address, routingPrefix); + return new AddressTuple(result.Head, result.Tail); + } + + /// + /// Initializes a new instance of the class. + /// contains only a single ip address + /// + /// the ip address + public Subnet(IPAddress address) + : base(address, address) + { + this.RoutingPrefix = IsIPv4 ? IPAddressUtilities.IPv4BitCount : IPAddressUtilities.IPv6BitCount; + + if (IsIPv6) + { + return; + } + + var netmaskBytes = Enumerable.Repeat((byte)0xff, IPAddressUtilities.IPv4ByteCount).ToArray(); + + this.Netmask = new IPAddress(netmaskBytes); + } + + /// Initializes a new instance of the class. + /// serialization info + /// serialization context + /// is + protected Subnet(SerializationInfo info, StreamingContext context) + : this( + new IPAddress( + (byte[]) + (info ?? throw new ArgumentNullException(nameof(info))).GetValue( + nameof(BroadcastAddress), + typeof(byte[]) + ) + ), + (int)info.GetValue(nameof(RoutingPrefix), typeof(int)) + ) { } + + #endregion // end: Ctor + + #region static factory methods + + #region FromNetMask + + /// + /// Create a subnet from an IP Address and netmask + /// + /// the ip address + /// the net mask + /// The created subnet + /// ipAddress + /// netmask + /// the given IP Address is not IPv4 + /// the given netmask is invalid + public static Subnet FromNetMask(IPAddress address, IPAddress netmask) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (netmask is null) + { + throw new ArgumentNullException(nameof(netmask)); + } + + if (!address.IsIPv4()) + { + throw new ArgumentException($"{nameof(address)} must be IPv4", nameof(address)); + } + + if (!netmask.IsValidNetMask()) + { + throw new ArgumentException($"{nameof(netmask)} must be IPv4", nameof(netmask)); + } + + try + { + return new Subnet(address, netmask.NetmaskToCidrRoutePrefix()); + } + catch (Exception e) + { + throw new Exception("could not instantiate subnet", e); + } + } + + /// + /// Try to create a subnet from an IP Address and netmask + /// + /// the ip address + /// the net mask + /// the created subnet or on failure + /// on success + public static bool TryFromNetMask(IPAddress address, IPAddress netmask, out Subnet subnet) + { + try + { + subnet = FromNetMask(address, netmask); + return true; + } + catch + { + subnet = null; + return false; + } + } + + #endregion // end: FromNetMask + + /// + /// Construct the smallest possible subnet that would contain both IP addresses encoded as bytes typically the address + /// specified are the Network and Broadcast addresses (lower and higher bounds) but this is not necessary. Addresses + /// *MUST* be the same address family (either Internetwork or InternetworkV6) + /// + /// the lower address array + /// the high address array + /// The created + public static Subnet FromBytes(byte[] lowAddressBytes, byte[] highAddressBytes) + { + if (lowAddressBytes is null) + { + throw new ArgumentNullException(nameof(lowAddressBytes)); + } + + if (highAddressBytes is null) + { + throw new ArgumentNullException(nameof(highAddressBytes)); + } + + IPAddress lowAddress; + try + { + lowAddress = new IPAddress(lowAddressBytes); + } + catch (ArgumentException e) + { + throw new ArgumentException( + $"could not convert {nameof(lowAddressBytes)} to an IPAddress", + nameof(lowAddressBytes), + e + ); + } + + IPAddress highAddress; + try + { + highAddress = new IPAddress(highAddressBytes); + } + catch (ArgumentException e) + { + throw new ArgumentException( + $"could not convert {nameof(highAddressBytes)} to an IPAddress", + nameof(highAddressBytes), + e + ); + } + + try + { + return new Subnet(lowAddress, highAddress); + } + catch (Exception e) + { + throw new Exception("could not instantiate subnet", e); + } + } + + /// + /// Construct the smallest possible subnet that would contain both IP addresses encoded as bytes typically the address + /// specified are the Network and Broadcast addresses (lower and higher bounds) but this is not necessary. Addresses + /// *MUST* be the same address family (either Internetwork or InternetworkV6) + /// + /// the lower address array + /// the high address array + /// the created subnet or on failure + /// on success + public static bool TryFromBytes(byte[] lowAddressBytes, byte[] highAddressBytes, out Subnet subnet) + { + try + { + subnet = FromBytes(lowAddressBytes, highAddressBytes); + return true; + } + catch + { + subnet = null; + return false; + } + } + + #region Parse / TryParse + + #region subnet string + + /// + /// Unsafe parsing of a string into a subnet + /// + /// the string to parse a subnet from + /// the parsed subnet + /// could not parse input + public static Subnet Parse(string subnetString) + { + if (subnetString is null) + { + throw new ArgumentNullException(nameof(subnetString)); + } + + if (string.IsNullOrWhiteSpace(subnetString)) + { + throw new ArgumentException("a non empty value is expected", nameof(subnetString)); + } + + var matches = RoughSubnetRegex.Matches(subnetString); + + if (matches.Count != 1) + { + throw new FormatException("unexpected format"); + } + + var match = matches[0]; + + var addressString = match.Groups[1].Value; + var routePrefixString = match.Groups[2].Value; + + if (!IPAddress.TryParse(addressString, out var address)) // attempt to parse IP address portion + { + throw new FormatException($"cannot parse ip address \"{addressString}\""); + } + + int routingPrefix; + if (!string.IsNullOrWhiteSpace(routePrefixString)) // attempt to parse routing prefix in there is a match + { + if (!int.TryParse(routePrefixString, out routingPrefix)) + { + throw new FormatException($"cannot parse routing prefix \"{routePrefixString}\""); + } + } + else // no routing prefix match, assume it is a single address + { + routingPrefix = address.IsIPv4() ? IPAddressUtilities.IPv4BitCount : IPAddressUtilities.IPv6BitCount; + } + + if (address.IsIPv4() && routingPrefix > IPAddressUtilities.IPv4BitCount) + { + throw new ArgumentException("routing prefix must be 32 or less for an IPv4 subnet", nameof(subnetString)); + } + + if (address.IsIPv6() && routingPrefix > IPAddressUtilities.IPv6BitCount) + { + throw new ArgumentException("routing prefix must be 128 or less for an IPv6 subnet", nameof(subnetString)); + } + + try + { + return new Subnet(address, routingPrefix); + } + catch (Exception e) + { + throw new Exception("could not instantiate subnet", e); + } + } + + /// + /// Attempt to parse a string into a subnet + /// + /// the string to parse + /// the created subnet or on failure + /// on success + public static bool TryParse(string subnetString, out Subnet subnet) + { + try + { + subnet = Parse(subnetString); + return true; + } + catch + { + subnet = null; + return false; + } + } + + #endregion // end: subnet string + + #region address, routing prefix + + /// + /// Unsafe parsing of a string address and routing prefix into a subnet + /// + /// the address string + /// the subnet routing prefix + /// + /// has an invalid + /// + /// is . + /// is less than 0 + /// + /// is out of range of the provided + /// + /// + /// The parsed + public static Subnet Parse(string addressString, int routingPrefix) + { + if (addressString is null) + { + throw new ArgumentNullException(nameof(addressString)); + } + + if (!IPAddress.TryParse(addressString, out var address)) + { + throw new FormatException($"could not parse {nameof(addressString)} value \"{addressString}\""); + } + + if (!IPAddressUtilities.ValidAddressFamilies.Contains(address.AddressFamily)) + { + throw new ArgumentException( + $"must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(addressString) + ); + } + + if (routingPrefix < 0) + { + throw new ArgumentOutOfRangeException(nameof(routingPrefix)); + } + + if (address.IsIPv4() && routingPrefix > IPAddressUtilities.IPv4BitCount) + { + throw new ArgumentOutOfRangeException(nameof(routingPrefix)); + } + + if (address.IsIPv6() && routingPrefix > IPAddressUtilities.IPv6BitCount) + { + throw new ArgumentOutOfRangeException(nameof(routingPrefix)); + } + + try + { + return new Subnet(address, routingPrefix); + } + catch (Exception e) + { + throw new Exception("could not instantiate subnet", e); + } + } + + /// + /// Try to parse as an for a Subnet with the given routing + /// prefix + /// + /// the address string + /// the subnet routing prefix + /// the created subnet or on failure + /// on success + public static bool TryParse(string addressString, int routingPrefix, out Subnet subnet) + { + try + { + subnet = Parse(addressString, routingPrefix); + return true; + } + catch + { + subnet = null; + return false; + } + } + + #endregion // end: address, routing prefix + + #region string string + + /// + /// Unsafe parsing of two string as a new subnet + /// + /// the low address string + /// the high address string + /// The parsed + public static Subnet Parse(string lowAddressString, string highAddressString) + { + if (lowAddressString is null) + { + throw new ArgumentNullException(nameof(lowAddressString)); + } + + if (highAddressString is null) + { + throw new ArgumentNullException(nameof(highAddressString)); + } + + if (!IPAddress.TryParse(lowAddressString, out var lowAddress)) + { + throw new FormatException($"could not parse {nameof(lowAddressString)} value \"{lowAddressString}\""); + } + + if (!IPAddress.TryParse(highAddressString, out var highAddress)) + { + throw new FormatException($"could not parse {nameof(highAddressString)} value \"{highAddressString}\""); + } + + if (lowAddress.AddressFamily != highAddress.AddressFamily) + { + throw new ArgumentException( + $"{nameof(lowAddressString)} and {nameof(highAddressString)} must have matching address families" + ); + } + + if (!IPAddressUtilities.ValidAddressFamilies.Contains(lowAddress.AddressFamily)) + { + throw new ArgumentException( + $"{nameof(lowAddressString)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(lowAddressString) + ); + } + + if (!IPAddressUtilities.ValidAddressFamilies.Contains(highAddress.AddressFamily)) + { + throw new ArgumentException( + $"{nameof(highAddressString)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(highAddressString) + ); + } + + if (!highAddress.IsGreaterThanOrEqualTo(lowAddress)) + { + throw new InvalidOperationException( + $"{nameof(highAddressString)} must be greater or equal to {nameof(lowAddressString)}" + ); + } + + try + { + return new Subnet(lowAddress, highAddress); + } + catch (Exception e) + { + throw new Exception("could not instantiate subnet", e); + } + } + + /// + /// Try parse two strings as addresses + /// + /// the low address string + /// the high address string + /// the created subnet or on failure + /// on success + public static bool TryParse(string lowAddressString, string highAddressString, out Subnet subnet) + { + try + { + subnet = Parse(lowAddressString, highAddressString); + + return true; + } + catch + { + subnet = null; + return false; + } + } + + #endregion // end: string string + + #region From Partial + + /// + /// Try to convert a partial IPv4 address into a subnet based on found provided partial octets + /// + /// the partial IP address to parse + /// the subnet created + /// true on success + public static bool TryIPv4FromPartial(string input, out Subnet subnet) + { + if (string.IsNullOrWhiteSpace(input) || !IPv4OctetPartialRegex.IsMatch(input)) + { + subnet = null; + return false; + } + + input = input.TrimEnd('.'); + var octetCount = input.Count(c => c == '.') + 1; + var addressString = input + string.Concat(Enumerable.Repeat(".0", IPAddressUtilities.IPv4OctetCount - octetCount)); + + try + { + subnet = Parse(addressString, octetCount * 8); + return true; + } + catch + { + subnet = null; + return false; + } + } + + /// + /// Given a IPv6 cidr-like or IPv6 like string build a collection of all possible valid subnets that could be intended + /// by the input + /// + /// the partial ipv6 cidr or address + /// a collection of all possible matching subnets on success, or an empty collection on failure + /// on success + [Obsolete( + "the needs for this method are very specialized and may not be what the developer is expecting; this is likely to be replaced by a host of other more explicit and useful methods" + )] + public static bool TryIPv6FromPartial(string input, out IEnumerable subnets) + { + // try to discover possible garbage or incomplete input + int hextetCount; + if ( + string.IsNullOrWhiteSpace(input) + || input.Equals(":", StringComparison.OrdinalIgnoreCase) + || input.Contains(":::") + || DoubleColonsAppearsMultipleTimes(input) + || (hextetCount = input.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Length) + > IPAddressUtilities.IPv6HextetCount + || ( + hextetCount >= IPAddressUtilities.IPv6HextetCount && input.EndsWith(":", StringComparison.OrdinalIgnoreCase) + ) + ) // too many hextets + { + subnets = Enumerable.Empty(); + return false; + } + + // parseable as a well defined IPv6 subnet already + if ( + input.Contains('/') + && !input.EndsWith("/", StringComparison.Ordinal) + && TryParse(input, out var subnet) + && subnet.IsIPv6 + ) + { + subnets = new[] { subnet }; + return true; + } + + // TODO this could probably be done more cleanly, and provide a more expected result + + // a IPv6 cidr partial is provided + if ( + ( + IPAddress.TryParse(input, out var address) // treat as a complete address, may contain a '::' or not + || (input.Contains("::") && IPAddress.TryParse(input.TrimEnd(':'), out address)) + || IPAddress.TryParse(input.TrimEnd(':') + "::", out address) + ) && address.IsIPv6() + ) // no collapse, but incomplete address + { + var trimmedPartial = input.TrimEnd(':'); // remove trailing ":" or "::" if exists + + // break up entry on hextets, an empty hextet implies a collapse + var hextets = trimmedPartial + .Split(new[] { ':' }, StringSplitOptions.None) // DO NOT remove empty splits + .ToList(); + + // should contain an empty, pump the first with appropriate values + + // get the index of the collapse + var collapse = hextets + .Select((value, index) => new { value, index }) + .FirstOrDefault(pair => string.IsNullOrWhiteSpace(pair.value)); + + int collapseIndex; + if (collapse is null && hextets.Count == 8) // no collapse - fully fledged address, all 8 hextets present + { + subnets = new[] { Parse(input, IPAddressUtilities.IPv6BitCount) }; + return true; + } + + // introduce a collapse at the end if one does not exist + if (collapse is null) // not fully fledged, add a collapse to the end + { + hextets.Add(string.Empty); + collapseIndex = hextets.Count - 1; + } + else // a collapse is present + { + collapseIndex = collapse.index; + } + + var subnetsList = new List(); + + hextetCount = hextets.Count; + var permutationLimit = (IPAddressUtilities.IPv6HextetCount + 1) - hextetCount; // number of permutations of IP partial, every hextet available removes a permutation + for (var permutation = 0; permutation <= permutationLimit; permutation++) + { + var hextetsCopy = hextets.ToList(); + hextetsCopy.RemoveAt(collapseIndex); // collapsed empty item + hextetsCopy.InsertRange(collapseIndex, Enumerable.Repeat("0", permutation)); // add zeros as appropriate + + var addressString = string.Join(":", hextetsCopy); // re join string + + if ( + !IPAddress.TryParse(addressString + "::", out var subnetAddress) + && !IPAddress.TryParse(addressString, out subnetAddress) + ) + { + subnets = Enumerable.Empty(); + return false; + } + + var routePrefix = ((permutation + hextetCount) - 1) * 16; + subnetsList.Add(new Subnet(subnetAddress, routePrefix)); + } + + subnets = subnetsList; + return true; + } + + // fail; could not parse anything useful from the input subnets = Enumerable.Empty(); return false; - } - // parseable as a well defined IPv6 subnet already - if (input.Contains('/') - && !input.EndsWith("/", StringComparison.Ordinal) - && TryParse(input, out var subnet) - && subnet.IsIPv6) - { - subnets = new[] { subnet }; - return true; - } + // checks input for multiple occurrences of discrete "::" substrings +#if NET6_0_OR_GREATER + static +#endif + bool DoubleColonsAppearsMultipleTimes(string @in) + { + const string colons = "::"; + int firstIndex; + + return @in.Length >= 4 + && (firstIndex = @in.IndexOf(colons, StringComparison.Ordinal)) != -1 + && @in.Substring(firstIndex + 2, @in.Length - firstIndex - 2).IndexOf(colons, StringComparison.Ordinal) + != -1; + } + } + + #endregion // end: From Partial + + #endregion // end: Parse / TryParse + + #endregion // end: Static Factory Methods + + /// + public virtual bool Equals(Subnet other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return DefaultIIPAddressRangeComparer.Instance.Compare(this, other) == 0; + } + + /// + public override bool Equals(object obj) + { + if (obj is Subnet other) + { + return Equals(other); + } + + return false; + } + + /// + public override int GetHashCode() => HashCode.Combine(AddressFamily, Head, RoutingPrefix); + +#if NETSTANDARD2_0 + #region From Interface ISerializable + + /// + /// is + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info is null) + { + throw new ArgumentNullException(nameof(info)); + } + + info.AddValue(nameof(this.BroadcastAddress), this.BroadcastAddress.GetAddressBytes()); + info.AddValue(nameof(this.RoutingPrefix), this.RoutingPrefix); + } + + #endregion +#endif - // TODO this could probably be done more cleanly, and provide a more expected result + #region operators - // a IPv6 cidr partial is provided - if ((IPAddress.TryParse(input, out var address) // treat as a complete address, may contain a '::' or not - || (input.Contains("::") && IPAddress.TryParse(input.TrimEnd(':'), out address)) - || IPAddress.TryParse(input.TrimEnd(':') + "::", out address)) - && address.IsIPv6()) // no collapse, but incomplete address - { - var trimmedPartial = input.TrimEnd(':'); // remove trailing ":" or "::" if exists + /// + /// Determines whether two instances are equal. + /// + /// The first instance. + /// The second instance. + /// true if both instances are equal; otherwise, false. + public static bool operator ==(Subnet left, Subnet right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left is null || right is null) + { + return false; + } - // break up entry on hextets, an empty hextet implies a collapse - var hextets = trimmedPartial.Split(new[] { ':' }, StringSplitOptions.None) // DO NOT remove empty splits - .ToList(); + return left.Equals(right); + } + + /// + /// Determines whether two instances are not equal. + /// + /// The first instance. + /// The second instance. + /// true if the instances are not equal; otherwise, false. + public static bool operator !=(Subnet left, Subnet right) => !(left == right); + + /// + /// Compares two instances to determine if the first is less than the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is less than the second; otherwise, false. + public static bool operator <(Subnet left, Subnet right) + { + if (left is null) + { + return !(right is null); // null is less than any non-null instance + } + + return left.CompareTo(right) < 0; + } + + /// + /// Compares two instances to determine if the first is greater than the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is greater than the second; otherwise, false. + public static bool operator >(Subnet left, Subnet right) + { + if (left is null) + { + return false; + } + + return left.CompareTo(right) > 0; + } + + /// + /// Compares two instances to determine if the first is less than or equal to the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is less than or equal to the second; otherwise, false. + public static bool operator <=(Subnet left, Subnet right) => left < right || left == right; + + /// + /// Compares two instances to determine if the first is greater than or equal to the second. + /// + /// The first instance. + /// The second instance. + /// true if the first instance is greater than or equal to the second; otherwise, false. + public static bool operator >=(Subnet left, Subnet right) => left > right || left == right; + + #endregion operators + + #region Static metods, may be appropriate for extracting + + private readonly struct AddressMaskAndPrefixTuple + { + public AddressMaskAndPrefixTuple(IPAddress head, IPAddress tail, IPAddress mask, int prefix) + { + this.Head = head ?? throw new ArgumentNullException(nameof(head)); + this.Tail = tail ?? throw new ArgumentNullException(nameof(tail)); + this.Mask = mask ?? throw new ArgumentNullException(nameof(mask)); + + if (prefix < 0) + { + throw new ArgumentOutOfRangeException(nameof(prefix)); + } + + this.Prefix = prefix; + } + + /// + /// Gets head + /// + public IPAddress Head { get; } + + /// + /// Gets tail + /// + public IPAddress Tail { get; } + + /// + /// Gets mask + /// + public IPAddress Mask { get; } + + /// + /// Gets prefix + /// + public int Prefix { get; } + } + + private static AddressMaskAndPrefixTuple NormalizeAndCreateNetMask(IPAddress head, IPAddress tail) + { + var headBytes = head.GetAddressBytes(); + var tailBytes = tail.GetAddressBytes(); + + var routingPrefix = CalculateRoutingPrefix(headBytes, tailBytes); + var result = NormalizeAndCreateNetMask(head, routingPrefix); + return new AddressMaskAndPrefixTuple(result.Head, result.Tail, result.Mask, routingPrefix); + +#if NET6_0_OR_GREATER + static +#endif + int CalculateRoutingPrefix(byte[] hb, byte[] tb) + { + var bitCount = hb.Length * 8; // 8 bits per byte + + // iterate in order to find the count of common bits starting at the 0th element of each byte array + for (var i = 0; i < bitCount; i++) + { + var byteIndex = i / 8; + var bitmask = (byte)(0x80 >> (i % 8)); + if ((hb[byteIndex] & bitmask) != (tb[byteIndex] & bitmask)) // if bits aren't equal break + { + return i; // return index when matching stops + } + } + + return bitCount; // all match, return length + } + } + + private readonly struct AddressAndMaskTuple + { + public AddressAndMaskTuple(IPAddress head, IPAddress tail, IPAddress mask) + { + this.Head = head ?? throw new ArgumentNullException(nameof(head)); + this.Tail = tail ?? throw new ArgumentNullException(nameof(tail)); + this.Mask = mask ?? throw new ArgumentNullException(nameof(mask)); + } - // should contain an empty, pump the first with appropriate values - - // get the index of the collapse - var collapse = hextets.Select((value, - index) => new { value, index }) - .FirstOrDefault(pair => string.IsNullOrWhiteSpace(pair.value)); - - int collapseIndex; - if (collapse == null - && hextets.Count == 8) // no collapse - fully fledged address, all 8 hextets present - { - subnets = new[] { Parse(input, IPAddressUtilities.IPv6BitCount) }; - return true; - } - - // introduce a collapse at the end if one does not exist - if (collapse == null) // not fully fledged, add a collapse to the end - { - hextets.Add(string.Empty); - collapseIndex = hextets.Count - 1; - } - else // a collapse is present - { - collapseIndex = collapse.index; - } - - var subnetsList = new List(); - - hextetCount = hextets.Count; - var permutationLimit = (IPAddressUtilities.IPv6HextetCount + 1) - hextetCount; // number of permutations of IP partial, every hextet available removes a permutation - for (var permutation = 0; permutation <= permutationLimit; permutation++) - { - var hextetsCopy = hextets.ToList(); - hextetsCopy.RemoveAt(collapseIndex); // collapsed empty item - hextetsCopy.InsertRange(collapseIndex, Enumerable.Repeat("0", permutation)); // add zeros as appropriate - - var addressString = string.Join(":", hextetsCopy); // re join string - - if (!IPAddress.TryParse(addressString + "::", out var subnetAddress) - && !IPAddress.TryParse(addressString, out subnetAddress)) - { - subnets = Enumerable.Empty(); - return false; - } - - var routePrefix = ((permutation + hextetCount) - 1) * 16; - subnetsList.Add(new Subnet(subnetAddress, routePrefix)); - } - - subnets = subnetsList; - return true; - } - - // fail; could not parse anything useful from the input - subnets = Enumerable.Empty(); - return false; - - // chick input for multiple occurrences of discrete "::" substrings - bool DoubleColonsAppearsMultipleTimes(string @in) - { - const string colons = "::"; - int firstIndex; - - return @in.Length >= 4 - && (firstIndex = @in.IndexOf(colons, StringComparison.Ordinal)) != -1 - && @in.Substring(firstIndex + 2, @in.Length - firstIndex - 2) - .IndexOf(colons, StringComparison.Ordinal) - != -1; - } - } - - #endregion // end: From Partial - - #endregion // end: Parse / TryParse - - #endregion // end: Static Factory Methods - - /// - public bool Equals(Subnet other) - { - return !ReferenceEquals(null, other) - && (ReferenceEquals(this, other) - || (Equals(this.NetworkPrefixAddress, other.NetworkPrefixAddress) - && this.RoutingPrefix == other.RoutingPrefix)); - } - - /// - public override bool Equals(object obj) - { - return !ReferenceEquals(null, obj) - && (ReferenceEquals(this, obj) - || (obj.GetType() == GetType() - && this.Equals((Subnet)obj))); - } - - /// - public override int GetHashCode() - { - unchecked - { - return Head.GetHashCode() - * 29 - * AddressFamily.GetHashCode() - * 187 - * this.RoutingPrefix.GetHashCode(); - } - } - - #region From Interface ISerializable - - /// - /// is - public void GetObjectData([NotNull] SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - info.AddValue(nameof(this.BroadcastAddress), this.BroadcastAddress.GetAddressBytes()); - info.AddValue(nameof(this.RoutingPrefix), this.RoutingPrefix); - } - - #endregion - #region operators - - /// - /// Compares two objects for equality - /// - /// left hand operand - /// right hand operand - /// when both sides are equal - public static bool operator ==(Subnet left, - Subnet right) - { - return Equals(left, right); - } - - /// - /// Compares two objects for non-equality - /// - /// left hand operand - /// right hand operand - /// when both sides are not equal - public static bool operator !=(Subnet left, - Subnet right) - { - return !Equals(left, right); - } - - /// - /// Compares two objects for being less than - /// - /// - /// left hand operand - /// right hand operand - /// when is less than - public static bool operator <(Subnet left, - Subnet right) - { - return Comparer.Default.Compare(left, right) < 0; - } - - /// - /// Compares two objects for being greater than - /// - /// - /// left hand operand - /// right hand operand - /// when is greater than - public static bool operator >(Subnet left, - Subnet right) - { - return Comparer.Default.Compare(left, right) > 0; - } - - /// - /// Compares two objects for being less than or equal - /// - /// - /// left hand operand - /// right hand operand - /// - /// when is less than or equal to - /// - public static bool operator <=(Subnet left, - Subnet right) - { - return Comparer.Default.Compare(left, right) <= 0; - } - - /// - /// Compares two objects for being greater than or equal to - /// - /// - /// left hand operand - /// right hand operand - /// - /// when is greater than or equal to - /// - public static bool operator >=(Subnet left, - Subnet right) - { - return Comparer.Default.Compare(left, right) >= 0; - } - - #endregion end operators - - #region Static metods, may be appropriate for extracting - - private struct AddressMaskAndPrefixTuple - { - public AddressMaskAndPrefixTuple([NotNull] IPAddress head, - [NotNull] IPAddress tail, - [NotNull] IPAddress mask, - int prefix) - { - this.Head = head ?? throw new ArgumentNullException(nameof(head)); - this.Tail = tail ?? throw new ArgumentNullException(nameof(tail)); - this.Mask = mask ?? throw new ArgumentNullException(nameof(mask)); - - if (prefix < 0) - { - throw new ArgumentOutOfRangeException(nameof(prefix)); - } - - this.Prefix = prefix; - } - - /// - /// Head - /// - [NotNull] - public IPAddress Head { get; } - - /// - /// Tail - /// - [NotNull] - public IPAddress Tail { get; } - - /// - /// Mask - /// - [NotNull] - public IPAddress Mask { get; } - - /// - /// Prefix - /// - public int Prefix { get; } - } - - private static AddressMaskAndPrefixTuple NormalizeAndCreateNetMask([NotNull] IPAddress head, - [NotNull] IPAddress tail) - { - var headBytes = head.GetAddressBytes(); - var tailBytes = tail.GetAddressBytes(); - - var routingPrefix = CalculateRoutingPrefix(headBytes, tailBytes); - var result = NormalizeAndCreateNetMask(head, routingPrefix); - return new AddressMaskAndPrefixTuple(result.Head, result.Tail, result.Mask, routingPrefix); - - int CalculateRoutingPrefix(byte[] hb, - byte[] tb) - { - var bitCount = hb.Length * 8; // 8 bits per byte - - // iterate in order to find the count of common bits starting at the 0th element of each byte array - for (var i = 0; i < bitCount; i++) - { - var byteIndex = i / 8; - var bitmask = (byte)(0x80 >> (i % 8)); - if ((hb[byteIndex] & bitmask) != (tb[byteIndex] & bitmask)) // if bits aren't equal break - { - return i; // return index when matching stops - } - } - - return bitCount; // all match, return length - } - } - - private struct AddressAndMaskTuple - { - public AddressAndMaskTuple([NotNull] IPAddress head, - [NotNull] IPAddress tail, - [NotNull] IPAddress mask) - { - this.Head = head ?? throw new ArgumentNullException(nameof(head)); - this.Tail = tail ?? throw new ArgumentNullException(nameof(tail)); - this.Mask = mask ?? throw new ArgumentNullException(nameof(mask)); - } - - /// - /// Head - /// - [NotNull] - public IPAddress Head { get; } - - /// - /// Tail - /// - [NotNull] - public IPAddress Tail { get; } - - /// - /// Mask - /// - [NotNull] - public IPAddress Mask { get; } - } - - private static AddressAndMaskTuple NormalizeAndCreateNetMask([NotNull] IPAddress head, - int routingPrefix) - { - var headBytes = head.GetAddressBytes(); - var addressByteLength = headBytes.Length; - - var maskBytes = Enumerable.Repeat((byte)0xFF, addressByteLength) - .ToArray() - .ShiftBitsLeft((addressByteLength * 8) - routingPrefix); - - var newHead = new IPAddress(ByteArrayUtils.BitwiseAndBigEndian(headBytes, maskBytes)); - var newTail = new IPAddress(ByteArrayUtils.BitwiseOrBigEndian(headBytes, ByteArrayUtils.BitwiseNot(maskBytes))); - var netmask = new IPAddress(maskBytes); - - return new AddressAndMaskTuple(newHead, newTail, netmask); - } - - #endregion // end: Static metods, may be appropriate for extracting - - #region Deconstructors - - /// - /// Deconstruct to Network Prefix Address, Broadcast Address, Netmask, and Routing Prefix - /// - /// the subnet - /// the subnet - /// the subnet , will be for non IPv6 subnets - /// the subnet - public void Deconstruct([NotNull] out IPAddress networkPrefixAddress, - [NotNull] out IPAddress broadcastAddress, - out IPAddress netmask, - out int routingPrefix) - { - networkPrefixAddress = this.NetworkPrefixAddress; - broadcastAddress = this.BroadcastAddress; - netmask = this.Netmask; - routingPrefix = this.RoutingPrefix; - } - - /// - /// Deconstruct to Network Prefix Address and Routing Prefix - /// - /// the subnet - /// the subnet - public void Deconstruct([NotNull] out IPAddress networkPrefixAddress, - out int routingPrefix) - { - networkPrefixAddress = this.NetworkPrefixAddress; - routingPrefix = this.RoutingPrefix; - } - - #endregion // end: Deconstruct - } + /// + /// Gets head + /// + public IPAddress Head { get; } + + /// + /// Gets tail + /// + public IPAddress Tail { get; } + + /// + /// Gets mask + /// + public IPAddress Mask { get; } + } + + private static AddressAndMaskTuple NormalizeAndCreateNetMask(IPAddress head, int routingPrefix) + { + var headBytes = head.GetAddressBytes(); + var addressByteLength = headBytes.Length; + + var maskBytes = Enumerable + .Repeat((byte)0xFF, addressByteLength) + .ToArray() + .ShiftBitsLeft((addressByteLength * 8) - routingPrefix); + + var newHead = new IPAddress(ByteArrayUtils.BitwiseAndBigEndian(headBytes, maskBytes)); + var newTail = new IPAddress(ByteArrayUtils.BitwiseOrBigEndian(headBytes, ByteArrayUtils.BitwiseNot(maskBytes))); + var netmask = new IPAddress(maskBytes); + + return new AddressAndMaskTuple(newHead, newTail, netmask); + } + + #endregion // end: Static metods, may be appropriate for extracting + + #region Deconstructors + + /// + /// Deconstruct to Network Prefix Address, Broadcast Address, Netmask, and Routing Prefix + /// + /// the subnet + /// the subnet + /// the subnet , will be for non IPv6 subnets + /// the subnet + public void Deconstruct( + out IPAddress networkPrefixAddress, + out IPAddress broadcastAddress, + out IPAddress netmask, + out int routingPrefix + ) + { + networkPrefixAddress = this.NetworkPrefixAddress; + broadcastAddress = this.BroadcastAddress; + netmask = this.Netmask; + routingPrefix = this.RoutingPrefix; + } + + /// + /// Deconstruct to Network Prefix Address and Routing Prefix + /// + /// the subnet + /// the subnet + public void Deconstruct(out IPAddress networkPrefixAddress, out int routingPrefix) + { + networkPrefixAddress = this.NetworkPrefixAddress; + routingPrefix = this.RoutingPrefix; + } + + #endregion // end: Deconstruct + } } diff --git a/src/Arcus/Utilities/IPAddressUtilities.cs b/src/Arcus/Utilities/IPAddressUtilities.cs index 951783e..f7b4238 100644 --- a/src/Arcus/Utilities/IPAddressUtilities.cs +++ b/src/Arcus/Utilities/IPAddressUtilities.cs @@ -1,11 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; using Gulliver; -using JetBrains.Annotations; namespace Arcus.Utilities { @@ -49,38 +48,49 @@ public static class IPAddressUtilities /// public const int IPv6HextetCount = 8; - private static readonly Regex HexLikeRegularExpression = new Regex(HexLikePattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private static readonly Regex DottedQuadLeadingZerosRegularExpression = new Regex(DottedQuadLeadingZerosPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant); - private static readonly Regex DottedQuadStringRegularExpression = new Regex(DottedQuadRegularExpressionPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant); + private static readonly Regex HexLikeRegularExpression = new Regex( + HexLikePattern, + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant + ); + private static readonly Regex DottedQuadLeadingZerosRegularExpression = new Regex( + DottedQuadLeadingZerosPattern, + RegexOptions.Compiled | RegexOptions.CultureInvariant + ); + private static readonly Regex DottedQuadStringRegularExpression = new Regex( + DottedQuadRegularExpressionPattern, + RegexOptions.Compiled | RegexOptions.CultureInvariant + ); /// /// Maximum IPv4 Address value (255.255.255.255) /// - [NotNull] public static readonly IPAddress IPv4MaxAddress = new IPAddress(uint.MaxValue); /// /// Minimum IPv4 Address value (0.0.0.0) /// - [NotNull] public static readonly IPAddress IPv4MinAddress = new IPAddress(0); /// /// Maximum IPv6 value (ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff) /// - [NotNull] - public static readonly IPAddress IPv6MaxAddress = new IPAddress(new byte[] {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0L); + public static readonly IPAddress IPv6MaxAddress = new IPAddress( + new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + 0L + ); /// /// Standard valid /// - [NotNull] - public static readonly IReadOnlyCollection ValidAddressFamilies = new List {AddressFamily.InterNetwork, AddressFamily.InterNetworkV6}.AsReadOnly(); + public static readonly IReadOnlyCollection ValidAddressFamilies = new List + { + AddressFamily.InterNetwork, + AddressFamily.InterNetworkV6, + }.AsReadOnly(); /// /// Minimum IPv6 value (::) /// - [NotNull] public static readonly IPAddress IPv6MinAddress = new IPAddress(new byte[IPv6ByteCount], 0L); #region Address Max / Minimum @@ -90,7 +100,7 @@ public static class IPAddressUtilities /// ) /// /// the - [NotNull] + /// when is or when public static IPAddress MaxIPAddress(this AddressFamily addressFamily) { switch (addressFamily) @@ -109,7 +119,7 @@ public static IPAddress MaxIPAddress(this AddressFamily addressFamily) /// ) /// /// the - [NotNull] + /// when is or when public static IPAddress MinIPAddress(this AddressFamily addressFamily) { switch (addressFamily) @@ -132,7 +142,7 @@ public static IPAddress MinIPAddress(this AddressFamily addressFamily) /// /// the IPAddress to test /// true if ipv4 - public static bool IsIPv4([CanBeNull] this IPAddress ipAddress) + public static bool IsIPv4(this IPAddress ipAddress) { return ipAddress != null && ipAddress.AddressFamily == AddressFamily.InterNetwork; } @@ -142,7 +152,7 @@ public static bool IsIPv4([CanBeNull] this IPAddress ipAddress) /// /// the IPAddress to test /// true if ipv6 - public static bool IsIPv6([CanBeNull] this IPAddress ipAddress) + public static bool IsIPv6(this IPAddress ipAddress) { return ipAddress != null && ipAddress.AddressFamily == AddressFamily.InterNetworkV6; } @@ -158,20 +168,16 @@ public static bool IsIPv6([CanBeNull] this IPAddress ipAddress) /// the IPAddress to test /// true if is an IPv4 address mapped to IPv6 /// is . - public static bool IsIPv4MappedIPv6([CanBeNull] this IPAddress ipAddress) + public static bool IsIPv4MappedIPv6(this IPAddress ipAddress) { - if (ipAddress == null - || !ipAddress.IsIPv6()) + if (ipAddress == null || !ipAddress.IsIPv6()) { return false; } var addressBytes = ipAddress.GetAddressBytes(); - return addressBytes.Take(10) - .All(b => b == 0x00) - && addressBytes[10] == 0xff - && addressBytes[11] == 0xff; + return addressBytes.Take(10).All(b => b == 0x00) && addressBytes[10] == 0xff && addressBytes[11] == 0xff; } /// @@ -179,10 +185,9 @@ public static bool IsIPv4MappedIPv6([CanBeNull] this IPAddress ipAddress) /// /// the netmask to test /// true if the given input is a valid netmask - public static bool IsValidNetMask([CanBeNull] this IPAddress netmask) + public static bool IsValidNetMask(this IPAddress netmask) { - if (netmask == null - || netmask.AddressFamily != AddressFamily.InterNetwork) + if (netmask == null || netmask.AddressFamily != AddressFamily.InterNetwork) { return false; } @@ -191,7 +196,7 @@ public static bool IsValidNetMask([CanBeNull] this IPAddress netmask) var netmaskBytes = netmask.GetAddressBytes(); for (var i = 0; i < netmaskBytes.Length * 8; i++) { - var bitMask = (byte) (0x80 >> (i % 8)); + var bitMask = (byte)(0x80 >> (i % 8)); var indexSet = (netmaskBytes[i / 8] & bitMask) != 0; if (!set && indexSet) @@ -220,9 +225,7 @@ public static bool IsValidNetMask([CanBeNull] this IPAddress netmask) /// hex input /// address family /// IP Address, or if parse fails - [NotNull] - public static IPAddress ParseFromHexString([NotNull] string input, - AddressFamily addressFamily) + public static IPAddress ParseFromHexString(string input, AddressFamily addressFamily) { #region defense @@ -238,15 +241,18 @@ public static IPAddress ParseFromHexString([NotNull] string input, if (!ValidAddressFamilies.Contains(addressFamily)) { - throw new ArgumentException($"{nameof(addressFamily)} must be in {string.Join(", ", ValidAddressFamilies)}", nameof(addressFamily)); + throw new ArgumentException( + $"{nameof(addressFamily)} must be in {string.Join(", ", ValidAddressFamilies)}", + nameof(addressFamily) + ); } #endregion // end: defense // ignore "0x" prefix, and trim most significant 0s - var byteString = (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase) - ? input.Substring(2, input.Length - 2) - : input).TrimStart('0'); + var byteString = ( + input.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? input.Substring(2, input.Length - 2) : input + ).TrimStart('0'); // if the byte string has an odd number of characters provide a single significant 0 if (byteString.Length % 2 != 0) @@ -261,10 +267,11 @@ public static IPAddress ParseFromHexString([NotNull] string input, } // for each i%2, convert i, i+1 into a byte, reverse, and convert into an array - var byteArray = Enumerable.Range(0, byteString.Length / 2) - .Select(i => i * 2) - .Select(i => Convert.ToByte(byteString.Substring(i, 2), 16)) - .ToArray(); + var byteArray = Enumerable + .Range(0, byteString.Length / 2) + .Select(i => i * 2) + .Select(i => Convert.ToByte(byteString.Substring(i, 2), 16)) + .ToArray(); return Parse(byteArray, addressFamily); } @@ -277,9 +284,7 @@ public static IPAddress ParseFromHexString([NotNull] string input, /// the desired address family /// the address that was parsed /// true on success - public static bool TryParseFromHexString([CanBeNull] string input, - AddressFamily addressFamily, - [CanBeNull] out IPAddress address) + public static bool TryParseFromHexString(string input, AddressFamily addressFamily, out IPAddress address) { if (input == null) { @@ -292,9 +297,7 @@ public static bool TryParseFromHexString([CanBeNull] string input, address = ParseFromHexString(input, addressFamily); return true; } -#pragma warning disable CA1031 // catch is purposely general catch -#pragma warning restore CA1031 { address = null; return false; @@ -314,8 +317,7 @@ public static bool TryParseFromHexString([CanBeNull] string input, /// notation for IPv6 /// /// An instance - [NotNull] - public static IPAddress ParseIgnoreOctalInIPv4([NotNull] string input) + public static IPAddress ParseIgnoreOctalInIPv4(string input) { #region defense @@ -347,8 +349,7 @@ public static IPAddress ParseIgnoreOctalInIPv4([NotNull] string input) /// The string to validate. /// The version of the string. /// true if ipString is a valid IP address; otherwise, false. - public static bool TryParseIgnoreOctalInIPv4([CanBeNull] string input, - [CanBeNull] out IPAddress address) + public static bool TryParseIgnoreOctalInIPv4(string input, out IPAddress address) { if (input == null) { @@ -361,9 +362,7 @@ public static bool TryParseIgnoreOctalInIPv4([CanBeNull] string input, address = ParseIgnoreOctalInIPv4(input); return true; } -#pragma warning disable CA1031 // catch is purposely general catch -#pragma warning restore CA1031 { address = null; return false; @@ -379,8 +378,8 @@ public static bool TryParseIgnoreOctalInIPv4([CanBeNull] string input, /// /// the big endian IPAddress /// the desired address family - public static IPAddress Parse([NotNull] byte[] input, - AddressFamily addressFamily) + /// The parsed + public static IPAddress Parse(byte[] input, AddressFamily addressFamily) { if (input == null) { @@ -402,7 +401,10 @@ public static IPAddress Parse([NotNull] byte[] input, if (input.Length > expectedByteCount) { - throw new ArgumentOutOfRangeException(nameof(input), $"{nameof(input)} length is greater than the expected byte count of {expectedByteCount} for {addressFamily}"); + throw new ArgumentOutOfRangeException( + nameof(input), + $"{nameof(input)} length is greater than the expected byte count of {expectedByteCount} for {addressFamily}" + ); } return new IPAddress(input.PadBigEndianMostSignificantBytes(expectedByteCount)); @@ -415,18 +417,14 @@ public static IPAddress Parse([NotNull] byte[] input, /// the desired address family /// the address on success /// true on success - public static bool TryParse(byte[] input, - AddressFamily addressFamily, - [CanBeNull] out IPAddress address) + public static bool TryParse(byte[] input, AddressFamily addressFamily, out IPAddress address) { try { address = Parse(input, addressFamily); return true; } -#pragma warning disable CA1031 // catch is purposely general catch -#pragma warning restore CA1031 { address = null; return false; @@ -443,9 +441,9 @@ public static bool TryParse(byte[] input, /// Determines if an is a private address. /// /// the input address - /// iff the is private. + /// if, and only if, the is private. /// is - public static bool IsPrivate([NotNull] this IPAddress address) + public static bool IsPrivate(this IPAddress address) { if (address is null) { diff --git a/src/Arcus/Utilities/SubnetUtilities.cs b/src/Arcus/Utilities/SubnetUtilities.cs index e4b074d..b639074 100644 --- a/src/Arcus/Utilities/SubnetUtilities.cs +++ b/src/Arcus/Utilities/SubnetUtilities.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using Arcus.Math; -using JetBrains.Annotations; namespace Arcus.Utilities { @@ -15,34 +14,26 @@ public static class SubnetUtilities /// /// A collection of all known private IP Address ranges /// - [NotNull] - [ItemNotNull] public static IReadOnlyList PrivateIPAddressRangesList = new[] - { - // IPv4 RFC 1918 - Subnet.Parse("10.0.0.0", 8), - Subnet.Parse("172.16.0.0", 12), - Subnet.Parse("192.168.0.0", 16), - - // IPv6 RFC 4193 - Subnet.Parse("fd00::", 8) - }.ToList() - .AsReadOnly(); + { + // IPv4 RFC 1918 + Subnet.Parse("10.0.0.0", 8), + Subnet.Parse("172.16.0.0", 12), + Subnet.Parse("192.168.0.0", 16), + // IPv6 RFC 4193 + Subnet.Parse("fd00::", 8), + }.ToList().AsReadOnly(); /// /// A collection of all known Link Local IP Address ranges /// - [NotNull] - [ItemNotNull] public static IReadOnlyList LinkLocalIPAddressRangesList = new[] - { - // RFC 3927 - Subnet.Parse("169.254.0.0", 16), - - // RFC 4291 - Subnet.Parse("fe80::", 10) - }.ToList() - .AsReadOnly(); + { + // RFC 3927 + Subnet.Parse("169.254.0.0", 16), + // RFC 4291 + Subnet.Parse("fe80::", 10), + }.ToList().AsReadOnly(); /// /// Get The fewest consecutive subnets that would fill the range between the given addresses (inclusive) @@ -54,10 +45,7 @@ public static class SubnetUtilities /// is . /// Address families must match /// Address families must be InterNetwork or InternetworkV6 - [NotNull] - [ItemNotNull] - public static IEnumerable FewestConsecutiveSubnetsFor([NotNull] IPAddress left, - [NotNull] IPAddress right) + public static IEnumerable FewestConsecutiveSubnetsFor(IPAddress left, IPAddress right) { #region defense @@ -73,12 +61,18 @@ public static IEnumerable FewestConsecutiveSubnetsFor([NotNull] IPAddres if (!IPAddressUtilities.ValidAddressFamilies.Contains(left.AddressFamily)) { - throw new ArgumentException($"{nameof(left)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(left)); + throw new ArgumentException( + $"{nameof(left)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(left) + ); } if (!IPAddressUtilities.ValidAddressFamilies.Contains(right.AddressFamily)) { - throw new ArgumentException($"{nameof(right)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", nameof(right)); + throw new ArgumentException( + $"{nameof(right)} must have an address family equal to {string.Join(", ", IPAddressUtilities.ValidAddressFamilies)}", + nameof(right) + ); } if (left.AddressFamily != right.AddressFamily) @@ -96,26 +90,28 @@ public static IEnumerable FewestConsecutiveSubnetsFor([NotNull] IPAddres // recursive function call // Works by verifying that passed subnet isn't bounded by head, tail IP Addresses // if not breaks subnet in half and recursively tests, building in essence a binary tree of testable subnet paths - IEnumerable FilledSubnets(IPAddress head, - IPAddress tail, - Subnet subnet) +#if NET6_0_OR_GREATER + static +#endif + IEnumerable FilledSubnets(IPAddress head, IPAddress tail, Subnet subnet) { var networkPrefixAddress = subnet.NetworkPrefixAddress; var broadcastAddress = subnet.BroadcastAddress; // the given subnet is the perfect size for the head/tail (not papa bear, not mama bear, but just right with baby bear) - if (networkPrefixAddress.IsGreaterThanOrEqualTo(head) - && broadcastAddress.IsLessThanOrEqualTo(tail)) + if (networkPrefixAddress.IsGreaterThanOrEqualTo(head) && broadcastAddress.IsLessThanOrEqualTo(tail)) { - return new[] {subnet}; + return new[] { subnet }; } // increasing the route prefix by 1 creates a subnet of half the initial size (due 2^(max-n) route prefix sizing) var nextSmallestRoutePrefix = subnet.RoutingPrefix + 1; // over-iterated route prefix, no valid subnet beyond this point; end search on this branch - if ((subnet.IsIPv6 && nextSmallestRoutePrefix > IPAddressUtilities.IPv6BitCount) - || (subnet.IsIPv4 && nextSmallestRoutePrefix > IPAddressUtilities.IPv4BitCount)) + if ( + (subnet.IsIPv6 && nextSmallestRoutePrefix > IPAddressUtilities.IPv6BitCount) + || (subnet.IsIPv4 && nextSmallestRoutePrefix > IPAddressUtilities.IPv4BitCount) + ) { return Enumerable.Empty(); // no subnets to be found here, stop investigating branch of tree } @@ -129,12 +125,10 @@ IEnumerable FilledSubnets(IPAddress head, throw new InvalidOperationException($"unable to increment {headSubnet.BroadcastAddress}"); } - // ReSharper disable once AssignNullToNotNullAttribute var tailSubnet = new Subnet(tailStartingAddress, nextSmallestRoutePrefix); // break into binary search tree, searching both head subnet and tail subnet for ownership of head and tail ip - return FilledSubnets(head, tail, headSubnet) - .Concat(FilledSubnets(head, tail, tailSubnet)); + return FilledSubnets(head, tail, headSubnet).Concat(FilledSubnets(head, tail, tailSubnet)); } } @@ -148,19 +142,13 @@ IEnumerable FilledSubnets(IPAddress head, /// The first largest subnet by routing prefix, or if no to /// choose from /// - [CanBeNull] - public static Subnet LargestSubnet([CanBeNull] IEnumerable subnets) + public static Subnet LargestSubnet(IEnumerable subnets) { var enumerable = (subnets ?? Enumerable.Empty()).ToList(); return !enumerable.Any() - ? null - : enumerable - .Where(s => s != null) - .Aggregate((s1, - s2) => s1.RoutingPrefix < s2.RoutingPrefix - ? s1 - : s2); + ? null + : enumerable.Where(s => s != null).Aggregate((s1, s2) => s1.RoutingPrefix < s2.RoutingPrefix ? s1 : s2); } /// @@ -170,18 +158,13 @@ public static Subnet LargestSubnet([CanBeNull] IEnumerable subnets) /// /// the list of subnets /// The first smallest subnet by routing prefix, or null if no subnets to choose from - [CanBeNull] - public static Subnet SmallestSubnet([CanBeNull] IEnumerable subnets) + public static Subnet SmallestSubnet(IEnumerable subnets) { var enumerable = (subnets ?? Enumerable.Empty()).ToList(); return !enumerable.Any() - ? null - : enumerable.Where(s => s != null) - .Aggregate((s1, - s2) => s1.RoutingPrefix > s2.RoutingPrefix - ? s1 - : s2); + ? null + : enumerable.Where(s => s != null).Aggregate((s1, s2) => s1.RoutingPrefix > s2.RoutingPrefix ? s1 : s2); } } } diff --git a/src/Arcus/packages.lock.json b/src/Arcus/packages.lock.json new file mode 100644 index 0000000..b4f8fd9 --- /dev/null +++ b/src/Arcus/packages.lock.json @@ -0,0 +1,235 @@ +{ + "version": 1, + "dependencies": { + ".NETStandard,Version=v2.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Gulliver": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.Bcl.HashCode": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + } + }, + "net8.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Gulliver": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + } + }, + "net9.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "Gulliver": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "83JnWd36tU9P9xvz4fkcq0hoVrccJZbfyo1luKM1jvqoRVU47kaSIiEsplzJKJPjgbseiNnzssicEgaheRnOKg==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Microsoft.VisualStudio.Threading.Analyzers": { + "type": "Direct", + "requested": "[17.13.2, )", + "resolved": "17.13.2", + "contentHash": "Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==" + }, + "Roslynator.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "KZpLy6ZlCebMk+d/3I5KU2R7AOb4LNJ6tPJqPtvFXmO8bEBHQvCIAvJOnY2tu4C9/aVOROTDYUFADxFqw1gh/g==" + }, + "Roslynator.CodeAnalysis.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "PwbuGlRFp87pdx9GVIjDmFwkYEfxVQKs0yWuIedTrUht2JwIzLe0Y7QNzgeJaE7E3YsCVED09h1X6WBqQ95XIA==" + }, + "Roslynator.Formatting.Analyzers": { + "type": "Direct", + "requested": "[4.13.1, )", + "resolved": "4.13.1", + "contentHash": "V9gCt0T1Tnu+jMWJDLiUV/vOTmGzmofEdVeUPKPdh8MJW7f3kkhFXSeDotwl8Ob88OxrBf+4LrV/D7OA4tAluA==" + }, + "SonarAnalyzer.CSharp": { + "type": "Direct", + "requested": "[10.7.0.110445, )", + "resolved": "10.7.0.110445", + "contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.1.118, )", + "resolved": "1.1.118", + "contentHash": "Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + } + } + } +} \ No newline at end of file diff --git a/src/CodeConvention.DotSettings b/src/CodeConvention.DotSettings deleted file mode 100644 index 10c85b6..0000000 --- a/src/CodeConvention.DotSettings +++ /dev/null @@ -1,349 +0,0 @@ - - Inherit - 1000 - 3000 - Built-in: Full Cleanup - Required - Required - Required - Required - Arithmetic, Shift, Relational, Equality, Bitwise, Conditional, Lowest - ThisClass - Field, Property, Event, Method - True - True - True - True - True - True - True - True - True - True - True - True - True - 0 - 1 - True - True - True - True - USUAL_INDENT - USUAL_INDENT - True - True - True - 1 - 1 - True - 6 - 2 - 1 - 5 - NEVER - NEVER - False - NEVER - False - True - True - CHOP_IF_LONG - CHOP_IF_LONG - True - CHOP_IF_LONG - CHOP_ALWAYS - WRAP_IF_LONG - CHOP_ALWAYS - CHOP_ALWAYS - CHOP_ALWAYS - CHOP_ALWAYS - CHOP_ALWAYS - CHOP_ALWAYS - - 120 - OnSingleLine - False - False - False - <?xml version="1.0" encoding="utf-16"?> -<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="Non-reorderable types"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - <HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /> - <HasAttribute Name="JetBrains.Annotations.NoReorder" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="xUnit.net Test Classes"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasMember> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> - <HasAttribute Name="Xunit.TheoryAttribute" /> - </Or> - </And> - </HasMember> - </And> - </TypePattern.Match> - <Region Name="Setup / Teardown"> - <Entry DisplayName="Setup/Teardown Members"> - <Entry.Match> - <Or> - <And> - <Kind Is="Field" /> - </And> - <Kind Is="Constructor" /> - <And> - <Kind Is="Method" /> - <ImplementsInterface Name="System.IDisposable" /> - </And> - </Or> - </Entry.Match> - <Entry.SortBy> - <Kind Order="Constructor" /> - </Entry.SortBy> - </Entry> - </Region> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <Or> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - <ImplementsInterface Name="System.Collections.Generic.IEnumerable&lt;object[]&gt;" /> - </And> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="Xunit.FactAttribute" /> - <HasAttribute Name="Xunit.TheoryAttribute" /> - </Or> - </And> - </Or> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - <Region Name="other members"> - <Entry DisplayName="All other members" /> - </Region> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Entry Priority="100" DisplayName="Public Delegates"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Delegate" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - <Region Name="Public Enums"> - <Entry Priority="100" DisplayName="Public Enums"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Enum" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </Region> - <Entry DisplayName="Static Fields and Constants"> - <Entry.Match> - <Or> - <Kind Is="Constant" /> - <And> - <Kind Is="Field" /> - <Static /> - </And> - </Or> - </Entry.Match> - <Entry.SortBy> - <Readonly /> - <Kind Is="Member" /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Fields"> - <Entry.Match> - <And> - <Kind Is="Field" /> - <Not> - <Static /> - </Not> - </And> - </Entry.Match> - <Entry.SortBy> - <Readonly /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Constructors"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - <Entry.SortBy> - <Access /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Properties, Indexers"> - <Entry.Match> - <Or> - <Kind Is="Property" /> - <Kind Is="Indexer" /> - </Or> - </Entry.Match> - </Entry> - <Region Name="From Interface {0}"> - <Region.GroupBy> - <ImplementsInterface /> - </Region.GroupBy> - <Entry Priority="100" DisplayName="Interface Implementations"> - <Entry.Match> - <And> - <Kind Is="Member" /> - <ImplementsInterface /> - </And> - </Entry.Match> - <Entry.SortBy> - <ImplementsInterface Immediate="True" /> - </Entry.SortBy> - </Entry> - </Region> - <Entry DisplayName="All other members" /> - <Entry DisplayName="Nested Types"> - <Entry.Match> - <Kind Is="Type" /> - </Entry.Match> - </Entry> - </TypePattern> -</Patterns> - False - CIDR - IP - IPV - MAC - OS - SNL - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="IPv4" Suffix="" Style="AaBb" /><ExtraRule Prefix="IPv6" Suffix="" Style="AaBb" /><ExtraRule Prefix="IP" Suffix="" Style="AaBb" /></Policy> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="IPv4" Suffix="" Style="AaBb" /><ExtraRule Prefix="IPv6" Suffix="" Style="AaBb" /></Policy> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="IPv4" Suffix="" Style="AaBb" /><ExtraRule Prefix="IPv6" Suffix="" Style="AaBb" /><ExtraRule Prefix="" Suffix="_Test" Style="AaBb_AaBb" /><ExtraRule Prefix="" Suffix="_Test_Values" Style="AaBb_AaBb" /></Policy> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="IPv4" Suffix="" Style="AaBb" /><ExtraRule Prefix="IPv6" Suffix="" Style="AaBb" /></Policy> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="IP" Suffix="" Style="AaBb" /><ExtraRule Prefix="I" Suffix="Tests" Style="AaBb" /><ExtraRule Prefix="" Suffix="Tests" Style="AaBb" /></Policy> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - Xunit.Sdk.XunitException,Always - DO_NOTHING - LIVE_MONITOR - LIVE_MONITOR - DO_NOTHING - LIVE_MONITOR - LIVE_MONITOR - LIVE_MONITOR - LIVE_MONITOR - LIVE_MONITOR - LIVE_MONITOR - LIVE_MONITOR - LIVE_MONITOR - DO_NOTHING - LIVE_MONITOR - True - True - True - True - True - diff --git a/src/Templates.DotSettings b/src/Templates.DotSettings deleted file mode 100644 index 59229bf..0000000 --- a/src/Templates.DotSettings +++ /dev/null @@ -1,743 +0,0 @@ - - True - True - xUnit Testing - xUnit Class - True - getFileNameWithoutExtension() - 0 - True - 1 - True - True - 2.0 - InCSharpTypeAndNamespace - True - 2.0 - InCSharpTypeMember - True - *Test.cs - InFileWithMask - xclass - True - public class $Classname$ -{ - [Xunit.Fact] - public void $TESTNAME$_Test() - { - // Arrange - $END$ - - // Act - - // Assert - Xunit.Assert.True(false, "Auto generated test"); - } -} - True - True - xUnit Testing - xUnit IsSealed Test - True - completeType("") - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xissealed - True - [Xunit.Fact] -public void IsSealed_Test() -{ - // Arrange - var type = typeof($Type$); - - // Act - var isSealed = type.IsSealed; - - // Assert - Xunit.Assert.True(isSealed); -} -$END$ - True - True - Testing - Arrange Act Assert - True - True - 2.0 - InCSharpFile - aaa - True - // Arrange -$END$ - -// Act - -// Assert - True - True - public static method declaration - True - 0 - True - True - 2.0 - InCSharpTypeMember - psv - True - public static void $MethodName$() -{ - $END$ -} - True - True - xUnit Testing - xUnit IsNotSealed Test - True - completeType("") - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xnotsealed - True - [Xunit.Fact] -public void IsNotSealed_Test() -{ - // Arrange - var type = typeof($Type$); - - // Act - var isSealed = type.IsSealed; - - // Assert - Xunit.Assert.False(isSealed); -} -$END$ - True - True - xUnit Testing - xUnit async test - True - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Test.cs - InFileWithMask - xatest - True - [Xunit.Fact] -public async System.Threading.Tasks.Task $TESTNAME$_Test() -{ - // Arrange - $END$ - - // Act - - // Assert - Xunit.Assert.True(false, "Auto generated test"); -} - True - True - True - Formatting - pragma disable restore - True - 0 - True - True - True - 2.0 - InCSharpFile - #pragma - True - #pragma warning disable $WARNING$ // $END$ -$SELECTION$ -#pragma warning restore $WARNING$ - True - True - xUnit Testing - xUnit Theory MembData Test - True - 0 - True - 1 - True - True - 2.0 - InCSharpTypeMember - True - *Test.cs - InFileWithMask - xtheorymember - True - public static System.Collections.Generic.IEnumerable<object[]> $MemberName$_Test_Values() -{ - yield return new object[] { new $Type$() {} }; -} - -[Xunit.Theory] -[Xunit.MemberData(nameof($MemberName$_Test_Values))] -public void $MemberName$_Test($Type$ input) -{ - // Arrange - $END$ - - // Act - - // Assert - Xunit.Assert.True(false, "Auto generated test"); -} - True - True - xUnit Testing - xUnit Assignability Test - True - completeType("") - 1 - True - completeType("") - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xassignability - True - [Xunit.Theory] -[Xunit.InlineData(typeof($AssignableFrom$))] -public void Assignability_Test(System.Type assignableFromType) -{ - // Arrange - var type = typeof($AssignableTo$); - - // Act - var isAssignableFrom = assignableFromType.IsAssignableFrom(type); - - // Assert - Xunit.Assert.True(isAssignableFrom); -} -$END$ - True - True - xUnit Testing - xUnit IsInterface Test - True - completeType("") - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xisinterface - True - [Xunit.Fact] -public void IsInterface_Test() -{ - // Arrange - var type = typeof($Type$); - - // Act - var isInterface = type.IsInterface; - - // Assert - Xunit.Assert.True(isInterface); -} -$END$ - True - True - TestCop - cs - Class - True - MSTest TestCop Test - True - getFileNameWithoutExtension() - -1 - 1 - True - fileheader() - 0 - True - fileDefaultNamespace() - -1 - 2 - True - True - InCSharpProjectFile - True - $HEADER$ -using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace $NAMESPACE$ -{ - [TestClass] - public class $CLASS$ - { - [TestMethod] - public void Test() - { - //TestCop - Assert.Fail("WriteMe"); - } - $END$ - } -} - - - True - True - True - Formatting - region surround - True - 0 - True - True - True - 2.0 - InCSharpFile - #region - True - #region $REGION_TITLE$ -$SELECTION$ -#endregion end: $REGION_TITLE$ - True - True - xUnit Testing - xUnit IsConcrete Test - True - completeType("") - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xisconcrete - True - [Xunit.Fact] -public void IsConcrete_Test() -{ - // Arrange - var type = typeof($Type$); - - // Act - var isConcrete = type.IsClass && !type.IsAbstract; - - // Assert - Xunit.Assert.True(isConcrete); -} -$END$ - True - True - TestCop - cs - Class - True - NUnit TestCop Test - True - getFileNameWithoutExtension() - -1 - 2 - True - fileheader() - 0 - True - fileDefaultNamespace() - -1 - 1 - True - True - InCSharpProjectFile - True - $HEADER$ -using NUnit.Framework; -namespace $NAMESPACE$ -{ - [TestFixture] - public class $CLASS$ - { - [Test] - public void Test() - { - //TestCop - Assert.Fail("WriteMe"); - } - $END$ - } -} - - - True - True - TestCop - cs - Class - True - xUnit TestCop Test - True - getFileNameWithoutExtension() - -1 - 2 - True - fileheader() - 0 - True - fileDefaultNamespace() - -1 - 1 - True - 3 - True - True - InCSharpProjectFile - True - $HEADER$ -using Xunit; -using Xunit.Abstractions; - -namespace $NAMESPACE$ -{ - public class $CLASS$ - { - - private readonly ITestOutputHelper _output; - - public $CLASS$(ITestOutputHelper output) - { - this._output = output; - } - - - [Fact] - public void $Test$_Test() - { - // Arrange - - // Act - - // Assert - $SELSTART$ - Assert.True(false,"TODO"); // TODO test must be written - $SELEND$ - } - } -} - True - True - xUnit Testing - xUnit Theory InlineData Test - True - 0 - True - 1 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xtheory - True - [Xunit.Theory] -[Xunit.InlineData()] -public void $TestName$_Test($Type$ input) -{ - // Arrange - $END$ - - // Act - - // Assert - Xunit.Assert.True(false, "Auto generated test"); -} - True - True - xUnit Testing - xUnit IsAbstract Test - True - completeType("") - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xisabstract - True - [Xunit.Fact] -public void IsAbstract_Test() -{ - // Arrange - var type = typeof($Type$); - - // Act - var isAbstract = type.IsClass && type.IsAbstract; - - // Assert - Xunit.Assert.True(isAbstract); -} -$END$ - True - True - xUnit Testing - xUnit test - True - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Test.cs - InFileWithMask - xtest - True - [Xunit.Fact] -public void $TESTNAME$_Test() -{ - // Arrange - $END$ - - // Act - - // Assert - Xunit.Assert.True(false, "Auto generated test"); -} - True - True - xUnit Testing - xUnit Enum Tests - True - completeType("") - 0 - True - completeSmart() - 3 - True - spacestounderstrokes(Enum) - 2 - True - completeType("System.Int32") - 1 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xenum - True - #region other members - -public static System.Collections.Generic.IEnumerable<object[]> $EnumName$_Values() -{ - return Enum.GetValues(typeof($Enum$)) - .OfType<$Enum$>() - .Select(e => new object[] {e}); -} - -#endregion - -[Xunit.Fact] -public void $EnumName$_GetEnumUnderlyingType_Test() -{ - // Arrange - var type = typeof($Enum$); - var enumUnderlyingType = type.GetEnumUnderlyingType(); - - // Act - // Assert - Assert.True(typeof($underlyingType$).IsAssignableFrom(enumUnderlyingType)); -} - -[Xunit.Fact] -public void $EnumName$_IsEnum_Test() -{ - // Arrange - var type = typeof($Enum$); - - // Act - // Assert - Assert.True(type.IsEnum); -} - -[Xunit.Fact] -public void $EnumName$_IsNotFlags_Test() -{ - // Arrange - var type = typeof($Enum$); - var flagAttributes = type.GetCustomAttributes(typeof(System.FlagsAttribute), false); - - // Act - // Assert - Assert.Empty(flagAttributes); -} - -[Xunit.Fact] -public void $EnumName$_IsFlags_Test() -{ - // Arrange - var type = typeof($Enum$); - var flagAttributes = type.GetCustomAttributes(typeof(System.FlagsAttribute), false); - - // Act - // Assert - Assert.NotEmpty(flagAttributes); -} - - -[Xunit.Fact] -public void $EnumName$_UniqueValues_Test() -{ - // Arrange - var values = Enum.GetValues(typeof($Enum$)) - .OfType<$Enum$>(); - - // Act - var groupedValues = values.GroupBy(flag => ($underlyingType$) flag, flag => flag); - - // Assert - Assert.All(groupedValues, - valueGroup => Assert.True(valueGroup.Count() == 1, string.Join(",", valueGroup.Select(v => v.ToString())))); -} - -[Xunit.Fact] -public void $EnumName$_Length_Test() -{ - // Arrange - // Act - var count = Enum.GetValues(typeof($Enum$)) - .OfType<$Enum$>() - .Count(); - - // Assert - Assert.Equal(1, count); -} - -[Xunit.Fact] -public void $EnumName$_DefaultValue_Test() -{ - // Arrange - // Act - // Assert - Assert.Equal($Enum$.$EnumDefaultValue$, default($Enum$)); -} - -[Xunit.Theory] -[Xunit.InlineData(0, $Enum$.$EnumDefaultValue$)] -public void $EnumName$_Index_Test(int expected, - $Enum$ enumValue) -{ - // Arrange - // Act - // Assert - Assert.Equal(expected, (int) enumValue); -} - -[Xunit.Theory] -[Xunit.InlineData($Enum$.$EnumDefaultValue$, "$EnumDefaultValue$")] -public void $EnumName$_Parse_Test($Enum$ expected, - string parseString) -{ - // Arrange - // Act - var result = Enum.Parse(typeof($Enum$), parseString, false); - - // Assert - Assert.Equal(expected, result); -} - True - True - xUnit Testing - xUnit Theory Enum MembData Test - True - 1 - True - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Test.cs - InFileWithMask - xtheorymemberenum - True - public static System.Collections.Generic.IEnumerable<object[]> $TestName$_Test_Values() -{ - return Enum.GetValues(typeof($Type$)) - .OfType<$Type$>() - .Select(e => new object[] {e}); -} - -[Xunit.Theory] -[Xunit.MemberData(nameof($TestName$_Test_Values))] -public void $TestName$_Test($Type$ input) -{ - // Arrange - $END$ - - // Act - - // Assert - Xunit.Assert.True(false, "Auto generated test"); -} - True - True - xUnit Testing - xUnit IsSerializable Test - True - completeType("") - 0 - True - True - 2.0 - InCSharpTypeMember - True - *Tests.cs - InFileWithMask - xisserializable - True - [Xunit.Fact] -public void IsSerializable_Test() -{ - // Arrange - var type = typeof($Type$); - - // Act - var isSerializable = type.IsDefined(typeof(SerializableAttribute), false); - - // Assert - Xunit.Assert.True(isSerializable); -} -$END$ - diff --git a/src/analyzers.ruleset b/src/analyzers.ruleset deleted file mode 100644 index f9579f8..0000000 --- a/src/analyzers.ruleset +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/analyzers.tests.ruleset b/src/analyzers.tests.ruleset deleted file mode 100644 index e464110..0000000 --- a/src/analyzers.tests.ruleset +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/global.json b/src/global.json new file mode 100644 index 0000000..030495a --- /dev/null +++ b/src/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.101", + "rollForward": "latestFeature" + } +} diff --git a/src/stylecop.json b/src/stylecop.json index c7596a7..286a3cb 100644 --- a/src/stylecop.json +++ b/src/stylecop.json @@ -13,16 +13,13 @@ "allowBuiltInTypeAliases": false }, "orderingRules": { - "usingDirectivesPlacement": "outsideNamespace", - "systemUsingDirectivesFirst": true, - "blankLinesBetweenUsingGroups": "allow" + "usingDirectivesPlacement": "outsideNamespace" }, "maintainabilityRules": { "topLevelTypes": ["class", "interface", "struct", "enum", "delegate"] }, "layoutRules": { - "allowConsecutiveUsings": true, - "newlineAtEndOfFile": "require" + "allowConsecutiveUsings": false } } }