├── .github └── workflows │ ├── benchmarks.yml │ ├── build.yml │ └── publish-release.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── NetworkPrimitives.sln ├── benchmarks ├── Ipv4AddressParsing.cs ├── Ipv4AddressRange.cs ├── Ipv4AddressRangeLists.cs ├── Ipv4SubnetParsing.cs ├── NetworkPrimitives.Benchmarks.csproj ├── Program.cs └── TestData.cs ├── docs ├── benchmarks.md ├── json-converters.md ├── performance.md ├── range-lists.md └── subnetting.md ├── global.json ├── readme.md ├── samples └── Demo │ ├── Demo.Core.csproj │ ├── Demo.Framework.csproj │ ├── Directory.Build.props │ ├── Program.cs │ └── TestData.cs ├── src ├── Directory.Build.props ├── NetworkPrimitives.JsonConverters │ ├── Ipv4 │ │ ├── Ipv4AddressJsonConverter.cs │ │ ├── Ipv4AddressRangeJsonConverter.cs │ │ ├── Ipv4AddressRangeListJsonConverter.cs │ │ ├── Ipv4CidrJsonConverter.cs │ │ ├── Ipv4NetworkMatchJsonConverter.cs │ │ ├── Ipv4SubnetJsonConverter.cs │ │ ├── Ipv4SubnetMaskJsonConverter.cs │ │ └── Ipv4WildcardMaskJsonConverter.cs │ ├── Ipv6 │ │ ├── Ipv6AddressJsonConverter.cs │ │ ├── Ipv6CidrJsonConverter.cs │ │ ├── Ipv6SubnetJsonConverter.cs │ │ └── Ipv6SubnetMaskJsonConverter.cs │ ├── JsonSerializerExtensions.cs │ └── NetworkPrimitives.JsonConverters.csproj └── NetworkPrimitives │ ├── BitOperations.cs │ ├── Common │ ├── BuildInformationAttribute.cs │ ├── ExcludeFromCodeCoverageAttribute.cs │ ├── Extensions │ │ ├── CharExtensions.cs │ │ ├── IpAddressExtensions.cs │ │ ├── NumericExtensions.cs │ │ ├── QueueExtensions.cs │ │ ├── SliceExtensions.cs │ │ ├── SpanExtensions.cs │ │ ├── StackExtensions.cs │ │ └── StringExtensions.cs │ ├── INetworkPrimitive.cs │ ├── ISlice.cs │ ├── ITryFormat.cs │ ├── JetBrainsAnnotations.cs │ └── Utilities │ │ ├── Formatting.cs │ │ ├── ImmutableListWrapper.cs │ │ ├── ImmutableListWrapperBuilder.cs │ │ ├── Parsing.cs │ │ └── ReadOnlyListSpan.cs │ ├── Ipv4 │ ├── Address │ │ ├── Ipv4Address.cs │ │ ├── Ipv4AddressClass.cs │ │ ├── Ipv4AddressRangeType.cs │ │ └── Ipv4WellKnownRanges.cs │ ├── Formatting │ │ ├── Ipv4Formatting.cs │ │ └── Ipv4Parsing.cs │ ├── Ipv4Extensions.cs │ ├── Match │ │ ├── Ipv4NetworkMatch.cs │ │ └── Ipv4WildcardMask.cs │ ├── Range │ │ ├── Ipv4AddressRange.ClassEnumerator.cs │ │ ├── Ipv4AddressRange.RefStructEnumerator.cs │ │ └── Ipv4AddressRange.cs │ ├── RangeList │ │ ├── Ipv4AddressListSpan.cs │ │ ├── Ipv4AddressRangeList.cs │ │ └── Ipv4AddressRangeListEnumerator.cs │ ├── Subnet │ │ ├── Ipv4Cidr.cs │ │ ├── Ipv4Subnet.cs │ │ ├── Ipv4SubnetMask.cs │ │ ├── SubnetMaskLookups.cs │ │ └── SubnetOperations.cs │ └── SubnetTree │ │ ├── Ipv4SubnetDictionary.Add.cs │ │ ├── Ipv4SubnetDictionary.Collections.cs │ │ ├── Ipv4SubnetDictionary.Consolidate.cs │ │ ├── Ipv4SubnetDictionary.KeyCollection.cs │ │ ├── Ipv4SubnetDictionary.Lookups.cs │ │ ├── Ipv4SubnetDictionary.NodeEnumerator.cs │ │ ├── Ipv4SubnetDictionary.Nodes.cs │ │ ├── Ipv4SubnetDictionary.Remove.cs │ │ └── Ipv4SubnetDictionary.cs │ ├── Ipv6 │ ├── Address │ │ └── Ipv6Address.cs │ ├── Formatting │ │ ├── Ipv6FormatInfo.cs │ │ ├── Ipv6Formatting.cs │ │ └── Ipv6Parsing.cs │ └── Subnet │ │ ├── Ipv6Cidr.cs │ │ ├── Ipv6Subnet.cs │ │ ├── Ipv6SubnetMask.cs │ │ └── SubnetMaskLookups.cs │ ├── NetworkPrimitives - Backup.Ipv4.csproj │ ├── NetworkPrimitives.csproj │ ├── NetworkPrimitives.csproj.DotSettings │ └── PublicAPI │ ├── net5.0 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt │ └── netstandard2.0 │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt └── tests ├── Directory.Build.props ├── EmbeddedResourceUtils.cs ├── GenerateTestCases.ps1 ├── NetworkPrimitives.Tests ├── Common │ ├── CharExtensionTests.cs │ ├── EndianSwapTests.cs │ ├── ListSpanTests.cs │ └── ParsingTests.cs ├── Ipv4 │ ├── Cidr │ │ ├── ComparisonTests.cs │ │ ├── Ipv4CidrTests.cs │ │ ├── ParsingTests.cs │ │ └── ValueTests.cs │ ├── Ipv4AddressTests.cs │ ├── Ipv4MatchTests.cs │ ├── Ipv4SubnetMaskTests.cs │ ├── Ipv4SubnetTests.cs │ ├── Ipv4TestCase.cs │ ├── Ipv4WildcardMaskTests.cs │ └── RangeList │ │ ├── AddressListSpanTests.cs │ │ ├── Parsing.cs │ │ ├── RangeListTestCase.cs │ │ └── RangeListTests.cs ├── Ipv6 │ └── Ipv6AddressTests.cs ├── NetworkPrimitives.Tests.Core.csproj ├── NetworkPrimitives.Tests.Framework.csproj └── TestData.cs ├── randomips.json └── range-test-cases.json /.github/workflows/benchmarks.yml: -------------------------------------------------------------------------------- 1 | name: benchmarks 2 | 3 | on: 4 | workflow_dispatch: 5 | #push: 6 | # branches: [ master ] 7 | #pull_request: 8 | # branches: [ master ] 9 | 10 | 11 | jobs: 12 | benchmarks: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v1 21 | with: 22 | dotnet-version: 5.0.x 23 | include-prerelease: true 24 | 25 | - name: Build 26 | run: dotnet build -c Release ./benchmarks/NetworkPrimitives.Benchmarks.csproj 27 | 28 | - name: Run Benchmarks 29 | run: dotnet run --project ./benchmarks/NetworkPrimitives.Benchmarks.csproj -- --filter "*Benchmarks.*" 30 | 31 | - name: Upload artifacts 32 | uses: actions/upload-artifact@v2 33 | with: 34 | name: benchmark-results 35 | path: BenchmarkResults 36 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ dev, master ] 7 | pull_request: 8 | branches: [ dev, master ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Get Timestamp 19 | uses: gerred/actions/current-time@master 20 | env: 21 | $TIMESTAMP: "${{ steps.current-time.outputs.time }}" 22 | 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v1 25 | with: 26 | dotnet-version: 6.0.x 27 | include-prerelease: true 28 | 29 | - name: Build 30 | run: dotnet build --configuration Release NetworkPrimitives.sln 31 | 32 | - name: Test 33 | run: dotnet test --no-build --configuration Release NetworkPrimitives.sln --logger:"nunit;LogFilePath=../../test-results/{framework}.xml" 34 | 35 | - name: Upload artifacts 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: test-results 39 | path: test-results 40 | 41 | 42 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*' 5 | 6 | jobs: 7 | github-release: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 15 10 | env: 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | NUGET_FEED: https://api.nuget.org/v3/index.json 13 | NUGET_KEY: ${{ secrets.NUGET_API_KEY }} 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Verify commit exists in origin/master 20 | run: | 21 | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* 22 | git branch --remote --contains | grep origin/master 23 | 24 | - name: Add nuget source 25 | run: | 26 | dotnet nuget add source --username binarycow --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/binarycow/index.json" 27 | 28 | - name: Build 29 | run: dotnet build --configuration Release NetworkPrimitives.sln 30 | 31 | - name: Test 32 | run: dotnet test --no-build --configuration Release NetworkPrimitives.sln --logger:"nunit;LogFilePath=../../test-results/{framework}.xml" 33 | 34 | - name: Pack 35 | run: dotnet pack --configuration Release --no-build --output . --include-symbols --include-source NetworkPrimitives.sln 36 | 37 | - name: Upload test result artifacts 38 | uses: actions/upload-artifact@v2 39 | with: 40 | name: test-results 41 | path: test-results 42 | 43 | - name: Upload nuget package artifacts 44 | uses: actions/upload-artifact@v2 45 | with: 46 | name: packages 47 | path: NetworkPrimitives*.nupkg 48 | 49 | - name: Create Release 50 | uses: ncipollo/release-action@v1.8.10 51 | with: 52 | artifacts: "NetworkPrimitives*.nupkg" 53 | artifactErrorsFailBuild: true 54 | omitBody: true 55 | prerelease: true 56 | token: ${{ secrets.GITHUB_TOKEN }} 57 | 58 | - name: Push to GitHub nuget feed 59 | run: dotnet nuget push --source github --api-key ${{ secrets.GITHUB_TOKEN }} NetworkPrimitives*.nupkg --skip-duplicate 60 | 61 | - name: Push to official nuget feed 62 | run: dotnet nuget push --source $NUGET_FEED --api-key $NUGET_KEY NetworkPrimitives*.nupkg --skip-duplicate 63 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | enable 4 | preview 5 | latest 6 | en-US 7 | 8 | 9 | 10 | true 11 | 12 | 13 | 14 | $(WarningsNotAsErrors);RS0016 15 | 16 | 17 | 18 | $(MSBuildThisFileDirectory) 19 | 20 | 21 | 22 | 23 | 24 | $(NoWarn);NU5104 25 | 26 | 27 | 28 | binarycow 29 | © 2022 Mike Christiansen 30 | https://github.com/binarycow/NetworkPrimitives 31 | git 32 | MIT 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mike Christiansen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NetworkPrimitives.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31717.71 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9A24AE3F-E98C-406F-8EF6-8816224ECB2B}" 7 | ProjectSection(SolutionItems) = preProject 8 | src\Directory.Build.props = src\Directory.Build.props 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1044DFB0-ADCA-41F0-AFA3-B942A47792A1}" 12 | ProjectSection(SolutionItems) = preProject 13 | tests\Directory.Build.props = tests\Directory.Build.props 14 | tests\randomips.json = tests\randomips.json 15 | tests\range-test-cases.json = tests\range-test-cases.json 16 | tests\range-list-test-cases.json = tests\range-list-test-cases.json 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkPrimitives", "src\NetworkPrimitives\NetworkPrimitives.csproj", "{F30DAE77-C764-4CE0-9E63-FF992AEEE251}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C004A053-6172-4403-935C-6B60AC3B2F53}" 22 | ProjectSection(SolutionItems) = preProject 23 | LICENSE = LICENSE 24 | readme.md = readme.md 25 | docs\subnetting.md = docs\subnetting.md 26 | docs\performance.md = docs\performance.md 27 | docs\json-converters.md = docs\json-converters.md 28 | docs\range-lists.md = docs\range-lists.md 29 | docs\benchmarks.md = docs\benchmarks.md 30 | EndProjectSection 31 | EndProject 32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{27430A89-25BF-45CC-9F7B-A7F3432045D5}" 33 | ProjectSection(SolutionItems) = preProject 34 | samples\Demo\Directory.Build.props = samples\Demo\Directory.Build.props 35 | EndProjectSection 36 | EndProject 37 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.Core", "samples\Demo\Demo.Core.csproj", "{96F4A4B9-9A95-4ABA-89AC-9279DD385331}" 38 | EndProject 39 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.Framework", "samples\Demo\Demo.Framework.csproj", "{C53DE5F2-870E-4A8B-A9E6-0C714A7FC14E}" 40 | EndProject 41 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkPrimitives.Tests.Core", "tests\NetworkPrimitives.Tests\NetworkPrimitives.Tests.Core.csproj", "{43216EA7-3C51-424D-8A17-E49A77B0BF19}" 42 | EndProject 43 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkPrimitives.Tests.Framework", "tests\NetworkPrimitives.Tests\NetworkPrimitives.Tests.Framework.csproj", "{07E0DA29-1157-4C7E-9211-45EB25E90C69}" 44 | EndProject 45 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{9C775FC2-BF3D-4316-9800-5B1FE338E8A0}" 46 | EndProject 47 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkPrimitives.Benchmarks", "benchmarks\NetworkPrimitives.Benchmarks.csproj", "{5CBAECD8-2497-48AD-85B5-9E961671E988}" 48 | EndProject 49 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkPrimitives.JsonConverters", "src\NetworkPrimitives.JsonConverters\NetworkPrimitives.JsonConverters.csproj", "{42CF6E7C-297B-4428-BB8E-66DFB05BB6DA}" 50 | EndProject 51 | Global 52 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 53 | src\NetworkPrimitives.Shared\NetworkPrimitives.Shared.projitems*{f30dae77-c764-4ce0-9e63-ff992aeee251}*SharedItemsImports = 5 54 | src\NetworkPrimitives.Shared\NetworkPrimitives.Shared.projitems*{f91cd3ed-a1ca-4515-9de9-264f8ac68625}*SharedItemsImports = 13 55 | EndGlobalSection 56 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 57 | Debug|Any CPU = Debug|Any CPU 58 | Release|Any CPU = Release|Any CPU 59 | EndGlobalSection 60 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 61 | {F30DAE77-C764-4CE0-9E63-FF992AEEE251}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {F30DAE77-C764-4CE0-9E63-FF992AEEE251}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {F30DAE77-C764-4CE0-9E63-FF992AEEE251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {F30DAE77-C764-4CE0-9E63-FF992AEEE251}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {96F4A4B9-9A95-4ABA-89AC-9279DD385331}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {96F4A4B9-9A95-4ABA-89AC-9279DD385331}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {96F4A4B9-9A95-4ABA-89AC-9279DD385331}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {96F4A4B9-9A95-4ABA-89AC-9279DD385331}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {C53DE5F2-870E-4A8B-A9E6-0C714A7FC14E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 70 | {C53DE5F2-870E-4A8B-A9E6-0C714A7FC14E}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {C53DE5F2-870E-4A8B-A9E6-0C714A7FC14E}.Release|Any CPU.ActiveCfg = Release|Any CPU 72 | {C53DE5F2-870E-4A8B-A9E6-0C714A7FC14E}.Release|Any CPU.Build.0 = Release|Any CPU 73 | {43216EA7-3C51-424D-8A17-E49A77B0BF19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 74 | {43216EA7-3C51-424D-8A17-E49A77B0BF19}.Debug|Any CPU.Build.0 = Debug|Any CPU 75 | {43216EA7-3C51-424D-8A17-E49A77B0BF19}.Release|Any CPU.ActiveCfg = Release|Any CPU 76 | {43216EA7-3C51-424D-8A17-E49A77B0BF19}.Release|Any CPU.Build.0 = Release|Any CPU 77 | {07E0DA29-1157-4C7E-9211-45EB25E90C69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 78 | {07E0DA29-1157-4C7E-9211-45EB25E90C69}.Debug|Any CPU.Build.0 = Debug|Any CPU 79 | {07E0DA29-1157-4C7E-9211-45EB25E90C69}.Release|Any CPU.ActiveCfg = Release|Any CPU 80 | {07E0DA29-1157-4C7E-9211-45EB25E90C69}.Release|Any CPU.Build.0 = Release|Any CPU 81 | {5CBAECD8-2497-48AD-85B5-9E961671E988}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 82 | {5CBAECD8-2497-48AD-85B5-9E961671E988}.Debug|Any CPU.Build.0 = Debug|Any CPU 83 | {5CBAECD8-2497-48AD-85B5-9E961671E988}.Release|Any CPU.ActiveCfg = Release|Any CPU 84 | {5CBAECD8-2497-48AD-85B5-9E961671E988}.Release|Any CPU.Build.0 = Release|Any CPU 85 | {42CF6E7C-297B-4428-BB8E-66DFB05BB6DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 86 | {42CF6E7C-297B-4428-BB8E-66DFB05BB6DA}.Debug|Any CPU.Build.0 = Debug|Any CPU 87 | {42CF6E7C-297B-4428-BB8E-66DFB05BB6DA}.Release|Any CPU.ActiveCfg = Release|Any CPU 88 | {42CF6E7C-297B-4428-BB8E-66DFB05BB6DA}.Release|Any CPU.Build.0 = Release|Any CPU 89 | EndGlobalSection 90 | GlobalSection(SolutionProperties) = preSolution 91 | HideSolutionNode = FALSE 92 | EndGlobalSection 93 | GlobalSection(NestedProjects) = preSolution 94 | {F30DAE77-C764-4CE0-9E63-FF992AEEE251} = {9A24AE3F-E98C-406F-8EF6-8816224ECB2B} 95 | {96F4A4B9-9A95-4ABA-89AC-9279DD385331} = {27430A89-25BF-45CC-9F7B-A7F3432045D5} 96 | {C53DE5F2-870E-4A8B-A9E6-0C714A7FC14E} = {27430A89-25BF-45CC-9F7B-A7F3432045D5} 97 | {43216EA7-3C51-424D-8A17-E49A77B0BF19} = {1044DFB0-ADCA-41F0-AFA3-B942A47792A1} 98 | {07E0DA29-1157-4C7E-9211-45EB25E90C69} = {1044DFB0-ADCA-41F0-AFA3-B942A47792A1} 99 | {5CBAECD8-2497-48AD-85B5-9E961671E988} = {9C775FC2-BF3D-4316-9800-5B1FE338E8A0} 100 | {42CF6E7C-297B-4428-BB8E-66DFB05BB6DA} = {9A24AE3F-E98C-406F-8EF6-8816224ECB2B} 101 | EndGlobalSection 102 | GlobalSection(ExtensibilityGlobals) = postSolution 103 | SolutionGuid = {157805D8-FB4B-4043-ABB9-61C17CC21945} 104 | EndGlobalSection 105 | EndGlobal 106 | -------------------------------------------------------------------------------- /benchmarks/Ipv4AddressParsing.cs: -------------------------------------------------------------------------------- 1 | extern alias Lib_IPN2; 2 | // ReSharper disable InconsistentNaming 3 | #nullable enable 4 | using BenchmarkDotNet.Jobs; 5 | using BenchmarkDotNet.Attributes; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | using IPN2 = Lib_IPN2::System.Net.IPNetwork; 9 | 10 | namespace NetworkPrimitives.Benchmarks; 11 | 12 | public class Ipv4AddressParsing 13 | { 14 | [Benchmark] 15 | public void NetworkPrimitives() 16 | { 17 | foreach (var address in TestData.RandomIpAddresses) 18 | { 19 | _ = Ipv4Address.Parse(address); 20 | } 21 | } 22 | 23 | [Benchmark] 24 | public void DotNet() 25 | { 26 | foreach (var address in TestData.RandomIpAddresses) 27 | { 28 | _ = System.Net.IPAddress.Parse(address); 29 | } 30 | } 31 | 32 | [Benchmark] 33 | public void IpNetwork2() 34 | { 35 | foreach (var address in TestData.RandomIpAddresses) 36 | { 37 | _ = IPN2.Parse(address); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /benchmarks/Ipv4AddressRange.cs: -------------------------------------------------------------------------------- 1 | extern alias Lib_IPN2; 2 | // ReSharper disable InconsistentNaming 3 | #nullable enable 4 | 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Jobs; 7 | using NetworkPrimitives.Ipv4; 8 | 9 | using IPN2 = Lib_IPN2::System.Net.IPNetwork; 10 | 11 | namespace NetworkPrimitives.Benchmarks; 12 | 13 | public class Ipv4AddressRange 14 | { 15 | [Benchmark] 16 | public void NetworkPrimitives() 17 | { 18 | foreach (var subnetString in TestData.RandomSubnets) 19 | { 20 | var subnet = Ipv4Subnet.Parse(subnetString); 21 | foreach (var address in subnet.GetAllAddresses()) 22 | { 23 | _ = address; 24 | } 25 | } 26 | } 27 | 28 | [Benchmark] 29 | public void IpNetwork2() 30 | { 31 | foreach (var subnetString in TestData.RandomSubnets) 32 | { 33 | var subnet = IPN2.Parse(subnetString); 34 | foreach (var address in subnet.ListIPAddress()) 35 | { 36 | _ = address; 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /benchmarks/Ipv4AddressRangeLists.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Jobs; 3 | using NetworkPrimitives.Ipv4; 4 | 5 | namespace NetworkPrimitives.Benchmarks; 6 | 7 | public class Ipv4AddressRangeLists 8 | { 9 | [Benchmark] 10 | public void NetworkPrimitives() 11 | { 12 | foreach (var rangeListString in TestData.RangeLists) 13 | { 14 | var rangeList = Ipv4AddressRangeList.Parse(rangeListString); 15 | foreach (var range in rangeList) 16 | { 17 | foreach (var address in range) 18 | { 19 | _ = address; 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /benchmarks/Ipv4SubnetParsing.cs: -------------------------------------------------------------------------------- 1 | extern alias Lib_IPN2; 2 | // ReSharper disable InconsistentNaming 3 | #nullable enable 4 | 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Jobs; 7 | using NetworkPrimitives.Ipv4; 8 | 9 | using IPN2 = Lib_IPN2::System.Net.IPNetwork; 10 | 11 | namespace NetworkPrimitives.Benchmarks; 12 | 13 | public class Ipv4SubnetParsing 14 | { 15 | 16 | [Benchmark] 17 | public void NetworkPrimitives() 18 | { 19 | foreach (var address in TestData.RandomSubnets) 20 | { 21 | _ = Ipv4Subnet.Parse(address); 22 | } 23 | } 24 | 25 | [Benchmark] 26 | public void IpNetwork2() 27 | { 28 | foreach (var address in TestData.RandomSubnets) 29 | { 30 | _ = IPN2.Parse(address); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /benchmarks/NetworkPrimitives.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net48;net5.0;net6.0;netcoreapp3.0 5 | 6 | 7 | AnyCPU 8 | pdbonly 9 | true 10 | true 11 | true 12 | Release 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using BenchmarkDotNet.Columns; 5 | using BenchmarkDotNet.Configs; 6 | using BenchmarkDotNet.Diagnosers; 7 | using BenchmarkDotNet.Environments; 8 | using BenchmarkDotNet.Exporters; 9 | using BenchmarkDotNet.Exporters.Csv; 10 | using BenchmarkDotNet.Jobs; 11 | using BenchmarkDotNet.Loggers; 12 | using BenchmarkDotNet.Running; 13 | 14 | namespace NetworkPrimitives.Benchmarks 15 | { 16 | internal static class Program 17 | { 18 | internal const int LAUNCH_COUNT = 1; 19 | internal const int WARMUP_COUNT = 0; 20 | internal const int TARGET_COUNT = 1; 21 | 22 | private static void Main(string[] args) 23 | { 24 | var path = "../../../../BenchmarkResults"; 25 | path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, path)); 26 | Directory.CreateDirectory(path); 27 | Environment.CurrentDirectory = path; 28 | Console.WriteLine("==========================="); 29 | Console.WriteLine("==========================="); 30 | Console.WriteLine($"Output Path: {path}"); 31 | Console.WriteLine("==========================="); 32 | Console.WriteLine("==========================="); 33 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly)?.RunAll(new Config()); 34 | } 35 | } 36 | 37 | public class Config : ManualConfig 38 | { 39 | public Config() 40 | { 41 | this.AddColumnProvider(DefaultColumnProviders.Instance!); 42 | this.AddExporter( 43 | MarkdownExporter.GitHub! 44 | , CsvMeasurementsExporter.Default! 45 | , RPlotExporter.Default! 46 | ); 47 | this.AddDiagnoser(MemoryDiagnoser.Default!); 48 | this.AddLogger(ConsoleLogger.Default!); 49 | this.AddJob( 50 | Job.Dry! 51 | .WithWarmupCount(Program.WARMUP_COUNT)! 52 | .WithLaunchCount(Program.LAUNCH_COUNT)! 53 | .WithRuntime(CoreRuntime.Core60!)! 54 | .WithRuntime(CoreRuntime.Core50!)! 55 | .WithRuntime(CoreRuntime.Core30!)! 56 | .WithRuntime(ClrRuntime.Net48!)! 57 | ); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /docs/benchmarks.md: -------------------------------------------------------------------------------- 1 |  2 | ## Benchmarks 3 | 4 | These benchmarks compare the performance of this library against [IPNetwork2](https://github.com/lduchosal/ipnetwork), 5 | using the benchmark library [BenchmarkDotNet](https://benchmarkdotnet.org/articles/overview.html). 6 | 7 | *Note:* It is not an error when the NetworkPrimitives benchmarks show `-` in the `Gen0` and `Allocated` columns. 8 | This means that there were no allocations. (or at least, below whatever reporting thresholds BenchmarkDotNet may have...) 9 | 10 | To run your own benchmarks, use [NetworkPrimitives.Benchmarks.csproj](../benchmarks/NetworkPrimitives.Benchmarks.csproj) 11 | 12 | ### Parsing 100 IPv4 Addresses 13 | 14 | ```c# 15 | public void NetworkPrimitives() 16 | { 17 | foreach (var address in TestData.RandomIpAddresses) 18 | { 19 | _ = NetworkPrimitives.Ipv4.Ipv4Address.Parse(address); 20 | } 21 | } 22 | 23 | public void DotNet() 24 | { 25 | foreach (var address in TestData.RandomIpAddresses) 26 | { 27 | _ = System.Net.IPAddress.Parse(address); 28 | } 29 | } 30 | 31 | public void IpNetwork2() 32 | { 33 | foreach (var address in TestData.RandomIpAddresses) 34 | { 35 | _ = System.Net.IPNetwork.Parse(address); 36 | } 37 | } 38 | ``` 39 | 40 | 41 | | Method | Mean | Error | StdDev | Gen 0 | Allocated | 42 | |------------------ |-----------:|----------:|-----------:|--------:|----------:| 43 | | NetworkPrimitives | 8.450 μs | 0.2642 μs | 0.7052 μs | - | - | 44 | | DotNet | 6.634 μs | 0.1577 μs | 0.4290 μs | 0.9537 | 4,000 B | 45 | | IpNetwork2 | 207.892 μs | 4.8725 μs | 13.5017 μs | 34.6680 | 145,053 B | 46 | 47 | 48 | ### Iterating 1 subnet containing 256 IPv4 addresses 49 | 50 | ```c# 51 | public void NetworkPrimitives() 52 | { 53 | var subnet = Ipv4Subnet.Parse("10.0.0.0/24"); 54 | foreach (var address in subnet.GetAllAddresses()) 55 | { 56 | 57 | } 58 | } 59 | 60 | public void IpNetwork2() 61 | { 62 | var subnet = System.Net.IPNetwork.Parse("10.0.0.0/24"); 63 | foreach (var address in subnet.ListIPAddress()) 64 | { 65 | 66 | } 67 | } 68 | ``` 69 | 70 | | Method | Mean | Error | StdDev | Median | Gen 0 | Allocated | 71 | |------------------ |-------------:|------------:|-------------:|-------------:|---------:|----------:| 72 | | NetworkPrimitives | 970.7 ns | 28.79 ns | 75.84 ns | 932.9 ns | - | - | 73 | | IpNetwork2 | 450,398.3 ns | 5,960.03 ns | 15,908.54 ns | 447,500.9 ns | 134.7656 | 564,865 B | 74 | 75 | -------------------------------------------------------------------------------- /docs/json-converters.md: -------------------------------------------------------------------------------- 1 | [Back to readme.md](../readme.md) 2 | 3 | ## NetworkPrimitives.JsonConverters 4 | 5 | The nuget package `NetworkPrimitives.JsonConverters` contains some basic `System.Test.Json` converters for the `NetworkPrimitive` types. 6 | 7 | **[Download from nuget.org](https://www.nuget.org/packages/NetworkPrimitives.JsonConverters)** 8 | 9 | [![NetworkPrimitives.JsonConverters (Nuget)](https://img.shields.io/nuget/v/NetworkPrimitives.JsonConverters?style=for-the-badge)](https://www.nuget.org/packages/NetworkPrimitives.JsonConverters) 10 | [![NetworkPrimitives.JsonConverters (Nuget)](https://img.shields.io/nuget/dt/NetworkPrimitives.JsonConverters?style=for-the-badge)](https://www.nuget.org/packages/NetworkPrimitives.JsonConverters) 11 | 12 | ### Usage Instructions 13 | 14 | ```c# 15 | var jsonSerializerOptions = new JsonSerializerOptions(); 16 | 17 | // Option 1: Add both IPv4 and IPv6 converters 18 | jsonSerializerOptions = jsonSerializerOptions.AddNetworkPrimitivesConverters(); 19 | 20 | // Option 2: Add only IPv4 converters 21 | jsonSerializerOptions = jsonSerializerOptions.AddIpv4Converters(); 22 | 23 | // Option 3: Add only IPv6 converters 24 | jsonSerializerOptions = jsonSerializerOptions.AddIpv6Converters(); 25 | ``` -------------------------------------------------------------------------------- /docs/performance.md: -------------------------------------------------------------------------------- 1 | # Performance Techniques 2 | 3 | `NetworkPrimitives` uses the following techniques to maintain high performance: 4 | 5 | 1. Reduce allocations whenever possible. 6 | 2. When appropriate, use `struct` rather than `class` 7 | 3. Provide `ReadOnlySpan` overloads for parsing for all types. 8 | 4. For types that can be serialized to binary, provide `Span` overloads. 9 | 5. Unless absolutely necessary, use immutable types only. 10 | 6. When possible, provide allocation-free enumerators. 11 | 12 | ## Enumerators 13 | 14 | One often overlooked allocation that occurs are enumerators. A type that implements 15 | `IEnumerable` has a method `GetEnumerator()` that will instantiate an `IEnumerator`. 16 | 17 | Additionally, anytime a `struct` is converted to an `interface`, it is boxed - which 18 | creates an allocation. 19 | 20 | C# uses duck-typing for `foreach` loops. `NetworkPrimitives` leverages this to 21 | allow you to iterate over collections without allocating an enumerator. 22 | 23 | For example, this code does not allocate an enumerator: 24 | 25 | ```c# 26 | Ipv4AddressRange range = subnet.GetAllAddresses(); 27 | foreach (var address in range) 28 | { 29 | Console.WriteLine(address.ToString()); 30 | } 31 | ``` 32 | 33 | This is because the `Ipv4AddressRange` type has a `GetEnumerator` method that 34 | returns an instance of the `Ipv4AddressEnumerator` `ref struct` - no allocation. 35 | The `Ipv4AddressEnumerator` type has a `MoveNext()` method and a `Current` 36 | property that returns an `Ipv4Address` `struct` - no allocation. 37 | 38 | 39 | If you prefer to have a `class` enumerator, you can do one of two things. 40 | 41 | First option, is to call the `ToEnumerable()` method on an `Ipv4AddressRange`. 42 | Note, that this will cause one allocation. 43 | 44 | ```c# 45 | Ipv4AddressRange range = subnet.GetAllAddresses(); 46 | foreach (var address in range.ToEnumerable()) 47 | { 48 | Console.WriteLine(address.ToString()); 49 | } 50 | ``` 51 | 52 | Your other option is to simply pass the `Ipv4AddressRange` to a method that 53 | needs an `IEnumerable`. 54 | 55 | 56 | ```c# 57 | 58 | private static void Main() 59 | { 60 | subnet = Ipv4Subnet.Parse("10.0.0.0/24"); 61 | Ipv4AddressRange range = subnet.GetAllAddresses(); 62 | WriteRange(range); 63 | } 64 | 65 | private static void WriteRange(IEnumerable range) 66 | { 67 | foreach (var address in range) 68 | { 69 | Console.WriteLine(address.ToString()); 70 | } 71 | } 72 | ``` 73 | 74 | 75 | 76 | ## Framework Versions 77 | 78 | ### .NET Core 3.0+ or .NET Standard 2.1 79 | 80 | Using either .NET Core 3.0 (or higher) or .NET Standard 2.1 will give the 81 | best performance when using the `NetworkPrimitives` library. 82 | 83 | ### .NET Standard 2.0 84 | 85 | If you're stuck on .NET Framework, a .NET Core version prior to 3.0, 86 | or some other version of .NET, that doesn't mean that `NetworkPrimitives` 87 | is not a high performance library. It just will not perform as well as 88 | on .NET Core 3.0 or higher. 89 | 90 | Some methods are not available in .NET Standard, which prevents 91 | `NetworkPrimitives` from gaining the full performance benefits. 92 | 93 | For example: 94 | 95 | - Constructor `System.String(ReadOnlySpan)` ([docs](https://docs.microsoft.com/en-us/dotnet/api/system.string.-ctor?#System_String__ctor_System_ReadOnlySpan_System_Char__)) 96 | without this constructor, we must instantiate a `char[]` to create the `string` 97 | - Constructor `System.Net.IPAddress(ReadOnlySpan)` ([docs](https://docs.microsoft.com/en-us/dotnet/api/system.net.ipaddress.-ctor?#System_Net_IPAddress__ctor_System_ReadOnlySpan_System_Byte__)) 98 | without this constructor, we must instantiate a `byte[]` to create the `IPAddress` 99 | - Method `System.Net.IPAddress.TryWriteBytes` ([docs](https://docs.microsoft.com/en-us/dotnet/api/system.net.ipaddress.trywritebytes)) 100 | without this method, we must call `IPAddress.GetAddressBytes()`, which instantiates a `byte[]`. -------------------------------------------------------------------------------- /docs/range-lists.md: -------------------------------------------------------------------------------- 1 | [Back to readme.md](../readme.md) 2 | 3 | ## Address Ranges / Lists 4 | 5 | **Parse/enumerate a single address range:** 6 | 7 | ```c# 8 | var range = Ipv4AddressRange.Parse("10.0.0.0-15"); 9 | foreach(var address in range) 10 | { 11 | Console.WriteLine(address); 12 | } 13 | ``` 14 | 15 | **Parse a range list** 16 | 17 | ```c# 18 | var rangeText = @" 19 | 10.0.0.0-10 20 | 10.0.1.0/29 21 | 10.1.0.40 22 | "; 23 | var rangeList = Ipv4AddressRangeList.Parse(rangeText); 24 | ``` 25 | 26 | **Enumerate a range list** 27 | 28 | Option 1: Nested enumeration 29 | 30 | ```c# 31 | foreach (var range in rangeList) 32 | { 33 | Console.WriteLine(range); 34 | foreach (var address in range) 35 | { 36 | Console.WriteLine(address); 37 | } 38 | } 39 | ``` 40 | 41 | Option 2: Enumerate all addresses 42 | 43 | ```c# 44 | foreach (var address in rangeList.GetAllAddresses()) 45 | { 46 | Console.WriteLine(address); 47 | } 48 | ``` -------------------------------------------------------------------------------- /docs/subnetting.md: -------------------------------------------------------------------------------- 1 | [Back to readme.md](../readme.md) 2 | 3 | ## Get basic subnet info 4 | 5 | ```c# 6 | var subnet = Ipv4Subnet.Parse("10.0.0.0/29"); 7 | Console.WriteLine($" Network Address: {subnet.NetworkAddress}"); // 10.0.0.0 8 | Console.WriteLine($" First Usable: {subnet.FirstUsable}"); // 10.0.0.1 9 | Console.WriteLine($" Last Usable: {subnet.LastUsable}"); // 10.0.0.6 10 | Console.WriteLine($"Broadcast Address: {subnet.BroadcastAddress}"); // 10.0.0.7 11 | Console.WriteLine($" Total Hosts: {subnet.TotalHosts}"); // 8 12 | Console.WriteLine($" Usable Hosts: {subnet.UsableHosts}"); // 6 13 | foreach (var address in subnet.GetUsableAddresses()) 14 | { 15 | Console.WriteLine(address); 16 | } 17 | ``` 18 | 19 | ## Subnet Operations 20 | 21 | 22 | **Attempt to split a subnet in half:** 23 | 24 | ```c# 25 | var slash24 = Ipv4Subnet.Parse("10.0.0.0/24"); 26 | Console.WriteLine($"Splitting subnet {slash24}"); // 10.0.0.0/24 27 | var success = slash24.TrySplit(out var lowHalf, out var highHalf); 28 | Console.WriteLine($"Success: {success}"); // true 29 | Console.WriteLine($"Low subnet: {lowHalf}"); // 10.0.0.0/25 30 | Console.WriteLine($"High subnet: {highHalf}"); // 10.0.0.128/25 31 | ``` 32 | 33 | **Supernet two or more subnets:** 34 | 35 | ```c# 36 | var subnetA = Ipv4Subnet.Parse("10.0.0.0/24"); 37 | var subnetB = Ipv4Subnet.Parse("10.0.1.0/24"); 38 | var subnetC = Ipv4Subnet.Parse("10.0.3.0/24"); 39 | var subnetD = Ipv4Subnet.Parse("10.0.255.0/24"); 40 | var supernet = Ipv4Subnet.GetContainingSupernet(subnetA, subnetB, subnetC, subnetD); 41 | Console.WriteLine($"Supernet: {supernet}"); // 10.0.0.0/16 42 | ``` 43 | 44 | **Contains:** 45 | 46 | ```c# 47 | var subnet = Ipv4Subnet.Parse("10.0.0.0/24"); 48 | 49 | Console.WriteLine(subnet.Contains(Ipv4Address.Parse("10.50.0.0"))); // False 50 | Console.WriteLine(subnet.Contains(Ipv4Address.Parse("10.0.0.50"))); // True 51 | Console.WriteLine(subnet.Contains(Ipv4Subnet.Parse("10.0.0.0/24"))); // True 52 | Console.WriteLine(subnet.Contains(Ipv4Subnet.Parse("10.0.0.128/26"))); // True 53 | Console.WriteLine(subnet.Contains(Ipv4Subnet.Parse("8.0.0.0/8"))); // False 54 | ``` 55 | 56 | ## Address Listing 57 | 58 | **All Addresses** 59 | 60 | Includes network/broadcast addresses 61 | 62 | ```c# 63 | var subnet = Ipv4Subnet.Parse("10.0.0.0/24"); 64 | var addresses = subnet.GetAllAddresses(); 65 | foreach(var address in addresses) 66 | { 67 | } 68 | ``` 69 | 70 | **Usable Addresses** 71 | 72 | Does not include network/broadcast addresses 73 | 74 | (_Note:_ Respects [RFC 3021](https://datatracker.ietf.org/doc/html/rfc3021)) 75 | 76 | ```c# 77 | var subnet = Ipv4Subnet.Parse("10.0.0.0/24"); 78 | var addresses = subnet.GetUsableAddresses(); 79 | foreach(var address in addresses) 80 | { 81 | } 82 | ``` -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "5.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # NetworkPrimitives 2 | 3 | Lightweight library for working with various networking objects 4 | 5 | ### Basic usage 6 | 7 | ```c# 8 | var subnet = Ipv4Subnet.Parse("10.0.0.0/29"); 9 | Console.WriteLine($" Network Address: {subnet.NetworkAddress}"); // 10.0.0.0 10 | Console.WriteLine($" First Usable: {subnet.FirstUsable}"); // 10.0.0.1 11 | Console.WriteLine($" Last Usable: {subnet.LastUsable}"); // 10.0.0.6 12 | Console.WriteLine($"Broadcast Address: {subnet.BroadcastAddress}"); // 10.0.0.7 13 | Console.WriteLine($" Total Hosts: {subnet.TotalHosts}"); // 8 14 | Console.WriteLine($" Usable Hosts: {subnet.UsableHosts}"); // 6 15 | foreach (var address in subnet.GetUsableAddresses()) 16 | { 17 | Console.WriteLine(address); 18 | } 19 | ``` 20 | 21 | ### Download 22 | 23 | The core `NetworkPrimitives` package contains the types themselves. 24 | 25 | [![NetworkPrimitives (Nuget)](https://img.shields.io/nuget/v/NetworkPrimitives?style=for-the-badge)](https://www.nuget.org/packages/NetworkPrimitives) 26 | [![NetworkPrimitives (Nuget)](https://img.shields.io/nuget/dt/NetworkPrimitives?style=for-the-badge)](https://www.nuget.org/packages/NetworkPrimitives) 27 | 28 | The package `NetworkPrimitives.JsonConverters` contains converters for `System.Text.Json` 29 | 30 | [![NetworkPrimitives.JsonConverters (Nuget)](https://img.shields.io/nuget/v/NetworkPrimitives.JsonConverters?style=for-the-badge)](https://www.nuget.org/packages/NetworkPrimitives.JsonConverters) 31 | [![NetworkPrimitives.JsonConverters (Nuget)](https://img.shields.io/nuget/dt/NetworkPrimitives.JsonConverters?style=for-the-badge)](https://www.nuget.org/packages/NetworkPrimitives.JsonConverters) 32 | 33 | 34 | ### Performance 35 | 36 | Overall goals are: 37 | 38 | 1. Fast parsing times 39 | 2. Low (preferably zero) allocations 40 | 3. Overall efficiency 41 | 42 | ### Detailed usage 43 | 44 | - [Benchmarks](docs/benchmarks.md) 45 | - [Subnetting](docs/subnetting.md) 46 | - [Address Ranges / Lists](docs/range-lists.md) 47 | - [Performance](docs/performance.md) 48 | - [Json Converters](docs/json-converters.md) -------------------------------------------------------------------------------- /samples/Demo/Demo.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/Demo/Demo.Framework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net48 6 | enable 7 | 9 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/Demo/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | obj\$(MSBuildProjectName)\ 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/Demo/Program.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using NetworkPrimitives.Ipv4; 8 | 9 | 10 | namespace Demo 11 | { 12 | internal static class Program 13 | { 14 | private static void Main() 15 | { 16 | var subnet = Ipv4Subnet.Parse("10.0.0.0/28"); 17 | Console.WriteLine("Option 1: ref struct"); 18 | // Option 1: ref struct 19 | foreach (var address in subnet.GetAllAddresses()) 20 | { 21 | Console.WriteLine($" {address.ToString()}"); 22 | } 23 | Console.WriteLine(); 24 | Console.WriteLine("Option 2: ToEnumerable() method"); 25 | foreach (var address in subnet.GetUsableAddresses().ToEnumerable()) 26 | { 27 | Console.WriteLine($" {address.ToString()}"); 28 | } 29 | Console.WriteLine(); 30 | Console.WriteLine("Option 3: Explicit implementation of IEnumerable"); 31 | WriteRange(subnet.GetUsableAddresses()); 32 | } 33 | 34 | private static void WriteRange(IEnumerable range) 35 | { 36 | foreach (var address in range) 37 | { 38 | Console.WriteLine($" {address.ToString()}"); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /samples/Demo/TestData.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace Demo 6 | { 7 | public static class TestData 8 | { 9 | public static readonly string[] RandomIpAddresses = @" 10 | 110.16.23.222 11 | 68.231.174.107 12 | 3.82.172.189 13 | 243.32.132.113 14 | 225.12.72.166 15 | 13.127.252.250 16 | 147.161.211.134 17 | 82.156.232.8 18 | 58.60.52.142 19 | 183.226.206.20 20 | 63.132.185.33 21 | 143.25.217.18 22 | 71.71.26.123 23 | 247.247.163.201 24 | 67.88.214.99 25 | 78.151.142.0 26 | 134.80.69.85 27 | 155.11.248.103 28 | 236.6.93.134 29 | 229.0.191.199 30 | 124.10.48.201 31 | 91.184.110.3 32 | 182.86.82.16 33 | 22.139.1.247 34 | 109.57.80.80 35 | 165.228.84.20 36 | 145.124.181.137 37 | 85.157.141.88 38 | 189.136.64.168 39 | 158.226.81.64 40 | 66.215.77.47 41 | 213.73.79.44 42 | 156.212.10.4 43 | 118.41.163.76 44 | 238.129.60.191 45 | 189.75.132.142 46 | 94.184.158.100 47 | 100.169.150.44 48 | 201.169.186.149 49 | 70.91.111.41 50 | 157.201.209.234 51 | 217.213.64.28 52 | 35.128.131.116 53 | 196.220.169.212 54 | 0.201.76.200 55 | 241.203.244.189 56 | 243.152.69.175 57 | 115.174.109.202 58 | 77.142.18.206 59 | 69.119.230.16 60 | 19.130.197.235 61 | 11.255.204.97 62 | 247.244.180.141 63 | 179.179.224.34 64 | 222.208.30.31 65 | 91.249.41.199 66 | 170.156.185.230 67 | 28.208.57.185 68 | 101.29.242.129 69 | 206.15.21.85 70 | 123.129.145.146 71 | 228.96.110.131 72 | 223.29.148.68 73 | 85.107.12.36 74 | 201.231.93.112 75 | 23.180.49.15 76 | 253.120.213.44 77 | 35.52.211.236 78 | 13.238.58.205 79 | 6.245.123.129 80 | 176.39.70.178 81 | 111.128.165.209 82 | 28.210.69.156 83 | 149.56.223.117 84 | 149.59.117.225 85 | 230.193.219.196 86 | 198.219.76.26 87 | 229.115.52.222 88 | 69.239.133.185 89 | 148.122.122.200 90 | 226.59.238.133 91 | 85.25.206.138 92 | 178.41.253.173 93 | 237.30.216.96 94 | 105.89.205.123 95 | 157.76.51.182 96 | 88.250.141.27 97 | 113.197.178.251 98 | 203.13.101.227 99 | 26.111.49.164 100 | 84.237.14.222 101 | 134.209.92.181 102 | 116.198.208.91 103 | 237.170.209.135 104 | 205.197.110.251 105 | 10.85.28.143 106 | 80.221.147.135 107 | 198.8.241.68 108 | 215.87.21.24 109 | 68.127.97.81 110 | ".Split(new[]{Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries); 111 | 112 | } 113 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net6.0;net5.0;netstandard2.0 6 | 1.1.0 7 | network; networking; subnet; ip-address; ipaddress; ip address; mac address; networking.primitives; network.primitives; primitives; 8 | Lightweight package for working with networking types such as IPv4 addresses, IPv6 addresses, ranges, and subnets. 9 | True 10 | $(WarningsNotAsErrors);CS1591 11 | true 12 | 13 | 14 | 15 | 16 | true 17 | true 18 | true 19 | snupkg 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | 34 | 35 | 45 | -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4AddressJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4AddressJsonConverter : JsonConverter 11 | { 12 | private Ipv4AddressJsonConverter() { } 13 | public static readonly Ipv4AddressJsonConverter Instance = new (); 14 | public override Ipv4Address Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4Address.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4Address value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4AddressRangeJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4AddressRangeJsonConverter : JsonConverter 11 | { 12 | private Ipv4AddressRangeJsonConverter() { } 13 | public static readonly Ipv4AddressRangeJsonConverter Instance = new (); 14 | public override Ipv4AddressRange Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4AddressRange.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4AddressRange value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4AddressRangeListJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4AddressRangeListJsonConverter : JsonConverter 11 | { 12 | private Ipv4AddressRangeListJsonConverter() { } 13 | public static readonly Ipv4AddressRangeListJsonConverter Instance = new (); 14 | public override Ipv4AddressRangeList Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4AddressRangeList.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4AddressRangeList value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4CidrJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4CidrJsonConverter : JsonConverter 11 | { 12 | private Ipv4CidrJsonConverter() { } 13 | public static readonly Ipv4CidrJsonConverter Instance = new (); 14 | public override Ipv4Cidr Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4Cidr.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4Cidr value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4NetworkMatchJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4NetworkMatchJsonConverter : JsonConverter 11 | { 12 | private Ipv4NetworkMatchJsonConverter() { } 13 | public static readonly Ipv4NetworkMatchJsonConverter Instance = new (); 14 | public override Ipv4NetworkMatch Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4NetworkMatch.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4NetworkMatch value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4SubnetJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4SubnetJsonConverter : JsonConverter 11 | { 12 | private Ipv4SubnetJsonConverter() { } 13 | public static readonly Ipv4SubnetJsonConverter Instance = new (); 14 | public override Ipv4Subnet Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4Subnet.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4Subnet value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4SubnetMaskJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4SubnetMaskJsonConverter : JsonConverter 11 | { 12 | private Ipv4SubnetMaskJsonConverter() { } 13 | public static readonly Ipv4SubnetMaskJsonConverter Instance = new (); 14 | public override Ipv4SubnetMask Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4SubnetMask.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4SubnetMask value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv4/Ipv4WildcardMaskJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv4; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv4 9 | { 10 | public class Ipv4WildcardMaskJsonConverter : JsonConverter 11 | { 12 | private Ipv4WildcardMaskJsonConverter() { } 13 | public static readonly Ipv4WildcardMaskJsonConverter Instance = new (); 14 | public override Ipv4WildcardMask Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv4WildcardMask.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv4WildcardMask value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv6/Ipv6AddressJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv6; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv6 9 | { 10 | public class Ipv6AddressJsonConverter : JsonConverter 11 | { 12 | private Ipv6AddressJsonConverter() { } 13 | public static readonly Ipv6AddressJsonConverter Instance = new (); 14 | public override Ipv6Address Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv6Address.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv6Address value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv6/Ipv6CidrJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv6; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv6 9 | { 10 | public class Ipv6CidrJsonConverter : JsonConverter 11 | { 12 | private Ipv6CidrJsonConverter() { } 13 | public static readonly Ipv6CidrJsonConverter Instance = new (); 14 | public override Ipv6Cidr Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv6Cidr.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv6Cidr value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv6/Ipv6SubnetJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv6; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv6 9 | { 10 | public class Ipv6SubnetJsonConverter : JsonConverter 11 | { 12 | private Ipv6SubnetJsonConverter() { } 13 | public static readonly Ipv6SubnetJsonConverter Instance = new (); 14 | public override Ipv6Subnet Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv6Subnet.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv6Subnet value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/Ipv6/Ipv6SubnetMaskJsonConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using NetworkPrimitives.Ipv6; 7 | 8 | namespace NetworkPrimitives.JsonConverters.Ipv6 9 | { 10 | public class Ipv6SubnetMaskJsonConverter : JsonConverter 11 | { 12 | private Ipv6SubnetMaskJsonConverter() { } 13 | public static readonly Ipv6SubnetMaskJsonConverter Instance = new (); 14 | public override Ipv6SubnetMask Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options 18 | ) => Ipv6SubnetMask.Parse(reader.GetString()); 19 | 20 | public override void Write( 21 | Utf8JsonWriter writer, 22 | Ipv6SubnetMask value, 23 | JsonSerializerOptions options 24 | ) => writer.WriteStringValue(value.ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/JsonSerializerExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Text.Json; 4 | using NetworkPrimitives.JsonConverters.Ipv4; 5 | using NetworkPrimitives.JsonConverters.Ipv6; 6 | 7 | namespace NetworkPrimitives.JsonConverters 8 | { 9 | public static class JsonSerializerExtensions 10 | { 11 | public static JsonSerializerOptions AddNetworkPrimitivesConverters( 12 | this JsonSerializerOptions options 13 | ) 14 | { 15 | return options 16 | .AddIpv4Converters() 17 | .AddIpv6Converters(); 18 | } 19 | 20 | 21 | public static JsonSerializerOptions AddIpv4Converters( 22 | this JsonSerializerOptions options 23 | ) 24 | { 25 | options.Converters.Add(Ipv4AddressJsonConverter.Instance); 26 | options.Converters.Add(Ipv4AddressRangeJsonConverter.Instance); 27 | options.Converters.Add(Ipv4AddressRangeListJsonConverter.Instance); 28 | options.Converters.Add(Ipv4CidrJsonConverter.Instance); 29 | options.Converters.Add(Ipv4NetworkMatchJsonConverter.Instance); 30 | options.Converters.Add(Ipv4SubnetJsonConverter.Instance); 31 | options.Converters.Add(Ipv4SubnetMaskJsonConverter.Instance); 32 | options.Converters.Add(Ipv4WildcardMaskJsonConverter.Instance); 33 | return options; 34 | } 35 | 36 | public static JsonSerializerOptions AddIpv6Converters( 37 | this JsonSerializerOptions options 38 | ) 39 | { 40 | options.Converters.Add(Ipv6AddressJsonConverter.Instance); 41 | options.Converters.Add(Ipv6CidrJsonConverter.Instance); 42 | options.Converters.Add(Ipv6SubnetJsonConverter.Instance); 43 | options.Converters.Add(Ipv6SubnetMaskJsonConverter.Instance); 44 | return options; 45 | } 46 | 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives.JsonConverters/NetworkPrimitives.JsonConverters.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;netstandard2.0 5 | enable 6 | enable 7 | 8 | 9 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/NetworkPrimitives/BitOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | #if !NETCOREAPP3_0_OR_GREATER 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace System.Numerics 7 | { 8 | internal static class BitOperations 9 | { 10 | public static int PopCount(uint value) 11 | { 12 | const uint c1 = 0x_55555555u; 13 | const uint c2 = 0x_33333333u; 14 | const uint c3 = 0x_0F0F0F0Fu; 15 | const uint c4 = 0x_01010101u; 16 | 17 | value -= (value >> 1) & c1; 18 | value = (value & c2) + ((value >> 2) & c2); 19 | value = (((value + (value >> 4)) & c3) * c4) >> 24; 20 | 21 | return (int)value; 22 | } 23 | 24 | } 25 | } 26 | #endif 27 | 28 | namespace NetworkPrimitives 29 | { 30 | internal static class BitOperationsEx 31 | { 32 | public static uint RoundUpToPowerOf2(uint value) 33 | { 34 | #if NET6_0_OR_GREATER 35 | return System.Numerics.BitOperations.RoundUpToPowerOf2(value); 36 | #else 37 | --value; 38 | value |= value >> 1; 39 | value |= value >> 2; 40 | value |= value >> 4; 41 | value |= value >> 8; 42 | value |= value >> 16; 43 | return value + 1; 44 | #endif 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/BuildInformationAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives 6 | { 7 | /// 8 | /// Provides build information 9 | /// 10 | [AttributeUsage(AttributeTargets.Assembly)] 11 | public class BuildInformationAttribute : Attribute 12 | { 13 | /// 14 | /// The git commit hash 15 | /// 16 | public string? CommitHash { get; } 17 | /// 18 | /// The timestamp when this build was built 19 | /// 20 | public string? BuildTimestamp { get; } 21 | /// 22 | /// The unique build number. 23 | /// 24 | public string? BuildNumber { get; } 25 | 26 | /// 27 | /// Branch name this build was created from 28 | /// 29 | public string? BranchName { get; } 30 | 31 | /// 32 | /// Create an instance of 33 | /// 34 | /// The git commit hash 35 | /// The timestamp when this build was built 36 | /// The unique build number. 37 | /// Branch name this build was created from 38 | public BuildInformationAttribute( 39 | string? commitHash = null, 40 | string? buildTimestamp = null, 41 | string? buildNumber = null, 42 | string? branchName = null 43 | ) 44 | { 45 | this.CommitHash = commitHash; 46 | this.BuildTimestamp = buildTimestamp; 47 | this.BuildNumber = buildNumber; 48 | this.BranchName = branchName; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/ExcludeFromCodeCoverageAttribute.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives 6 | { 7 | [AttributeUsage( 8 | validOn: AttributeTargets.Assembly 9 | | AttributeTargets.Class 10 | | AttributeTargets.Constructor 11 | | AttributeTargets.Event 12 | | AttributeTargets.Method 13 | | AttributeTargets.Property 14 | | AttributeTargets.Struct, 15 | Inherited=false 16 | )] 17 | [ExcludeFromCodeCoverage("Internal")] 18 | internal sealed class ExcludeFromCodeCoverageAttribute : Attribute 19 | { 20 | public ExcludeFromCodeCoverageAttribute(string? reason = null) => Reason = reason; 21 | public string? Reason { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/CharExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace NetworkPrimitives 2 | { 3 | [ExcludeFromCodeCoverage("Internal")] 4 | internal static class CharExtensions 5 | { 6 | public static bool IsHex(this char ch) => ch switch 7 | { 8 | >= '0' and <= '9' => true, 9 | >= 'a' and <= 'f' => true, 10 | >= 'A' and <= 'F' => true, 11 | _ => false, 12 | }; 13 | } 14 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/IpAddressExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Net; 5 | 6 | namespace NetworkPrimitives 7 | { 8 | [ExcludeFromCodeCoverage("Internal")] 9 | internal static class IpAddressExtensions 10 | { 11 | #if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER) 12 | public static bool TryWriteBytes(this IPAddress ipAddress, Span bytes, out int charsWritten) 13 | { 14 | charsWritten = default; 15 | var byteArray = ipAddress.GetAddressBytes(); // TODO: Is this the right way to do it? 16 | if (bytes.Length < byteArray.Length) 17 | return false; 18 | for (var i = 0; i < byteArray.Length; ++i) 19 | bytes[i] = byteArray[i]; 20 | charsWritten = byteArray.Length; 21 | return true; 22 | } 23 | #endif 24 | } 25 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/NumericExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers.Binary; 5 | using System.Numerics; 6 | 7 | namespace NetworkPrimitives 8 | { 9 | internal static class NumericExtensions 10 | { 11 | [ExcludeFromCodeCoverage("Internal")] 12 | public static uint PopCount(this uint v) 13 | { 14 | // Intentionally not using BitOperations.PopCount as its not CLS compliant. 15 | // https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel 16 | v = v - ((v >> 1) & 0x55555555); 17 | v = (v & 0x33333333) + ((v >> 2) & 0x33333333); 18 | return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; 19 | } 20 | 21 | [ExcludeFromCodeCoverage("Internal")] 22 | public static byte[] ToBytesBigEndian(this uint value) 23 | { 24 | var bytes = new byte[4]; 25 | value.TryWriteBigEndian(bytes, out _); 26 | return bytes; 27 | } 28 | 29 | [ExcludeFromCodeCoverage("Internal")] 30 | public static bool TryWriteBigEndian(this uint value, Span span, out int bytesWritten) 31 | { 32 | bytesWritten = default; 33 | return value.TryWriteBigEndian(ref span, ref bytesWritten); 34 | } 35 | 36 | [ExcludeFromCodeCoverage("Internal")] 37 | public static bool TryWriteBigEndian(this uint value, ref Span span, ref int bytesWritten) 38 | { 39 | if (!BinaryPrimitives.TryWriteUInt32BigEndian(span, value)) 40 | return false; 41 | span = span[4..]; 42 | bytesWritten += 4; 43 | return true; 44 | } 45 | 46 | [ExcludeFromCodeCoverage("Internal")] 47 | public static uint SwapEndianIfLittleEndian(this uint value) 48 | => BitConverter.IsLittleEndian ? value.SwapEndian() : value; 49 | 50 | public static uint SwapEndian(this uint value) 51 | { 52 | Span span = stackalloc byte[4]; 53 | BinaryPrimitives.WriteUInt32BigEndian(span, value); 54 | return BinaryPrimitives.ReadUInt32LittleEndian(span); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/QueueExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace NetworkPrimitives; 6 | 7 | internal static class QueueExtensions 8 | { 9 | #if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_0_OR_GREATER) 10 | public static bool TryDequeue( 11 | this Queue queue, 12 | [NotNullWhen(true)] out T? item 13 | ) where T : notnull 14 | { 15 | item = default; 16 | if (queue.Count == 0) 17 | return false; 18 | try 19 | { 20 | item = queue.Dequeue(); 21 | } 22 | catch 23 | { 24 | return false; 25 | } 26 | return item is not null; 27 | } 28 | #endif 29 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/SliceExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | namespace NetworkPrimitives 7 | { 8 | [ExcludeFromCodeCoverage("Internal")] 9 | internal static class SliceExtensions 10 | { 11 | [return: NotNullIfNotNull("defaultValue")] 12 | public static TItem? SliceOrDefault( 13 | ref this TDerived span, 14 | TItem? defaultValue = default 15 | ) where TDerived : struct, ISlice 16 | { 17 | if (span.Length == 0) 18 | return defaultValue; 19 | var value = span[0]; 20 | span = span[1..]; 21 | return value; 22 | } 23 | 24 | public static bool TrySliceFirst(ref this TDerived span, ref int charsRead, TItem expected) 25 | where TDerived : struct, ISlice 26 | where TItem : struct 27 | => span.TrySliceFirst(ref charsRead, expected, out _); 28 | 29 | public static bool TrySliceFirst(ref this TDerived span, ref int charsRead, TItem expected, out TItem value) 30 | where TDerived : struct, ISlice 31 | where TItem : struct 32 | { 33 | if (!span.TrySliceFirst(expected, out value)) 34 | return false; 35 | ++charsRead; 36 | return true; 37 | } 38 | 39 | public static bool TrySliceFirst(ref this TDerived span, ref int charsRead, out TItem value) 40 | where TDerived : struct, ISlice 41 | where TItem : struct 42 | { 43 | if (!span.TrySliceFirst(out value)) 44 | return false; 45 | ++charsRead; 46 | return true; 47 | } 48 | 49 | public static bool TrySliceFirst(ref this TDerived span, TItem expected, out TItem value) 50 | where TDerived : struct, ISlice 51 | where TItem : struct 52 | { 53 | value = default; 54 | if (span.Length == 0) 55 | return false; 56 | if (!EqualityComparer.Default.Equals(span[0], expected)) 57 | return false; 58 | value = span[0]; 59 | span = span[1..]; 60 | return true; 61 | } 62 | 63 | public static bool TrySliceFirst(ref this TDerived span, out TItem value) 64 | where TDerived : struct, ISlice 65 | where TItem : struct 66 | { 67 | value = default; 68 | if (span.Length == 0) 69 | return false; 70 | value = span[0]; 71 | span = span[1..]; 72 | return true; 73 | } 74 | 75 | public static bool TrySliceLast(ref this TDerived span, out TItem value) 76 | where TDerived : struct, ISlice 77 | where TItem : struct 78 | { 79 | value = default; 80 | if (span.Length == 0) 81 | return false; 82 | value = span[^1]; 83 | span = span[..^1]; 84 | return true; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/SpanExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers.Binary; 5 | using System.Collections.Generic; 6 | 7 | namespace NetworkPrimitives 8 | { 9 | [ExcludeFromCodeCoverage("Internal")] 10 | internal static class SpanExtensions 11 | { 12 | 13 | public static bool EqualToArray(this Span span, T[] array, IEqualityComparer? comparer = null) 14 | { 15 | comparer ??= EqualityComparer.Default; 16 | if (span.Length != array.Length) return false; 17 | for (var i = 0; i < span.Length; ++i) 18 | { 19 | if (comparer.Equals(span[i], array[i]) is false) 20 | return false; 21 | } 22 | return true; 23 | } 24 | 25 | public static string CreateString(this ReadOnlySpan span) 26 | { 27 | #if NETSTANDARD2_1_OR_GREATER 28 | return new (span); 29 | #else 30 | return new(GetArray(span.ToArray())); 31 | 32 | 33 | static char[] GetArray(char[]? arr) => arr ?? Array.Empty(); 34 | #endif 35 | } 36 | public static string CreateString(this Span span) 37 | { 38 | #if NETSTANDARD2_1_OR_GREATER 39 | return new (span); 40 | #else 41 | return new(GetArray(span.ToArray())); 42 | 43 | 44 | static char[] GetArray(char[]? arr) => arr ?? Array.Empty(); 45 | #endif 46 | } 47 | 48 | public static bool TryReadUInt32BigEndian(ref this ReadOnlySpan span, out uint result) 49 | { 50 | var bytesRead = 0; 51 | return span.TryReadUInt32BigEndian(ref bytesRead, out result); 52 | } 53 | 54 | public static bool TryReadUInt32BigEndian(ref this ReadOnlySpan span, ref int bytesRead, out uint result) 55 | { 56 | if (!BinaryPrimitives.TryReadUInt32BigEndian(span, out result)) return false; 57 | span = span[4..]; 58 | bytesRead += 4; 59 | return true; 60 | } 61 | 62 | public static bool TryWrite(ref this Span span, T item, ref int charsWritten) 63 | { 64 | if (span.Length == 0) 65 | return false; 66 | span[0] = item; 67 | span = span[1..]; 68 | ++charsWritten; 69 | return true; 70 | } 71 | 72 | public static bool TryWrite(ref this Span span, T item) 73 | { 74 | var written = 0; 75 | return span.TryWrite(item, ref written); 76 | } 77 | 78 | public static bool TryConsumeWhiteSpace(ref this ReadOnlySpan span, ref int charsRead) 79 | { 80 | var atLeastOne = false; 81 | while (!span.IsEmpty && char.IsWhiteSpace(span[0])) 82 | { 83 | span = span[1..]; 84 | ++charsRead; 85 | atLeastOne = true; 86 | } 87 | return atLeastOne; 88 | } 89 | 90 | public static string GetString(this ReadOnlySpan span) 91 | { 92 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER 93 | return new string(span); 94 | #else 95 | return new string(span.ToArray() ?? Array.Empty()); // TODO: Is this the right way to do it? 96 | #endif 97 | } 98 | 99 | public static void SplitKeepSecond(ref this ReadOnlySpan first, int length, out ReadOnlySpan second) 100 | { 101 | second = first[..length]; 102 | first = first[length..]; 103 | } 104 | 105 | public static bool TrySliceFirst(ref this ReadOnlySpan span, out char value) 106 | { 107 | value = default; 108 | if (span.IsEmpty) 109 | return false; 110 | value = span[0]; 111 | if (span.Length == 1) 112 | { 113 | // Workaround because for some reason on .NET Framework, it turns 1.. 114 | // into Slice(1, -1) when resulting length will be zero? 115 | span = string.Empty.AsSpan(); 116 | return true; 117 | } 118 | span = span[1..]; 119 | return true; 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/StackExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace NetworkPrimitives; 5 | 6 | internal static class StackExtensions 7 | { 8 | #if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_0_OR_GREATER) 9 | public static bool TryPop( 10 | this Stack stack, 11 | [NotNullWhen(true)] out T? item 12 | ) where T : notnull 13 | { 14 | item = default; 15 | if (stack.Count == 0) 16 | return false; 17 | try 18 | { 19 | item = stack.Pop(); 20 | } 21 | catch 22 | { 23 | return false; 24 | } 25 | return item is not null; 26 | } 27 | #endif 28 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives 6 | { 7 | internal static class StringExtensions 8 | { 9 | public static ReadOnlySpan GetSpan(this string? text) => (text ?? string.Empty).AsSpan(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/INetworkPrimitive.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives 6 | { 7 | internal interface INetworkPrimitive : IEquatable, ITryFormat 8 | where TDerived : INetworkPrimitive 9 | { 10 | #if STATIC_ABSTRACT 11 | public static abstract TDerived Parse(string? text); 12 | public static abstract TDerived Parse(ReadOnlySpan text); 13 | public static abstract bool TryParse(string? text, out TDerived result); 14 | public static abstract bool TryParse(string? text, out int charsRead, out TDerived result); 15 | public static abstract bool TryParse(ReadOnlySpan text, out TDerived result); 16 | public static abstract bool TryParse(ReadOnlySpan text, out int charsRead, out TDerived result); 17 | #endif 18 | } 19 | internal interface IBinaryNetworkPrimitive : INetworkPrimitive 20 | where TDerived : IBinaryNetworkPrimitive 21 | { 22 | public bool TryWriteBytes(Span destination, out int bytesWritten); 23 | public byte[] GetBytes(); 24 | } 25 | 26 | internal interface IFormattableNetworkPrimitive : INetworkPrimitive, ITryFormattable 27 | where TDerived : IFormattableNetworkPrimitive 28 | { 29 | #if STATIC_ABSTRACT 30 | public static abstract TDerived ParseExact(string? text, string format); 31 | public static abstract TDerived ParseExact(ReadOnlySpan text, string format); 32 | public static abstract bool TryParseExact(string? text, string format, out TDerived result); 33 | public static abstract bool TryParseExact(string? text, string format, out int charsRead, out TDerived result); 34 | public static abstract bool TryParseExact(ReadOnlySpan text, string format, out TDerived result); 35 | public static abstract bool TryParseExact(ReadOnlySpan text, string format, out int charsRead, out TDerived result); 36 | #endif 37 | } 38 | internal interface IFormattableBinaryNetworkPrimitive : IBinaryNetworkPrimitive, ITryFormattable 39 | where TDerived : IFormattableBinaryNetworkPrimitive 40 | { 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/ISlice.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives 6 | { 7 | internal interface ISlice 8 | where TDerived : ISlice 9 | { 10 | public TItem this[int index] { get; } 11 | public int Length { get; } 12 | public TDerived Slice(int start, int length); 13 | } 14 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/ITryFormat.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives 6 | { 7 | internal interface ITryFormat 8 | { 9 | public int MaximumLengthRequired { get; } 10 | public bool TryFormat(Span destination, out int charsWritten); 11 | } 12 | 13 | internal interface ITryFormattable : ITryFormat, IFormattable 14 | { 15 | public bool TryFormat(Span destination, out int charsWritten, string? format, IFormatProvider? formatProvider); 16 | } 17 | 18 | 19 | [ExcludeFromCodeCoverage("Internal")] 20 | internal static class TryFormatExtensions 21 | { 22 | private const int MAXIMUM_STACKALLOC_LENGTH = 256; 23 | internal static string GetString( 24 | this T tryFormat 25 | ) where T : ITryFormat 26 | { 27 | var charsRequired = tryFormat.MaximumLengthRequired; 28 | var chars = charsRequired >= MAXIMUM_STACKALLOC_LENGTH 29 | ? new char[charsRequired] 30 | : stackalloc char[charsRequired]; 31 | _ = tryFormat.TryFormat(chars, out var charsWritten); 32 | chars = chars[..charsWritten]; 33 | return chars.CreateString(); 34 | } 35 | 36 | internal static string GetString( 37 | this T tryFormat, 38 | string? format, 39 | IFormatProvider? formatProvider 40 | ) where T : ITryFormattable 41 | { 42 | var charsRequired = tryFormat.MaximumLengthRequired; 43 | var chars = charsRequired >= MAXIMUM_STACKALLOC_LENGTH 44 | ? new char[charsRequired] 45 | : stackalloc char[charsRequired]; 46 | _ = tryFormat.TryFormat(chars, out var charsWritten, format, formatProvider); 47 | chars = chars[..charsWritten]; 48 | return chars.CreateString(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Utilities/Formatting.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives.Utilities 6 | { 7 | [ExcludeFromCodeCoverage("Internal")] 8 | internal static class Formatting 9 | { 10 | private const int MAX_BYTE_LENGTH = 3; 11 | 12 | public static bool TryFormatTo( 13 | this byte value, 14 | ref Span destination, 15 | ref int charsWritten 16 | ) 17 | { 18 | Span source = stackalloc char[Formatting.MAX_BYTE_LENGTH]; 19 | if (!value.TryFormat(source, out var length)) 20 | return false; 21 | source = source[..length]; 22 | source.CopyTo(destination); 23 | destination = destination[length..]; 24 | charsWritten += length; 25 | return true; 26 | } 27 | 28 | #if !NETSTANDARD2_1_OR_GREATER 29 | public static bool TryFormat( 30 | this byte value, 31 | Span destination, 32 | out int charsWritten 33 | ) 34 | { 35 | charsWritten = default; 36 | var length = value switch 37 | { 38 | >= 100 => 3, 39 | >= 10 => 2, 40 | _ => 1, 41 | }; 42 | if (destination.Length < length) 43 | return false; 44 | Span chars = stackalloc char[length]; 45 | for (var i = 0; i < chars.Length; ++i) 46 | { 47 | chars[i] = (char)(value % 10 + '0'); 48 | value /= 10; 49 | } 50 | chars.Reverse(); 51 | if (!chars.TryCopyTo(destination)) 52 | return false; 53 | charsWritten = length; 54 | return true; 55 | } 56 | #endif 57 | } 58 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Utilities/ImmutableListWrapper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | #nullable enable 4 | 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.Collections.Immutable; 9 | 10 | namespace NetworkPrimitives.Utilities 11 | { 12 | public abstract class ImmutableListWrapperWithBuilder 13 | where TDerived : ImmutableListWrapperWithBuilder 14 | where TBuilder : ImmutableListWrapperBuilder 15 | { 16 | internal ImmutableListWrapperWithBuilder() 17 | { 18 | 19 | } 20 | //public abstract TBuilder ToBuilder(); 21 | } 22 | public abstract class ImmutableListWrapperWithBuilder 23 | where TDerived : ImmutableListWrapperWithBuilder 24 | where TBuilder : ImmutableListWrapperBuilder 25 | { 26 | internal ImmutableListWrapperWithBuilder() 27 | { 28 | 29 | } 30 | //public abstract TBuilder ToBuilder(); 31 | } 32 | 33 | public abstract class ImmutableListWrapper : IReadOnlyList 34 | where TDerived : ImmutableListWrapper 35 | { 36 | protected abstract IEqualityComparer EqualityComparer { get; } 37 | protected abstract TDerived CreateInstance(ImmutableList items); 38 | protected ImmutableList Items { get; } 39 | internal ImmutableListWrapper(ImmutableList items) => this.Items = items; 40 | public int Count => Items.Count; 41 | public bool IsEmpty => Count == 0; 42 | public TItem this[int index] => this.Items[index]; 43 | public TDerived Add(TItem item) => CreateInstance(this.Items.Add(item)); 44 | public TDerived AddRange(IEnumerable items) => CreateInstance(this.Items.AddRange(items)); 45 | public TDerived Clear() => CreateInstance(ImmutableList.Empty); 46 | public TItem? Find(Predicate predicate) => this.Items.Find(predicate); 47 | public TDerived FindAll(Predicate predicate) => CreateInstance(this.Items.FindAll(predicate)); 48 | public TDerived Insert(int index, TItem item) => CreateInstance(this.Items.Insert(index, item)); 49 | public int IndexOf(TItem item) => this.Items.IndexOf(item, 0, Count, EqualityComparer); 50 | public TDerived InsertRange(int index, IEnumerable items) => CreateInstance(this.Items.InsertRange(index, items)); 51 | public ref readonly TItem ItemRef(int index) => ref this.Items.ItemRef(index); 52 | public int LastIndexOf(TItem item) => this.Items.LastIndexOf(item, 0, Count, EqualityComparer); 53 | public TDerived Remove(TItem item) => CreateInstance(this.Items.Remove(item, EqualityComparer)); 54 | public TDerived GetRange(int index, int count) => CreateInstance(this.Items.GetRange(index, count)); 55 | public TDerived RemoveAll(Predicate predicate) => CreateInstance(this.Items.RemoveAll(predicate)); 56 | public TDerived RemoveRange(IEnumerable items) => CreateInstance(this.Items.RemoveRange(items, EqualityComparer)); 57 | public TDerived RemoveAt(int index) => CreateInstance(this.Items.RemoveAt(index)); 58 | public TDerived Replace(TItem oldValue, TItem newValue) => CreateInstance(this.Items.Replace(oldValue, newValue, EqualityComparer)); 59 | public TDerived Reverse() => CreateInstance(this.Items.Reverse()); 60 | public TDerived Reverse(int index, int count) => CreateInstance(this.Items.Reverse(index, count)); 61 | public TDerived SetItem(int index, TItem item) => CreateInstance(this.Items.SetItem(index, item)); 62 | protected virtual IEnumerator GetClassEnumerator() => Items.GetEnumerator(); 63 | IEnumerator IEnumerable.GetEnumerator() => GetClassEnumerator(); 64 | IEnumerator IEnumerable.GetEnumerator() => GetClassEnumerator(); 65 | } 66 | } 67 | 68 | 69 | */ -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Utilities/ImmutableListWrapperBuilder.cs: -------------------------------------------------------------------------------- 1 | /*#nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives.Utilities 6 | { 7 | public abstract class ImmutableListWrapperBuilder 8 | where TDerived : ImmutableListWrapperBuilder 9 | { 10 | internal ImmutableListWrapperBuilder() 11 | { 12 | 13 | } 14 | //public abstract TImmutable ToImmutable(); 15 | } 16 | }*/ -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Utilities/Parsing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | #nullable enable 4 | 5 | namespace NetworkPrimitives.Utilities 6 | { 7 | [ExcludeFromCodeCoverage("Internal")] 8 | internal static class Parsing 9 | { 10 | 11 | public static bool TryParseHexUshort( 12 | ref this ReadOnlySpan text, 13 | ref int charsRead, 14 | out ushort value 15 | ) 16 | { 17 | var length = GetHexChars(text); 18 | length = Math.Min(4, length); 19 | value = default; 20 | for (var i = 0; i < length; ++i) 21 | { 22 | var hex = text[0]; 23 | text = text[1..]; 24 | value <<= 4; 25 | value |= GetValue(hex); 26 | } 27 | charsRead += length; 28 | return length > 0; 29 | 30 | static ushort GetValue(char ch) => ch switch 31 | { 32 | >= '0' and <= '9' => (ushort)(ch - '0'), 33 | >= 'a' and <= 'f' => (ushort)(ch - 'a' + 10), 34 | >= 'A' and <= 'F' => (ushort)(ch - 'A' + 10), 35 | _ => 0, 36 | }; 37 | } 38 | public static bool TryParseUInt64( 39 | ref this ReadOnlySpan text, 40 | ref int charsRead, 41 | out ulong value 42 | ) 43 | { 44 | value = default; 45 | var length = Parsing.GetDigitLength(text); 46 | if (length == 0) return false; 47 | text.SplitKeepSecond(length, out var remainder); 48 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER 49 | var success = ulong.TryParse(remainder, out value); 50 | #else 51 | var success = ulong.TryParse(remainder.GetString(), out value); 52 | #endif 53 | if (!success) return false; 54 | charsRead += length; 55 | return true; 56 | } 57 | 58 | public static bool TryParseByte( 59 | ref this ReadOnlySpan text, 60 | ref int charsRead, 61 | out byte value 62 | ) 63 | { 64 | /* 65 | 25[0-5] 66 | 2[0-4][0-9] 67 | 1[0-9][0-9] 68 | 69 | [1-9][0-9] 70 | 71 | [0-9] 72 | */ 73 | value = default; 74 | var digitLength = text.Length switch 75 | { 76 | 0 => default, 77 | 1 => GetByteDigitLength(text[0], default, default), 78 | 2 => GetByteDigitLength(text[0], text[1], default), 79 | _ => GetByteDigitLength(text[0], text[1], text[2]), 80 | }; 81 | value = 0; 82 | for (var i = 0; i < digitLength; ++i) 83 | { 84 | value *= 10; 85 | value += (byte)(text[0] - '0'); 86 | text = text[1..]; 87 | ++charsRead; 88 | } 89 | return digitLength > 0; 90 | 91 | static int GetByteDigitLength(char a, char b, char c) => (a, b, c) switch 92 | { 93 | (a: '2' , b: >= '0' and <= '4', c: >= '0' and <= '9') => 3, 94 | (a: '2' , b: '5' , c: >= '0' and <= '5') => 3, 95 | 96 | (a: >= '2' , b: >= '0' and <= '9', c: >= '0' and <= '9') => 0, 97 | 98 | (a: '1' , b: >= '0' and <= '9', c: >= '0' and <= '9') => 3, 99 | (a: >= '1' and <= '9', b: >= '0' and <= '9', c: _ ) => 2, 100 | (a: >= '0' and <= '9', b: _ , c: _ ) => 1, 101 | _ => 0, 102 | }; 103 | 104 | } 105 | 106 | private static int GetDigitLength(ReadOnlySpan text) 107 | { 108 | var length = 0; 109 | while (text.TrySliceFirst(out var ch) && ch is >= '0' and <= '9') 110 | ++length; 111 | return length; 112 | } 113 | 114 | private static int GetHexChars(ReadOnlySpan text) 115 | { 116 | var length = 0; 117 | while (text.TrySliceFirst(out var ch) && ch.IsHex()) 118 | ++length; 119 | return length; 120 | } 121 | 122 | public static bool TryReadCharacter(ref this ReadOnlySpan span, ref int charsRead, char expected) 123 | { 124 | if (!span.TrySliceFirst(out var actual) || actual != expected) 125 | return false; 126 | ++charsRead; 127 | return true; 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Common/Utilities/ReadOnlyListSpan.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace NetworkPrimitives.Utilities 6 | { 7 | [ExcludeFromCodeCoverage("Internal")] 8 | internal readonly struct ReadOnlyListSpan : IEquatable>, ISlice, T> 9 | { 10 | private readonly IReadOnlyList list; 11 | private readonly int startIndex; 12 | public ReadOnlyListSpan(IReadOnlyList? list) : this(list ?? Array.Empty(), 0, list?.Count ?? 0) 13 | { 14 | } 15 | private ReadOnlyListSpan(IReadOnlyList list, int startIndex, int length) 16 | { 17 | this.list = list; 18 | this.startIndex = startIndex; 19 | this.Length = length; 20 | } 21 | public int Length { get; } 22 | public bool IsEmpty => Length == 0; 23 | public T this[int index] => this.list[this.startIndex + index]; 24 | 25 | public ReadOnlyListSpan Slice(int start, int length) 26 | { 27 | if ((uint)start > (uint)this.Length || (uint)length > (uint)(this.Length - start)) 28 | throw new ArgumentOutOfRangeException(); 29 | return new (this.list, this.startIndex + start, length); 30 | } 31 | 32 | public IEnumerable ToEnumerable() 33 | { 34 | for (var i = 0; i < this.Length; ++i) 35 | { 36 | yield return this[i]; 37 | } 38 | } 39 | 40 | public bool Equals(ReadOnlyListSpan other) => this.list.Equals(other.list) && this.startIndex == other.startIndex && this.Length == other.Length; 41 | public override bool Equals(object? obj) => obj is ReadOnlyListSpan other && Equals(other); 42 | public override int GetHashCode() => HashCode.Combine(this.list, this.startIndex, this.Length); 43 | public static bool operator ==(ReadOnlyListSpan left, ReadOnlyListSpan right) => left.Equals(right); 44 | public static bool operator !=(ReadOnlyListSpan left, ReadOnlyListSpan right) => !left.Equals(right); 45 | } 46 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/Address/Ipv4AddressClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetworkPrimitives.Ipv4 4 | { 5 | /// 6 | /// In the classful networking architecture, an IP address is assigned to one of five classes. 7 | /// 8 | /// 9 | /// Classful IP addressing is obsolete. This type is provided for your information, but you should 10 | /// do your best to remove your need to use this type. 11 | /// 12 | public enum Ipv4AddressClass 13 | { 14 | /// 15 | /// IPv4 addresses between 0.0.0.0 and 127.255.255.255, with a default subnet mask of 255.0.0.0 16 | /// 17 | ClassA, 18 | /// 19 | /// IPv4 addresses between 128.0.0.0 and 191.255.255.255, with a default subnet mask of 255.255.0.0 20 | /// 21 | ClassB, 22 | /// 23 | /// IPv4 addresses between 192.0.0.0 and 223.255.255.255, with a default subnet mask of 255.255.255.0 24 | /// 25 | ClassC, 26 | /// 27 | /// IPv4 addresses between 224.0.0.0 and 239.255.255.255; Multicast. 28 | /// 29 | ClassD, 30 | /// 31 | /// IPv4 addresses between 240.0.0.0 and 255.255.255.255; Reserved. 32 | /// 33 | ClassE, 34 | } 35 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/Address/Ipv4AddressRangeType.cs: -------------------------------------------------------------------------------- 1 | namespace NetworkPrimitives.Ipv4 2 | { 3 | /// 4 | /// Indicates which special type of address (if any) that the address has. 5 | /// 6 | public enum Ipv4AddressRangeType 7 | { 8 | /// 9 | /// A normal address 10 | /// 11 | Normal, 12 | 13 | /// 14 | /// Private IP address, in the range 192.168.0.0/16 15 | /// 16 | /// RFC 1918 17 | Rfc1918, 18 | 19 | /// 20 | /// IP Range 100.64.0.0/10, used for carrier-grade NAT; Allocated by RFC 6598 21 | /// 22 | /// RFC 6598 23 | CgNat, 24 | 25 | /// 26 | /// Documentation ranges; Allocated by RFC 5737. 27 | /// 28 | /// RFC 5737 29 | Documentation, 30 | 31 | /// 32 | /// IP Range 169.254.0.0/16, used for link-local addresses[6] between two hosts on a single link 33 | /// when no IP address is otherwise specified, such as would have normally been retrieved from a DHCP server. 34 | /// Allocated by RFC 3927. 35 | /// 36 | /// RFC 3927 37 | Apipa, 38 | 39 | /// 40 | /// IP Range 127.0.0.0/8, used for loopback addresses to the host. 41 | /// Allocated by RFC 1122. 42 | /// 43 | /// RFC 1122 44 | Loopback, 45 | 46 | /// 47 | /// IP range 0.0.0.0/8 48 | /// 49 | Current, 50 | 51 | /// 52 | /// IP Address 255.255.255.255; The broadcast network 53 | /// 54 | Broadcast, 55 | 56 | /// 57 | /// IP Range 224.0.0.0/4; Multicast networks 58 | /// 59 | Multicast, 60 | 61 | /// 62 | /// IP Range 198.18.0.0/15, used for benchmark testing of inter-network communications between two separate subnets. 63 | /// 64 | Benchmark, 65 | } 66 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/Address/Ipv4WellKnownRanges.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives.Ipv4 6 | { 7 | /// 8 | /// Provides access to many well-defined Ipv4 address ranges. 9 | /// 10 | public static class Ipv4WellKnownRanges 11 | { 12 | /// 13 | /// Private IP address, in the range 10.0.0.0/8; Allocated by RFC 1918 14 | /// 15 | /// RFC 1918 16 | public static readonly Ipv4Subnet Rfc1918A = new (Ipv4Address.Parse(0x0A000000), Ipv4Cidr.Parse(8)); 17 | 18 | /// 19 | /// Private IP address, in the range 172.16.0.0/12; Allocated by RFC 1918 20 | /// 21 | /// RFC 1918 22 | public static readonly Ipv4Subnet Rfc1918B = new (Ipv4Address.Parse(0xAC100000), Ipv4Cidr.Parse(12)); 23 | 24 | /// 25 | /// Private IP address, in the range 192.168.0.0/16; Allocated by RFC 1918 26 | /// 27 | /// RFC 1918 28 | public static readonly Ipv4Subnet Rfc1918C = new (Ipv4Address.Parse(0xC0A80000), Ipv4Cidr.Parse(16)); 29 | 30 | /// 31 | /// IP Range 100.64.0.0/10, used for carrier-grade NAT; Allocated by RFC 6598 32 | /// 33 | /// RFC 6598 34 | public static readonly Ipv4Subnet CgNat = new (Ipv4Address.Parse(0x64400000), Ipv4Cidr.Parse(10)); 35 | 36 | /// 37 | /// IP Range 192.0.2.0/24, used for documentation; Allocated by RFC 5737. 38 | /// 39 | /// RFC 5737 40 | public static readonly Ipv4Subnet Doc1 = new (Ipv4Address.Parse(0xC0000200), Ipv4Cidr.Parse(24)); 41 | 42 | /// 43 | /// IP Range 198.51.100.0/24, used for documentation; Allocated by RFC 5737. 44 | /// 45 | /// RFC 5737 46 | public static readonly Ipv4Subnet Doc2 = new (Ipv4Address.Parse(0xC6336400), Ipv4Cidr.Parse(24)); 47 | 48 | /// 49 | /// IP Range 203.0.113.0/24, used for documentation; Allocated by RFC 5737. 50 | /// 51 | /// RFC 5737 52 | public static readonly Ipv4Subnet Doc3 = new (Ipv4Address.Parse(0xCB007100), Ipv4Cidr.Parse(24)); 53 | 54 | /// 55 | /// IP Range 169.254.0.0/16, used for link-local addresses[6] between two hosts on a single link 56 | /// when no IP address is otherwise specified, such as would have normally been retrieved from a DHCP server. 57 | /// Allocated by RFC 3927. 58 | /// 59 | /// RFC 3927 60 | public static readonly Ipv4Subnet Apipa = new (Ipv4Address.Parse(0xA9FE0000), Ipv4Cidr.Parse(16)); 61 | 62 | /// 63 | /// IP Range 127.0.0.0/8, used for loopback addresses to the host. 64 | /// Allocated by RFC 1122. 65 | /// 66 | /// RFC 1122 67 | public static readonly Ipv4Subnet Loopback = new (Ipv4Address.Parse(0x7F000000), Ipv4Cidr.Parse(8)); 68 | 69 | /// 70 | /// IP range 0.0.0.0/8 71 | /// 72 | public static readonly Ipv4Subnet Current = new (Ipv4Address.Parse(0x00000000), Ipv4Cidr.Parse(8)); 73 | 74 | /// 75 | /// IP Address 255.255.255.255; The broadcast network 76 | /// 77 | public static readonly Ipv4Subnet Broadcast = new (Ipv4Address.Parse(0xFFFFFFFF), Ipv4Cidr.Parse(32)); 78 | 79 | /// 80 | /// IP Range 224.0.0.0/4; Multicast networks 81 | /// 82 | public static readonly Ipv4Subnet Multicast = new (Ipv4Address.Parse(0xE0000000), Ipv4Cidr.Parse(4)); 83 | 84 | /// 85 | /// IP Range 198.18.0.0/15, used for benchmark testing of inter-network communications between two separate subnets. 86 | /// 87 | public static readonly Ipv4Subnet Benchmark = new (Ipv4Address.Parse(0xC6120000), Ipv4Cidr.Parse(15)); 88 | } 89 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/Formatting/Ipv4Parsing.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers.Binary; 5 | using NetworkPrimitives.Utilities; 6 | 7 | namespace NetworkPrimitives.Ipv4 8 | { 9 | [ExcludeFromCodeCoverage("Internal")] 10 | internal static class Ipv4Parsing 11 | { 12 | public static bool TryParseDottedDecimalUInt32(string? text, out uint result) 13 | { 14 | result = default; 15 | return text is not null && Ipv4Parsing.TryParseDottedDecimalUInt32(text, out var charsRead, out result) && 16 | charsRead == text.Length; 17 | } 18 | 19 | public static bool TryParseDottedDecimalUInt32(string? text, out int charsRead, out uint result) 20 | { 21 | charsRead = default; 22 | var span = text.GetSpan(); 23 | return TryParseDottedDecimalUInt32(ref span, ref charsRead, out result); 24 | } 25 | 26 | internal static bool TryParseDottedDecimalUInt32(ref ReadOnlySpan text, ref int charsRead, out uint result) 27 | { 28 | result = default; 29 | if (text.Length < Ipv4Address.MINIMUM_LENGTH) 30 | return false; 31 | Span octets = stackalloc byte[4]; 32 | var textCopy = text; 33 | var charsReadCopy = charsRead; 34 | for (var i = 0; i < 4; ++i) 35 | { 36 | if (i != 0 && !textCopy.TryReadCharacter(ref charsReadCopy, '.')) 37 | return false; 38 | if (!textCopy.TryParseByte(ref charsReadCopy, out var octet)) 39 | return false; 40 | octets[i] = octet; 41 | } 42 | charsRead = charsReadCopy; 43 | text = textCopy; 44 | result = BinaryPrimitives.ReadUInt32BigEndian(octets); 45 | return true; 46 | } 47 | public static bool TryParseDottedDecimalUInt32(ReadOnlySpan text, out uint result) 48 | => TryParseDottedDecimalUInt32(text, out var charsRead, out result) && charsRead == text.Length; 49 | public static bool TryParseDottedDecimalUInt32(ReadOnlySpan text, out int charsRead, out uint result) 50 | { 51 | charsRead = default; 52 | return TryParseDottedDecimalUInt32(ref text, ref charsRead, out result); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/Ipv4Extensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace NetworkPrimitives.Ipv4 6 | { 7 | /// 8 | /// Utility methods for and 9 | /// 10 | public static class Ipv4Extensions 11 | { 12 | private const uint CLASS_A_MASK = 0b_10000000_00000000_00000000_00000000; 13 | private const uint CLASS_B_MASK = 0b_11000000_00000000_00000000_00000000; 14 | private const uint CLASS_C_MASK = 0b_11100000_00000000_00000000_00000000; 15 | private const uint CLASS_D_MASK = 0b_11110000_00000000_00000000_00000000; 16 | private const uint CLASS_A_VALUE = 0b_00000000_00000000_00000000_00000000; 17 | private const uint CLASS_B_VALUE = 0b_10000000_00000000_00000000_00000000; 18 | private const uint CLASS_C_VALUE = 0b_11000000_00000000_00000000_00000000; 19 | private const uint CLASS_D_VALUE = 0b_11100000_00000000_00000000_00000000; 20 | 21 | /// 22 | /// Determine which special type of address (if any) that an has. 23 | /// 24 | /// An instance of 25 | /// 26 | /// The special address type, or if the address does not have a special address type. 27 | /// 28 | public static Ipv4AddressRangeType GetRangeType(this Ipv4Address value) 29 | { 30 | return CheckAll(value, out var type) ? type : default; 31 | static bool CheckAll(Ipv4Address value, out Ipv4AddressRangeType result) => 32 | CheckOne(Ipv4WellKnownRanges.Rfc1918A.Contains(value), Ipv4AddressRangeType.Rfc1918, out result) 33 | || CheckOne(Ipv4WellKnownRanges.Rfc1918B.Contains(value), Ipv4AddressRangeType.Rfc1918, out result) 34 | || CheckOne(Ipv4WellKnownRanges.Rfc1918C.Contains(value), Ipv4AddressRangeType.Rfc1918, out result) 35 | || CheckOne(Ipv4WellKnownRanges.CgNat.Contains(value), Ipv4AddressRangeType.CgNat, out result) 36 | || CheckOne(Ipv4WellKnownRanges.Doc1.Contains(value), Ipv4AddressRangeType.Documentation, out result) 37 | || CheckOne(Ipv4WellKnownRanges.Doc2.Contains(value), Ipv4AddressRangeType.Documentation, out result) 38 | || CheckOne(Ipv4WellKnownRanges.Doc3.Contains(value), Ipv4AddressRangeType.Documentation, out result) 39 | || CheckOne(Ipv4WellKnownRanges.Apipa.Contains(value), Ipv4AddressRangeType.Apipa, out result) 40 | || CheckOne(Ipv4WellKnownRanges.Loopback.Contains(value), Ipv4AddressRangeType.Loopback, out result) 41 | || CheckOne(Ipv4WellKnownRanges.Current.Contains(value), Ipv4AddressRangeType.Current, out result) 42 | || CheckOne(Ipv4WellKnownRanges.Broadcast.Contains(value), Ipv4AddressRangeType.Broadcast, out result) 43 | || CheckOne(Ipv4WellKnownRanges.Multicast.Contains(value), Ipv4AddressRangeType.Multicast, out result) 44 | || CheckOne(Ipv4WellKnownRanges.Benchmark.Contains(value), Ipv4AddressRangeType.Benchmark, out result) 45 | ; 46 | } 47 | 48 | 49 | /// 50 | /// Determine which special type of address (if any) that an has. 51 | /// 52 | /// An instance of 53 | /// 54 | /// The special address type, or if the subnet does not have a special address type. 55 | /// 56 | public static Ipv4AddressRangeType GetRangeType(this Ipv4Subnet value) 57 | { 58 | return CheckAll(value, out var type) ? type : default; 59 | static bool CheckAll(Ipv4Subnet value, out Ipv4AddressRangeType result) => 60 | CheckOne(Ipv4WellKnownRanges.Rfc1918A.Contains(value), Ipv4AddressRangeType.Rfc1918, out result) 61 | || CheckOne(Ipv4WellKnownRanges.Rfc1918B.Contains(value), Ipv4AddressRangeType.Rfc1918, out result) 62 | || CheckOne(Ipv4WellKnownRanges.Rfc1918C.Contains(value), Ipv4AddressRangeType.Rfc1918, out result) 63 | || CheckOne(Ipv4WellKnownRanges.CgNat.Contains(value), Ipv4AddressRangeType.CgNat, out result) 64 | || CheckOne(Ipv4WellKnownRanges.Doc1.Contains(value), Ipv4AddressRangeType.Documentation, out result) 65 | || CheckOne(Ipv4WellKnownRanges.Doc2.Contains(value), Ipv4AddressRangeType.Documentation, out result) 66 | || CheckOne(Ipv4WellKnownRanges.Doc3.Contains(value), Ipv4AddressRangeType.Documentation, out result) 67 | || CheckOne(Ipv4WellKnownRanges.Apipa.Contains(value), Ipv4AddressRangeType.Apipa, out result) 68 | || CheckOne(Ipv4WellKnownRanges.Loopback.Contains(value), Ipv4AddressRangeType.Loopback, out result) 69 | || CheckOne(Ipv4WellKnownRanges.Current.Contains(value), Ipv4AddressRangeType.Current, out result) 70 | || CheckOne(Ipv4WellKnownRanges.Broadcast.Contains(value), Ipv4AddressRangeType.Broadcast, out result) 71 | || CheckOne(Ipv4WellKnownRanges.Multicast.Contains(value), Ipv4AddressRangeType.Multicast, out result) 72 | || CheckOne(Ipv4WellKnownRanges.Benchmark.Contains(value), Ipv4AddressRangeType.Benchmark, out result) 73 | ; 74 | } 75 | 76 | private static bool CheckOne(bool ret, Ipv4AddressRangeType type, out Ipv4AddressRangeType result) 77 | { 78 | result = ret ? type : default; 79 | return ret; 80 | } 81 | 82 | /// 83 | /// Determine which address class an is a part of. 84 | /// 85 | /// 86 | /// An instance of an 87 | /// 88 | /// 89 | /// An representing which address class is a part of. 90 | /// 91 | /// 92 | /// Classful IP addressing is obsolete. This type is provided for your information, but you should 93 | /// do your best to remove your need to use this type. 94 | /// 95 | public static Ipv4AddressClass GetAddressClass(this Ipv4Address address) 96 | { 97 | var value = address.Value; 98 | if ((value & CLASS_A_MASK) == CLASS_A_VALUE) 99 | return Ipv4AddressClass.ClassA; 100 | if ((value & CLASS_B_MASK) == CLASS_B_VALUE) 101 | return Ipv4AddressClass.ClassB; 102 | if ((value & CLASS_C_MASK) == CLASS_C_VALUE) 103 | return Ipv4AddressClass.ClassC; 104 | if ((value & CLASS_D_MASK) == CLASS_D_VALUE) 105 | return Ipv4AddressClass.ClassD; 106 | return Ipv4AddressClass.ClassE; 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/Range/Ipv4AddressRange.ClassEnumerator.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace NetworkPrimitives.Ipv4 8 | { 9 | public readonly partial struct Ipv4AddressRange 10 | { 11 | private class ClassEnumerator : IEnumerator, IEnumerable 12 | { 13 | private readonly Ipv4Address startAddress; 14 | private readonly ulong totalAddresses; 15 | private const int STATE_NOT_STARTED = 0; 16 | private const int STATE_IN_PROGRESS = 1; 17 | private const int STATE_FINISHED = 2; 18 | 19 | private int state; 20 | private uint counter; 21 | private Ipv4Address current; 22 | 23 | public ClassEnumerator(Ipv4Address startAddress, ulong totalAddresses) 24 | { 25 | this.startAddress = startAddress; 26 | this.totalAddresses = totalAddresses; 27 | this.state = default; 28 | this.current = default; 29 | this.counter = default; 30 | this.Reset(); 31 | } 32 | 33 | public bool MoveNext() 34 | { 35 | switch (this.state) 36 | { 37 | case STATE_NOT_STARTED: 38 | this.current = startAddress; 39 | this.state = STATE_IN_PROGRESS; 40 | return true; 41 | case STATE_IN_PROGRESS when this.counter + 1 > totalAddresses: 42 | this.state = STATE_FINISHED; 43 | return false; 44 | case STATE_IN_PROGRESS: 45 | ++this.counter; 46 | this.current = this.startAddress.AddInternal(this.counter); 47 | return true; 48 | default: 49 | return false; 50 | } 51 | } 52 | 53 | public void Reset() 54 | { 55 | this.state = default; 56 | this.counter = default; 57 | this.current = default; 58 | } 59 | 60 | object IEnumerator.Current => this.Current; 61 | 62 | public Ipv4Address Current => state switch 63 | { 64 | 0 => throw new InvalidOperationException("Enumeration not yet started."), 65 | 1 => this.current, 66 | _ => throw new InvalidOperationException("Enumeration already finished."), 67 | }; 68 | void IDisposable.Dispose() 69 | { 70 | } 71 | 72 | public IEnumerator GetEnumerator() => this; 73 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/Range/Ipv4AddressRange.RefStructEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetworkPrimitives.Ipv4 4 | { 5 | /// 6 | /// Allocation-less enumerator over a range of 7 | /// 8 | public ref struct Ipv4AddressEnumerator 9 | { 10 | private readonly Ipv4Address startAddress; 11 | private readonly ulong totalAddresses; 12 | private const int STATE_NOT_STARTED = 0; 13 | private const int STATE_IN_PROGRESS = 1; 14 | private const int STATE_FINISHED = 2; 15 | 16 | private int state; 17 | private uint counter; 18 | private Ipv4Address current; 19 | 20 | internal Ipv4AddressEnumerator(Ipv4Address startAddress, ulong totalAddresses) 21 | { 22 | this.startAddress = startAddress; 23 | this.totalAddresses = totalAddresses; 24 | this.state = default; 25 | this.current = default; 26 | this.counter = default; 27 | this.Reset(); 28 | } 29 | 30 | /// 31 | /// Advances the enumerator to the next address 32 | /// 33 | /// 34 | /// if the enumerator was successfully advanced to the next element; 35 | /// if the enumerator has passed the end of the range. 36 | /// 37 | public bool MoveNext() 38 | { 39 | switch (this.state) 40 | { 41 | case STATE_NOT_STARTED: 42 | this.current = startAddress; 43 | this.state = STATE_IN_PROGRESS; 44 | return true; 45 | case STATE_IN_PROGRESS when this.counter + 1 > totalAddresses: 46 | this.state = STATE_FINISHED; 47 | return false; 48 | case STATE_IN_PROGRESS: 49 | ++this.counter; 50 | this.current = this.startAddress.AddInternal(this.counter); 51 | return true; 52 | default: 53 | return false; 54 | } 55 | } 56 | 57 | /// 58 | /// Sets the enumerator to its initial position, which is before the first address in the range. 59 | /// 60 | public void Reset() 61 | { 62 | this.state = default; 63 | this.counter = default; 64 | this.current = default; 65 | } 66 | 67 | /// 68 | /// The current address 69 | /// 70 | /// 71 | /// The enumerator has not yet started, or the enumerator has already finished. 72 | /// 73 | public Ipv4Address Current => state switch 74 | { 75 | 0 => throw new InvalidOperationException("Enumeration not yet started."), 76 | 1 => this.current, 77 | _ => throw new InvalidOperationException("Enumeration already finished."), 78 | }; 79 | } 80 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/RangeList/Ipv4AddressRangeListEnumerator.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using NetworkPrimitives.Utilities; 5 | 6 | namespace NetworkPrimitives.Ipv4 7 | { 8 | /// 9 | /// Allocation-less enumerator over a range of 10 | /// 11 | public ref struct Ipv4AddressRangeListEnumerator 12 | { 13 | private readonly ReadOnlyListSpan original; 14 | private ReadOnlyListSpan available; 15 | private Ipv4AddressRange current; 16 | internal Ipv4AddressRangeListEnumerator(ReadOnlyListSpan original) 17 | { 18 | this.available = this.original = original; 19 | this.current = default; 20 | } 21 | 22 | 23 | /// 24 | /// Advances the enumerator to the next range 25 | /// 26 | /// 27 | /// if the enumerator was successfully advanced to the next element; 28 | /// if the enumerator has passed the end of the range. 29 | /// 30 | public bool MoveNext() => this.available.TrySliceFirst(out this.current); 31 | 32 | 33 | /// 34 | /// The current range 35 | /// 36 | public Ipv4AddressRange Current => current; 37 | 38 | 39 | /// 40 | /// Sets the enumerator to its initial position, which is before the first range in the range. 41 | /// 42 | public void Reset() 43 | { 44 | this.available = this.original; 45 | this.current = default; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/SubnetTree/Ipv4SubnetDictionary.Collections.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | #pragma warning disable CS1591 6 | 7 | namespace NetworkPrimitives.Ipv4; 8 | 9 | public partial class Ipv4SubnetDictionary 10 | : IReadOnlyDictionary, 11 | //IDictionary, 12 | //ICollection>, 13 | 14 | IEnumerable>>, 15 | IReadOnlyCollection>>, 16 | IReadOnlyDictionary> 17 | { 18 | private static NodeEnumerator GetNodeEnumerator(Node? node) => new (node); 19 | private NodeEnumerator GetNodeEnumerator() => GetNodeEnumerator(this.rootNode); 20 | 21 | public IEnumerable<(Ipv4Subnet Subnet, TValue? Value, Ipv4Subnet? Left, Ipv4Subnet? Right)> Nodes() 22 | => this.GetNodeEnumerator().Select(n => n.ToNodeTuple()); 23 | 24 | 25 | public IEnumerable<(Ipv4Subnet Subnet, TValue Value)> SubnetsAsTuples() 26 | { 27 | using var enumerator = GetNodeEnumerator(); 28 | while (enumerator.MoveNext()) 29 | { 30 | if (enumerator.Current is IValueNode valueNode) 31 | yield return (valueNode.Subnet, valueNode.Value); 32 | } 33 | } 34 | private IEnumerable<(Ipv4Address Address, TValue Value)> AddressesAsTuples() 35 | { 36 | using var enumerator = GetNodeEnumerator(); 37 | while (enumerator.MoveNext()) 38 | { 39 | if (enumerator.Current is not IValueNode valueNode) continue; 40 | foreach (var address in valueNode.Subnet.GetAllAddresses().ToEnumerable()) 41 | yield return (address, valueNode.Value); 42 | } 43 | } 44 | private IEnumerable> SubnetsAsKeyValuePairs() 45 | { 46 | using var enumerator = GetNodeEnumerator(); 47 | while (enumerator.MoveNext()) 48 | { 49 | if (enumerator.Current is IValueNode valueNode) 50 | yield return new(valueNode.Subnet, valueNode.Value); 51 | } 52 | } 53 | private IEnumerable> AddressesAsKeyValuePairs() 54 | { 55 | using var enumerator = GetNodeEnumerator(); 56 | while (enumerator.MoveNext()) 57 | { 58 | if (enumerator.Current is not IValueNode valueNode) continue; 59 | foreach (var address in valueNode.Subnet.GetAllAddresses().ToEnumerable()) 60 | yield return new(address, valueNode.Value); 61 | } 62 | } 63 | 64 | 65 | 66 | #region IEnumerable 67 | 68 | IEnumerator IEnumerable.GetEnumerator() 69 | => this.SubnetsAsKeyValuePairs().GetEnumerator(); 70 | 71 | #endregion IEnumerable 72 | 73 | #region IEnumerable> 74 | 75 | IEnumerator> IEnumerable>.GetEnumerator() 76 | => this.AddressesAsKeyValuePairs().GetEnumerator(); 77 | 78 | #endregion IEnumerable> 79 | 80 | #region IReadOnlyDictionary 81 | 82 | IEnumerable IReadOnlyDictionary.Values 83 | => new ValueCollection(this); 84 | 85 | IEnumerable IReadOnlyDictionary.Keys 86 | => new AddressKeyCollection(this); 87 | 88 | #endregion IReadOnlyDictionary 89 | 90 | 91 | #region ICollection> 92 | /* 93 | void ICollection>.Add(KeyValuePair item) 94 | => this.Add(item.Key, item.Value); 95 | 96 | bool ICollection>.Contains(KeyValuePair item) 97 | => this.TryGetValue(item.Key, out var value) && this.valueComparer.Equals(value, item.Value); 98 | 99 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) 100 | => throw new NotImplementedException(); 101 | 102 | bool ICollection>.Remove(KeyValuePair item) 103 | => ((ICollection>)this).Contains(item) 104 | && this.Remove(item.Key); 105 | 106 | bool ICollection>.IsReadOnly => false; 107 | */ 108 | #endregion ICollection> 109 | 110 | 111 | #region IDictionary 112 | 113 | /* 114 | bool IDictionary.Remove(Ipv4Address key) 115 | => this.Remove(key); 116 | 117 | ICollection IDictionary.Values 118 | => new ValueCollection(this); 119 | 120 | ICollection IDictionary.Keys 121 | => new AddressKeyCollection(this); 122 | */ 123 | #endregion IDictionary 124 | 125 | 126 | #region IEnumerable>> 127 | 128 | IEnumerator>> IEnumerable>>.GetEnumerator() 129 | { 130 | var dict = this.Clone(); 131 | return dict.GetNodeEnumerator() 132 | .OfType() 133 | .Select(ToKeyValuePair).GetEnumerator(); 134 | KeyValuePair> ToKeyValuePair(IValueNode node) 135 | => new (node.Subnet, dict.Get(node.Subnet) ?? Array.Empty()); 136 | } 137 | 138 | #endregion IEnumerable>> 139 | 140 | 141 | #region IReadOnlyDictionary> 142 | 143 | IEnumerable IReadOnlyDictionary>.Keys 144 | => new SubnetKeyCollection(this); 145 | 146 | IEnumerable> IReadOnlyDictionary>.Values 147 | => throw new NotImplementedException(); 148 | 149 | IReadOnlyList IReadOnlyDictionary>.this[Ipv4Subnet subnet] 150 | => Get(subnet) ?? Array.Empty(); 151 | 152 | #endregion IReadOnlyDictionary> 153 | 154 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/SubnetTree/Ipv4SubnetDictionary.Lookups.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq; 6 | 7 | #pragma warning disable CS1591 8 | 9 | 10 | namespace NetworkPrimitives.Ipv4; 11 | 12 | public partial class Ipv4SubnetDictionary 13 | { 14 | public TValue this[Ipv4Address address] 15 | { 16 | get => this.Get(address); 17 | set => this.Set(address, value); 18 | } 19 | public TValue this[Ipv4Subnet subnet] 20 | { 21 | get 22 | { 23 | if (!this.TryGetValue(subnet, out var value) || value.Count == 0) 24 | throw new KeyNotFoundException(); 25 | return value[0]; 26 | } 27 | set => this.Set(subnet, value); 28 | } 29 | 30 | 31 | private TValue Get(Ipv4Address address) => this.TryGetValue(address, out var value) ? value : throw new KeyNotFoundException(); 32 | 33 | private IReadOnlyList? Get(Ipv4Subnet subnet) 34 | { 35 | _ = this.TryGetValue(subnet, out var value); 36 | return value; 37 | } 38 | 39 | #if NETSTANDARD2_0 40 | private bool NonNullTryGetValue(Ipv4Subnet subnet, out IReadOnlyList value) 41 | { 42 | value = Array.Empty(); // Null-forgiving operator 43 | if (!this.TryGetValue(subnet, out var nullableValue)) return false; 44 | value = nullableValue; 45 | return true; 46 | } 47 | bool IReadOnlyDictionary>.TryGetValue(Ipv4Subnet address, out IReadOnlyList value) 48 | => NonNullTryGetValue(address, out value); 49 | #endif 50 | 51 | public bool TryGetValue(Ipv4Subnet subnet, [NotNullWhen(true)] out IReadOnlyList? value) 52 | { 53 | value = default; 54 | var nodes = LocateMatchingNode(this.rootNode, subnet); 55 | if (nodes is null) 56 | return false; 57 | List? list = null; 58 | while (nodes.TryPop(out var node)) 59 | { 60 | if (node is not IValueNode valueNode) continue; 61 | list ??= new (); 62 | list.Add(valueNode.Value); 63 | } 64 | value = list?.AsReadOnly(); 65 | return value is not null; 66 | } 67 | 68 | #if NETSTANDARD2_0 69 | private bool NonNullTryGetValue(Ipv4Address address, out TValue value) 70 | { 71 | value = default!; // Null-forgiving operator 72 | if (!this.TryGetValue(address, out var nullableValue)) return false; 73 | value = nullableValue; 74 | return true; 75 | } 76 | bool IReadOnlyDictionary.TryGetValue(Ipv4Address address, out TValue value) 77 | => NonNullTryGetValue(address, out value); 78 | /* 79 | bool IDictionary.TryGetValue(Ipv4Address address, out TValue value) 80 | => NonNullTryGetValue(address, out value); 81 | */ 82 | #endif 83 | 84 | public bool TryGetValue(Ipv4Address address, [MaybeNullWhen(false)] out TValue value) 85 | { 86 | value = default; 87 | var nodes = LocateMatchingNode(this.rootNode, new (address, Ipv4Cidr.Slash32)); 88 | if (nodes is null) 89 | return false; 90 | while (nodes.TryPop(out var node)) 91 | { 92 | if (node is not IValueNode valueNode) continue; 93 | value = valueNode.Value; 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | public bool ContainsKey(Ipv4Subnet subnet) => ContainsKey(this.rootNode, subnet); 100 | public bool ContainsKey(Ipv4Address address) => ContainsKey(this.rootNode, new (address, Ipv4Cidr.Slash32)); 101 | 102 | private static bool ContainsKey(Node? node, Ipv4Subnet subnet) 103 | { 104 | while (node is not null) 105 | { 106 | switch (node) 107 | { 108 | case IValueNode valueNode when valueNode.Subnet.Contains(subnet): 109 | return true; 110 | case BranchNode(_, var (leftSubnet, left), var (rightSubnet, right)) when leftSubnet.Contains(subnet): 111 | node = left; 112 | continue; 113 | case BranchNode(_, var (leftSubnet, left), var (rightSubnet, right)) when rightSubnet.Contains(subnet): 114 | node = right; 115 | continue; 116 | default: 117 | node = null; 118 | continue; 119 | } 120 | } 121 | return false; 122 | } 123 | private static Stack? LocateMatchingNode(Node? node, Ipv4Subnet address) 124 | { 125 | Stack? stack = null; 126 | while (node is not null) 127 | { 128 | stack ??= new(); 129 | stack.Push(node); 130 | switch (node) 131 | { 132 | case BranchNode(_, var (leftSubnet, left), var (rightSubnet, right)) when leftSubnet.Contains(address): 133 | node = left; 134 | continue; 135 | case BranchNode(_, var (leftSubnet, left), var (rightSubnet, right)) when rightSubnet.Contains(address): 136 | node = right; 137 | continue; 138 | default: 139 | node = null; 140 | continue; 141 | } 142 | } 143 | return stack; 144 | } 145 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/SubnetTree/Ipv4SubnetDictionary.NodeEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | #pragma warning disable CS1591 6 | 7 | 8 | namespace NetworkPrimitives.Ipv4; 9 | 10 | public partial class Ipv4SubnetDictionary 11 | { 12 | private class NodeEnumerator : IEnumerable, IEnumerator 13 | { 14 | private const int STATE_NOT_STARTED = 0; 15 | private const int STATE_STARTED = 1; 16 | private const int STATE_FINISHED = 2; 17 | 18 | private readonly Node? rootNode; 19 | private readonly Queue queue = new(); 20 | private int state = STATE_NOT_STARTED; 21 | 22 | public NodeEnumerator(Ipv4SubnetDictionary dictionary) : this(dictionary.rootNode) 23 | { 24 | } 25 | 26 | public NodeEnumerator(Node? rootNode) => this.rootNode = rootNode; 27 | private NodeEnumerator(Node? rootNode, IEnumerable queueNodes, int state) 28 | { 29 | this.rootNode = rootNode; 30 | this.state = state; 31 | foreach (var node in queueNodes) 32 | this.queue.Enqueue(node); 33 | } 34 | 35 | public void Reset() 36 | { 37 | this.queue.Clear(); 38 | this.state = STATE_NOT_STARTED; 39 | } 40 | 41 | public bool MoveNext() 42 | { 43 | switch (this.state) 44 | { 45 | case STATE_NOT_STARTED when this.rootNode is null: 46 | this.state = STATE_FINISHED; 47 | return false; 48 | case STATE_NOT_STARTED: 49 | this.state = STATE_STARTED; 50 | this.queue.Enqueue(this.rootNode); 51 | goto case STATE_STARTED; 52 | case STATE_STARTED: 53 | this.current = HandleMoveNext(); 54 | this.state = this.current is null 55 | ? STATE_FINISHED 56 | : STATE_STARTED; 57 | return this.state == STATE_STARTED; 58 | default: 59 | return false; 60 | } 61 | } 62 | 63 | private Node? HandleMoveNext() 64 | { 65 | if (!this.queue.TryDequeue(out var nextNode)) 66 | { 67 | return null; 68 | } 69 | if (nextNode is not BranchNode(_, var (leftSubnet, left), var (rightSubnet, right))) return nextNode; 70 | this.QueueNode(left); 71 | this.QueueNode(right); 72 | return nextNode; 73 | } 74 | 75 | private void QueueNode(Node? node) 76 | { 77 | if (node is not null) 78 | this.queue.Enqueue(node); 79 | } 80 | 81 | public NodeEnumerator Clone() => new (this.rootNode, this.queue, this.state); 82 | private Node? current = null; 83 | public Node Current => this.current ?? throw new InvalidOperationException(); 84 | public NodeEnumerator GetEnumerator() => this; 85 | 86 | 87 | object? IEnumerator.Current => this.Current; 88 | void IDisposable.Dispose() 89 | { 90 | } 91 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 92 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 93 | } 94 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/SubnetTree/Ipv4SubnetDictionary.Nodes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | #pragma warning disable CS1591 5 | 6 | namespace NetworkPrimitives.Ipv4; 7 | 8 | public static class NodeExtensions 9 | { 10 | public static void Deconstruct( 11 | this Ipv4SubnetDictionary.IValueNode node, 12 | out Ipv4Subnet subnet, 13 | out TValue value 14 | ) 15 | { 16 | subnet = node.Subnet; 17 | value = node.Value; 18 | } 19 | 20 | 21 | public static void Deconstruct( 22 | this Ipv4SubnetDictionary.IBranchNode node, 23 | out Ipv4Subnet subnet, 24 | out Ipv4SubnetDictionary.Node? left, 25 | out Ipv4SubnetDictionary.Node? right 26 | ) 27 | { 28 | subnet = node.Subnet; 29 | left = node.Left; 30 | right = node.Right; 31 | } 32 | } 33 | 34 | public partial class Ipv4SubnetDictionary 35 | { 36 | 37 | public readonly record struct NodeAndSubnet(Ipv4Subnet Subnet, Node? Node); 38 | 39 | 40 | 41 | public interface INode 42 | { 43 | Ipv4Subnet Subnet { get; } 44 | public (Ipv4Subnet Subnet, TValue? Value, Ipv4Subnet? Left, Ipv4Subnet? Right) ToNodeTuple(); 45 | } 46 | public interface IValueNode : INode 47 | { 48 | TValue Value { get; } 49 | } 50 | public interface IBranchNode : INode 51 | { 52 | Node? Left { get; } 53 | Node? Right { get; } 54 | Ipv4Subnet LeftSubnet { get; } 55 | Ipv4Subnet RightSubnet { get; } 56 | } 57 | public interface IBranchValueNode : IBranchNode, IValueNode 58 | { 59 | } 60 | 61 | public abstract record Node( 62 | Ipv4Subnet Subnet 63 | ) : INode 64 | { 65 | public abstract (Ipv4Subnet Subnet, TValue? Value, Ipv4Subnet? Left, Ipv4Subnet? Right) ToNodeTuple(); 66 | } 67 | 68 | public record LeafNode( 69 | Ipv4Subnet Subnet, 70 | TValue Value 71 | ) : Node(Subnet), IValueNode 72 | { 73 | public override (Ipv4Subnet Subnet, TValue? Value, Ipv4Subnet? Left, Ipv4Subnet? Right) ToNodeTuple() 74 | => (this.Subnet, this.Value, null, null); 75 | 76 | public BranchNode Split() 77 | { 78 | _ = this.Subnet.TrySplit(out var leftSubnet, out var rightSubnet); 79 | return BranchNode.Create( 80 | Subnet, 81 | new LeafNode(leftSubnet, this.Value), 82 | new LeafNode(rightSubnet, this.Value) 83 | ); 84 | } 85 | } 86 | 87 | public record BranchNode( 88 | Ipv4Subnet Subnet, 89 | NodeAndSubnet Left, 90 | NodeAndSubnet Right 91 | ) : Node(Subnet), IBranchNode 92 | { 93 | public Ipv4SubnetMask ChildMask => Left.Subnet.Mask; 94 | public Ipv4Cidr ChildCidr => Left.Subnet.Cidr; 95 | 96 | public static BranchNode Create( 97 | Ipv4Subnet subnet, 98 | Node? left, 99 | Node? right 100 | ) 101 | { 102 | _ = subnet.TrySplit(out var leftSubnet, out var rightSubnet); 103 | return new (subnet, new (leftSubnet, left), new (rightSubnet, right)); 104 | } 105 | public static BranchValueNode Create( 106 | Ipv4Subnet subnet, 107 | TValue value, 108 | Node? left, 109 | Node? right 110 | ) 111 | { 112 | _ = subnet.TrySplit(out var leftSubnet, out var rightSubnet); 113 | return new (subnet, value, new (leftSubnet, left), new (rightSubnet, right)); 114 | } 115 | 116 | Node? IBranchNode.Left => this.Left.Node; 117 | 118 | Node? IBranchNode.Right => this.Right.Node; 119 | 120 | Ipv4Subnet IBranchNode.LeftSubnet => this.Left.Subnet; 121 | 122 | Ipv4Subnet IBranchNode.RightSubnet => this.Right.Subnet; 123 | 124 | public override (Ipv4Subnet Subnet, TValue? Value, Ipv4Subnet? Left, Ipv4Subnet? Right) ToNodeTuple() 125 | => (this.Subnet, default, this.Left.Subnet, this.Right.Subnet); 126 | 127 | public static BranchNode WithLeft(BranchNode node, Node? left) 128 | => node is BranchValueNode bvn 129 | ? bvn with { Left = bvn.Left with { Node = left } } 130 | : node with { Left = node.Left with { Node = left } }; 131 | 132 | public static BranchNode WithRight(BranchNode node, Node? right) 133 | => node is BranchValueNode bvn 134 | ? bvn with { Right = bvn.Right with { Node = right } } 135 | : node with { Right = node.Right with { Node = right } }; 136 | } 137 | 138 | 139 | public record BranchValueNode( 140 | Ipv4Subnet Subnet, 141 | TValue Value, 142 | NodeAndSubnet Left, 143 | NodeAndSubnet Right 144 | ) : BranchNode(Subnet, Left, Right), IBranchValueNode 145 | { 146 | 147 | public override (Ipv4Subnet Subnet, TValue? Value, Ipv4Subnet? Left, Ipv4Subnet? Right) ToNodeTuple() 148 | => (this.Subnet, this.Value, this.Left.Subnet, this.Right.Subnet); 149 | 150 | public static BranchValueNode From(BranchNode node, TValue value) 151 | => new (node.Subnet, value, node.Left, node.Right); 152 | // ReSharper disable once SuggestBaseTypeForParameter 153 | public static BranchValueNode From(LeafNode node) 154 | => Create(node.Subnet, node.Value, null, null); 155 | 156 | public BranchNode WithoutValue() => new (this.Subnet, this.Left, this.Right); 157 | } 158 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/SubnetTree/Ipv4SubnetDictionary.Remove.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1591 2 | /* 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | namespace NetworkPrimitives.Ipv4; 8 | 9 | public partial class Ipv4SubnetDictionary 10 | { 11 | public bool Remove(Ipv4Address address) 12 | => Remove(new Ipv4Subnet(address, Ipv4Cidr.Slash32)); 13 | public bool Remove(Ipv4Address address, TValue value) 14 | => Remove(new Ipv4Subnet(address, Ipv4Cidr.Slash32), value); 15 | 16 | public bool Remove(Ipv4Subnet subnet) 17 | { 18 | var newNode = Remove(this.rootNode, subnet, out var removed); 19 | this.rootNode = Consolidate(newNode); 20 | return removed; 21 | } 22 | 23 | public bool Remove(Ipv4Subnet subnet, TValue value) 24 | { 25 | var newNode = RemoveWithValue(this.rootNode, subnet, value, out var removed); 26 | this.rootNode = Consolidate(newNode); 27 | return removed; 28 | } 29 | 30 | 31 | private Node? RemoveWithValue( 32 | Node? originalNode, 33 | Ipv4Subnet subnet, 34 | TValue value, 35 | out bool removed 36 | ) => RemoveWithValue(originalNode, subnet, value, this.valueComparer, out removed); 37 | 38 | private static Node? Remove( 39 | Node? originalNode, 40 | Ipv4Subnet subnet, 41 | out bool removed 42 | ) 43 | { 44 | if (originalNode is null) 45 | { 46 | removed = false; 47 | return null; 48 | } 49 | if (originalNode.Subnet == subnet) 50 | { 51 | removed = true; 52 | return null; 53 | } 54 | 55 | switch (originalNode) 56 | { 57 | case LeafNode leafNode: 58 | return Remove(leafNode.Split(), subnet, out removed); 59 | case BranchNode branch: 60 | Node? newNode; 61 | if (branch.Left.Subnet.Contains(subnet)) 62 | { 63 | newNode = Remove(branch.Left.Node, subnet, out removed); 64 | return removed ? BranchNode.WithLeft(branch, newNode) : branch; 65 | } 66 | newNode = Remove(branch.Right.Node, subnet, out removed); 67 | return removed ? BranchNode.WithRight(branch, newNode) : branch; 68 | default: 69 | throw new InvalidOperationException(); 70 | } 71 | } 72 | 73 | 74 | private static Node? RemoveWithValue( 75 | Node? originalNode, 76 | Ipv4Subnet subnet, 77 | TValue value, 78 | IEqualityComparer valueComparer, 79 | out bool removed 80 | ) 81 | { 82 | Node? newNode; 83 | if (originalNode is null) 84 | { 85 | removed = false; 86 | return null; 87 | } 88 | 89 | if (originalNode.Subnet == subnet) 90 | { 91 | (removed, newNode) = originalNode switch 92 | { 93 | BranchValueNode node when valueComparer.Equals(node.Value, value) 94 | => (Removed: true, Node: node.WithoutValue()), 95 | LeafNode node when valueComparer.Equals(node.Value, value) 96 | => (Removed: true, Node: null), 97 | BranchValueNode or BranchNode or LeafNode 98 | => (Removed: false, Node: originalNode), 99 | _ => throw new InvalidOperationException(), 100 | }; 101 | removed = originalNode is IValueNode(_, var nodeValue) && valueComparer.Equals(nodeValue, value); 102 | return newNode; 103 | } 104 | 105 | if (originalNode is not BranchNode branch) 106 | { 107 | removed = false; 108 | return originalNode; 109 | } 110 | 111 | 112 | if (branch.Left.Subnet.Contains(subnet)) 113 | { 114 | newNode = RemoveWithValue(branch.Left.Node, subnet, value, valueComparer, out removed); 115 | return removed ? BranchNode.WithLeft(branch, newNode) : branch; 116 | } 117 | 118 | newNode = RemoveWithValue(branch.Right.Node, subnet, value, valueComparer, out removed); 119 | return removed ? BranchNode.WithRight(branch, newNode) : branch; 120 | 121 | } 122 | } 123 | */ -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv4/SubnetTree/Ipv4SubnetDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | using JetBrains.Annotations; 8 | 9 | #pragma warning disable CS1591 10 | 11 | namespace NetworkPrimitives.Ipv4; 12 | 13 | public partial class Ipv4SubnetDictionary 14 | { 15 | private enum AddSetMode 16 | { 17 | Add, 18 | TryAdd, 19 | Set 20 | } 21 | 22 | [PublicAPI] 23 | public Ipv4SubnetDictionary(IEqualityComparer? valueComparer = null) 24 | : this(true, null, valueComparer) 25 | { 26 | } 27 | 28 | [PublicAPI] 29 | public Ipv4SubnetDictionary(bool consolidate, IEqualityComparer? valueComparer = null) 30 | : this(consolidate, null, valueComparer) 31 | { 32 | } 33 | private Ipv4SubnetDictionary(bool consolidate, Node? rootNode, IEqualityComparer? valueComparer) 34 | { 35 | this.valueComparer = valueComparer ?? EqualityComparer.Default; 36 | this.consolidate = consolidate; 37 | this.rootNode = rootNode; 38 | } 39 | 40 | private readonly bool consolidate; 41 | private Node? rootNode; 42 | private readonly IEqualityComparer valueComparer; 43 | 44 | [DoesNotReturn] 45 | private static Node ThrowDuplicateKey(TKey key, string parameterName) 46 | { 47 | throw new ArgumentException($"Duplicate key '{key}' added.", parameterName); 48 | } 49 | 50 | public void Clear() => this.rootNode = null; 51 | 52 | public Ipv4SubnetDictionary Clone() => new (this.consolidate, this.rootNode, this.valueComparer); 53 | 54 | public int Count => throw new NotImplementedException(); 55 | 56 | public INode? RootNodeInterface => this.rootNode; 57 | 58 | [PublicAPI] 59 | public IEnumerable Addresses => new AddressKeyCollection(this); 60 | 61 | [PublicAPI] 62 | public IEnumerable Subnets => new SubnetKeyCollection(this); 63 | 64 | 65 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv6/Address/Ipv6Address.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Runtime.InteropServices; 5 | using NetworkPrimitives.Utilities; 6 | 7 | namespace NetworkPrimitives.Ipv6 8 | { 9 | public readonly struct Ipv6Address : IEquatable// , IFormattable 10 | { 11 | internal readonly ulong Low; 12 | internal readonly ulong High; 13 | 14 | private Ipv6Address(ulong low, ulong high) 15 | { 16 | this.Low = low; 17 | this.High = high; 18 | } 19 | 20 | internal void Deconstruct(out ulong low, out ulong high) 21 | { 22 | low = this.Low; 23 | high = this.High; 24 | } 25 | 26 | public static Ipv6Address operator &(Ipv6Address address, Ipv6SubnetMask mask) 27 | => new (address.Low & mask.Low, address.High & mask.High); 28 | 29 | public static Ipv6Address Parse(Ipv6SubnetMask value) => new (value.Low, value.High); 30 | 31 | 32 | public static Ipv6Address Parse(string? value) 33 | => TryParse(value, out var result) ? result : throw new FormatException(); 34 | public static bool TryParse(IPAddress? ipAddress, out Ipv6Address result) 35 | { 36 | result = default; 37 | if (ipAddress is null || ipAddress.AddressFamily != AddressFamily.InterNetworkV6) return false; 38 | Span bytes = stackalloc byte[16]; 39 | return ipAddress.TryWriteBytes(bytes, out _) && TryParse(bytes, out result); 40 | } 41 | 42 | public static bool TryParse(ReadOnlySpan octets, out Ipv6Address result) 43 | { 44 | result = default; 45 | if (octets.Length < 16) 46 | return false; 47 | var longs = MemoryMarshal.Cast(octets); 48 | result = new Ipv6Address(longs[0], longs[1]); 49 | return true; 50 | } 51 | 52 | public static bool TryParse(string? text, out Ipv6Address result) 53 | => TryParse(text, out _, out result); 54 | 55 | public IPAddress ToIpAddress() 56 | { 57 | Span longs = stackalloc ulong[2]; 58 | longs[0] = this.High; 59 | longs[1] = this.Low; 60 | var octets = MemoryMarshal.Cast(longs); 61 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER 62 | return new (octets); 63 | #else 64 | return new (octets.ToArray() ?? Array.Empty()); 65 | #endif 66 | } 67 | 68 | public override string ToString() 69 | => Ipv6Formatting.FormatIpv6Address(this.Low, this.High); 70 | 71 | public static bool TryParse(string? text, out int charsRead, out Ipv6Address result) 72 | { 73 | var spanWrapper = text.GetSpan(); 74 | return TryParse(spanWrapper, out charsRead, out result); 75 | } 76 | internal static bool TryParse(ReadOnlySpan text, out int charsRead, out Ipv6Address result) 77 | { 78 | if (Ipv6Parsing.TryParseIpv6Address(text, out charsRead, out var high, out var low)) 79 | { 80 | result = new Ipv6Address(low, high); 81 | return true; 82 | } 83 | result = default; 84 | return false; 85 | } 86 | 87 | internal static bool TryParse(ref ReadOnlySpan text, ref int charsRead, out Ipv6Address result) 88 | { 89 | if (!Ipv6Address.TryParse(text, out var length, out result)) 90 | return false; 91 | charsRead += length; 92 | text = text[length..]; 93 | return true; 94 | } 95 | 96 | 97 | 98 | public bool Equals(Ipv6Address other) => this.Low == other.Low && this.High == other.High; 99 | public override bool Equals(object? obj) => obj is Ipv6Address other && Equals(other); 100 | public override int GetHashCode() => HashCode.Combine(this.Low, this.High); 101 | 102 | public static bool operator ==(Ipv6Address left, Ipv6Address right) => left.Equals(right); 103 | public static bool operator !=(Ipv6Address left, Ipv6Address right) => !left.Equals(right); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv6/Formatting/Ipv6FormatInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetworkPrimitives.Ipv6 4 | { 5 | [ExcludeFromCodeCoverage("Internal")] 6 | internal readonly struct Ipv6FormatInfo : IEquatable 7 | { 8 | public bool Compress { get; } 9 | public bool CompressGroups { get; } 10 | public bool Hex { get; } 11 | public bool Upper { get; } 12 | 13 | public Ipv6FormatInfo(bool compress, bool compressGroups, bool hex, bool upper) 14 | { 15 | this.Compress = compress; 16 | this.CompressGroups = compressGroups; 17 | this.Hex = hex; 18 | this.Upper = upper; 19 | } 20 | 21 | public void Deconstruct(out bool compress, out bool compressGroups, out bool hex, out bool upper) 22 | { 23 | compress = this.Compress; 24 | compressGroups = this.CompressGroups; 25 | hex = this.Hex; 26 | upper = this.Upper; 27 | } 28 | 29 | public bool Equals(Ipv6FormatInfo other) => this.Compress == other.Compress && this.CompressGroups == other.CompressGroups && this.Hex == other.Hex && this.Upper == other.Upper; 30 | public override bool Equals(object? obj) => obj is Ipv6FormatInfo other && Equals(other); 31 | public override int GetHashCode() => HashCode.Combine(this.Compress, this.CompressGroups, this.Hex, this.Upper); 32 | public static bool operator ==(Ipv6FormatInfo left, Ipv6FormatInfo right) => left.Equals(right); 33 | public static bool operator !=(Ipv6FormatInfo left, Ipv6FormatInfo right) => !left.Equals(right); 34 | } 35 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv6/Formatting/Ipv6Parsing.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers.Binary; 5 | using System.Runtime.InteropServices; 6 | using NetworkPrimitives.Utilities; 7 | 8 | namespace NetworkPrimitives.Ipv6 9 | { 10 | [ExcludeFromCodeCoverage("Internal")] 11 | internal static class Ipv6Parsing 12 | { 13 | internal static bool TryParseIpv6Address(ReadOnlySpan text, out int charsRead, out ulong high, out ulong low) 14 | { 15 | high = default; 16 | low = default; 17 | charsRead = default; 18 | var doubleColonPos = text.IndexOf("::".GetSpan()); 19 | if (doubleColonPos == -1) 20 | return TryParseUnabbreviated(text, out charsRead, out high, out low); 21 | var highSpan = text[..doubleColonPos]; 22 | var lowSpan = text[(doubleColonPos + 2)..]; 23 | var highGroupCount = GetColonCount(highSpan) + 1; 24 | var lowGroupCount = GetColonCount(lowSpan) + 1; 25 | Span bitGroups = stackalloc ushort[8]; 26 | if (highGroupCount + lowGroupCount > 8) 27 | { 28 | return false; 29 | } 30 | var highGroups = bitGroups[..highGroupCount]; 31 | var lowGroups = bitGroups[^lowGroupCount..]; 32 | if (!TryFillBitGroups(highSpan, ref charsRead, highGroups)) 33 | { 34 | return false; 35 | } 36 | if (!TryFillBitGroups(lowSpan, ref charsRead, lowGroups)) 37 | { 38 | return false; 39 | } 40 | return Create(bitGroups, out high, out low); 41 | 42 | static bool TryFillBitGroups(ReadOnlySpan text, ref int charsRead, Span bitGroups) 43 | { 44 | while (bitGroups.Length > 0 && text.TryParseHexUshort(ref charsRead, out var bitGroup)) 45 | { 46 | bitGroups[0] = bitGroup; 47 | bitGroups = bitGroups[1..]; 48 | if (text.Length == 0 || text[0] != ':') 49 | break; 50 | text = text[1..]; 51 | ++charsRead; 52 | } 53 | return bitGroups.Length == 0; 54 | } 55 | static int GetColonCount(ReadOnlySpan text) 56 | { 57 | var ct = 0; 58 | for (var i = 0; i < text.Length; ++i) 59 | { 60 | if (text[i] == ':') 61 | ++ct; 62 | } 63 | return ct; 64 | } 65 | } 66 | 67 | 68 | internal static bool TryParseUnabbreviated(ReadOnlySpan text, out int charsRead, out ulong high, out ulong low) 69 | { 70 | charsRead = default; 71 | Span bitGroups = stackalloc ushort[8]; 72 | // Read the first bit groups 73 | var startLength = 0; 74 | while (text.TryParseHexUshort(ref charsRead, out var bitGroup)) 75 | { 76 | bitGroups[startLength++] = bitGroup; 77 | if (text.Length == 0 || text[0] != ':') 78 | break; 79 | text = text[1..]; 80 | ++charsRead; 81 | } 82 | if (startLength == 8) 83 | return Create(bitGroups, out high, out low); 84 | high = default; 85 | low = default; 86 | return false; 87 | } 88 | 89 | 90 | 91 | private static bool Create(Span bitGroups, out ulong high, out ulong low) 92 | { 93 | high = Convert(bitGroups[..4]); 94 | low = Convert(bitGroups[4..]); 95 | return true; 96 | static ulong Convert(Span ushorts) 97 | => BinaryPrimitives.ReadUInt64BigEndian( 98 | MemoryMarshal.Cast(ushorts) 99 | ); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv6/Subnet/Ipv6Cidr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NetworkPrimitives.Utilities; 3 | 4 | namespace NetworkPrimitives.Ipv6 5 | { 6 | public readonly struct Ipv6Cidr : ITryFormat, IEquatable 7 | { 8 | internal byte Value { get; } 9 | private Ipv6Cidr(byte value) => this.Value = value; 10 | int ITryFormat.MaximumLengthRequired => 3; 11 | 12 | public static implicit operator Ipv6Cidr(Ipv6SubnetMask mask) 13 | { 14 | var (low, high) = mask; 15 | var cidr = SubnetMaskLookups.GetCidr(high, low); 16 | return new Ipv6Cidr(cidr); 17 | } 18 | public bool TryFormat(Span destination, out int charsWritten) 19 | => Value.TryFormat(destination, out charsWritten); 20 | 21 | public override string ToString() => this.GetString(); 22 | public static explicit operator byte(Ipv6Cidr cidr) => cidr.Value; 23 | 24 | public bool Equals(Ipv6Cidr other) => this.Value == other.Value; 25 | public override bool Equals(object? obj) => obj is Ipv6Cidr other && Equals(other); 26 | public override int GetHashCode() => this.Value.GetHashCode(); 27 | public static bool operator ==(Ipv6Cidr left, Ipv6Cidr right) => left.Equals(right); 28 | public static bool operator !=(Ipv6Cidr left, Ipv6Cidr right) => !left.Equals(right); 29 | public static bool operator >(Ipv6Cidr left, Ipv6Cidr right) => left.Value > right.Value; 30 | public static bool operator <(Ipv6Cidr left, Ipv6Cidr right) => left.Value < right.Value; 31 | public static bool operator >=(Ipv6Cidr left, Ipv6Cidr right) => left.Value >= right.Value; 32 | public static bool operator <=(Ipv6Cidr left, Ipv6Cidr right) => left.Value <= right.Value; 33 | 34 | public static bool operator ==(Ipv6Cidr left, byte right) => left.Value == right; 35 | public static bool operator !=(Ipv6Cidr left, byte right) => left.Value != right; 36 | public static bool operator >(Ipv6Cidr left, byte right) => left.Value > right; 37 | public static bool operator <(Ipv6Cidr left, byte right) => left.Value < right; 38 | public static bool operator >=(Ipv6Cidr left, byte right) => left.Value >= right; 39 | public static bool operator <=(Ipv6Cidr left, byte right) => left.Value <= right; 40 | 41 | public static bool operator ==(byte left, Ipv6Cidr right) => left == right.Value; 42 | public static bool operator !=(byte left, Ipv6Cidr right) => left != right.Value; 43 | public static bool operator >(byte left, Ipv6Cidr right) => left > right.Value; 44 | public static bool operator <(byte left, Ipv6Cidr right) => left < right.Value; 45 | public static bool operator >=(byte left, Ipv6Cidr right) => left >= right.Value; 46 | public static bool operator <=(byte left, Ipv6Cidr right) => left <= right.Value; 47 | 48 | public static Ipv6Cidr Parse(string? value) 49 | => TryParse(value, out var result) ? result : throw new FormatException(); 50 | 51 | public static Ipv6Cidr Parse(int value) 52 | => Parse((byte)value); 53 | public static Ipv6Cidr Parse(byte value) 54 | => TryParse(value, out var result) ? result : throw new ArgumentOutOfRangeException(); 55 | public static bool TryParse(byte value, out Ipv6Cidr result) 56 | { 57 | if (value <= 128) 58 | { 59 | result = new (value); 60 | return true; 61 | } 62 | result = default; 63 | return false; 64 | } 65 | 66 | public static bool TryParse(string? text, out Ipv6Cidr result) 67 | { 68 | result = default; 69 | return text is not null && Ipv6Cidr.TryParse(text, out var charsRead, out result) && charsRead == text.Length; 70 | } 71 | 72 | public static bool TryParse(string? text, out int charsRead, out Ipv6Cidr result) 73 | { 74 | charsRead = default; 75 | var span = text.GetSpan(); 76 | return TryParse(ref span, ref charsRead, out result); 77 | } 78 | 79 | internal static bool TryParse(ref ReadOnlySpan text, ref int charsRead, out Ipv6Cidr result) 80 | { 81 | result = default; 82 | if (!text.TryParseByte(ref charsRead, out var value) || value > 128) 83 | return false; 84 | result = new (value); 85 | return true; 86 | } 87 | 88 | public static bool TryParse(ReadOnlySpan text, out int charsRead, out Ipv6Cidr result) 89 | { 90 | charsRead = default; 91 | return TryParse(ref text, ref charsRead, out result); 92 | } 93 | public static bool TryParse(ReadOnlySpan text, out Ipv6Cidr result) 94 | => TryParse(text, out var charsRead, out result) && charsRead == text.Length; 95 | 96 | public Ipv6SubnetMask ToSubnetMask() => this; 97 | } 98 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv6/Subnet/Ipv6Subnet.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using NetworkPrimitives.Ipv4; 6 | using NetworkPrimitives.Utilities; 7 | 8 | namespace NetworkPrimitives.Ipv6 9 | { 10 | public readonly struct Ipv6Subnet : IEquatable 11 | { 12 | public Ipv6Address NetworkAddress { get; } 13 | public Ipv6SubnetMask Mask { get; } 14 | public Ipv6Subnet(Ipv6Address address, Ipv6SubnetMask mask) 15 | { 16 | this.NetworkAddress = address & mask; 17 | this.Mask = mask; 18 | } 19 | 20 | public bool Equals(Ipv6Subnet other) => this.NetworkAddress.Equals(other.NetworkAddress) && this.Mask.Equals(other.Mask); 21 | public override bool Equals(object? obj) => obj is Ipv6Subnet other && Equals(other); 22 | public override int GetHashCode() => HashCode.Combine(this.NetworkAddress, this.Mask); 23 | public static bool operator ==(Ipv6Subnet left, Ipv6Subnet right) => left.Equals(right); 24 | public static bool operator !=(Ipv6Subnet left, Ipv6Subnet right) => !left.Equals(right); 25 | 26 | public static Ipv6Subnet Parse(ReadOnlySpan text) 27 | => TryParse(text, out var result) ? result : throw new FormatException(); 28 | 29 | public static Ipv6Subnet Parse(string? text) 30 | { 31 | text = text ?? throw new ArgumentNullException(nameof(text)); 32 | return Ipv6Subnet.TryParse(text, out var result) ? result : throw new FormatException(); 33 | } 34 | 35 | public static bool TryParse([NotNullWhen(true)] string? text, out Ipv6Subnet result) 36 | { 37 | result = default; 38 | return text is not null && TryParse(text.GetSpan(), out var length, out result) && length == text.Length; 39 | } 40 | 41 | public static bool TryParse(ReadOnlySpan text, out Ipv6Subnet result) 42 | => TryParse(text, out var length, out result) && length == text.Length; 43 | 44 | public static bool TryParse(ReadOnlySpan text, out int charsRead, out Ipv6Subnet result) 45 | { 46 | charsRead = default; 47 | return TryParse(ref text, ref charsRead, out result); 48 | } 49 | 50 | internal static bool TryParse(ref ReadOnlySpan text, ref int charsRead, out Ipv6Subnet result) 51 | { 52 | result = default; 53 | if (!Ipv6Address.TryParse(ref text, ref charsRead, out var address)) 54 | return false; 55 | return TryParseWithCidr(ref text, ref charsRead, address, out result) 56 | || TryParseWithMask(ref text, ref charsRead, address, out result); 57 | } 58 | 59 | public override string ToString() => $"{NetworkAddress.ToString()}/{Mask.ToCidr().ToString()}"; 60 | 61 | 62 | private static bool TryParseWithCidr(ref ReadOnlySpan text, ref int charsRead, Ipv6Address address, out Ipv6Subnet result) 63 | { 64 | result = default; 65 | var textCopy = text; 66 | var charsReadCopy = charsRead; 67 | if (!textCopy.TryReadCharacter(ref charsReadCopy, '/')) 68 | return false; 69 | if (!Ipv6Cidr.TryParse(ref textCopy, ref charsReadCopy, out var cidr)) 70 | return false; 71 | result = new (address, cidr); 72 | charsRead = charsReadCopy; 73 | text = textCopy; 74 | return true; 75 | } 76 | 77 | 78 | private static bool TryParseWithMask(ref ReadOnlySpan text, ref int charsRead, Ipv6Address address, out Ipv6Subnet result) 79 | { 80 | result = default; 81 | var textCopy = text; 82 | var charsReadCopy = charsRead; 83 | if (!textCopy.TryReadCharacter(ref charsReadCopy, ' ')) 84 | return false; 85 | if (!Ipv6SubnetMask.TryParse(ref textCopy, ref charsReadCopy, out var mask)) 86 | return false; 87 | result = new (address, mask); 88 | charsRead = charsReadCopy; 89 | text = textCopy; 90 | return true; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/Ipv6/Subnet/Ipv6SubnetMask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace NetworkPrimitives.Ipv6 5 | { 6 | public readonly struct Ipv6SubnetMask : IEquatable 7 | { 8 | internal readonly ulong Low; 9 | internal readonly ulong High; 10 | public Ipv6SubnetMask(ulong low, ulong high) 11 | { 12 | this.Low = low; 13 | this.High = high; 14 | } 15 | internal void Deconstruct(out ulong low, out ulong high) 16 | { 17 | low = this.Low; 18 | high = this.High; 19 | } 20 | 21 | public Ipv6Cidr ToCidr() => Ipv6Cidr.Parse(SubnetMaskLookups.GetCidr(this.High, this.Low)); 22 | public static implicit operator Ipv6SubnetMask(Ipv6Cidr cidr) 23 | { 24 | SubnetMaskLookups.TryConvertIpv6Address((byte)cidr, out var high, out var low); 25 | return new (low, high); 26 | } 27 | public static implicit operator Ipv6Cidr(Ipv6SubnetMask cidr) => cidr.ToCidr(); 28 | 29 | public static bool TryParse(Ipv6Address address, out Ipv6SubnetMask result) 30 | { 31 | var (low, high) = address; 32 | if (SubnetMaskLookups.IsValidSubnetMask(high, low)) 33 | { 34 | result = new (low, high); 35 | return true; 36 | } 37 | result = default; 38 | return false; 39 | } 40 | 41 | public static Ipv6SubnetMask Parse(string? value) 42 | => TryParse(value, out var result) ? result : throw new FormatException(); 43 | public static bool TryParse(IPAddress? ipAddress, out Ipv6SubnetMask result) 44 | { 45 | result = default; 46 | return Ipv6Address.TryParse(ipAddress, out var address) 47 | && TryParse(address, out result); 48 | } 49 | 50 | public static bool TryParse(ReadOnlySpan octets, out Ipv6SubnetMask result) 51 | { 52 | result = default; 53 | return Ipv6Address.TryParse(octets, out var address) 54 | && TryParse(address, out result); 55 | } 56 | 57 | public static bool TryParse(string? text, out Ipv6SubnetMask result) 58 | => TryParse(text, out _, out result); 59 | 60 | public IPAddress ToIpAddress() => Ipv6Address.Parse(this).ToIpAddress(); 61 | 62 | public override string ToString() 63 | => Ipv6Formatting.FormatIpv6Address(this.Low, this.High); 64 | 65 | public static bool TryParse(string? text, out int charsRead, out Ipv6SubnetMask result) 66 | { 67 | result = default; 68 | return Ipv6Address.TryParse(text, out charsRead, out var address) 69 | && TryParse(address, out result); 70 | } 71 | public static bool TryParse(ReadOnlySpan text, out int charsRead, out Ipv6SubnetMask result) 72 | { 73 | result = default; 74 | return Ipv6Address.TryParse(text, out charsRead, out var address) 75 | && TryParse(address, out result); 76 | } 77 | 78 | 79 | internal static bool TryParse(ref ReadOnlySpan text, ref int charsRead, out Ipv6SubnetMask result) 80 | { 81 | if (!TryParse(text, out var length, out result)) 82 | return false; 83 | text = text[length..]; 84 | charsRead += length; 85 | return true; 86 | } 87 | 88 | 89 | 90 | 91 | public bool Equals(Ipv6SubnetMask other) => this.Low == other.Low && this.High == other.High; 92 | public override bool Equals(object? obj) => obj is Ipv6SubnetMask other && Equals(other); 93 | public override int GetHashCode() => HashCode.Combine(this.Low, this.High); 94 | public static bool operator ==(Ipv6SubnetMask left, Ipv6SubnetMask right) => left.Equals(right); 95 | public static bool operator !=(Ipv6SubnetMask left, Ipv6SubnetMask right) => !left.Equals(right); 96 | 97 | } 98 | } -------------------------------------------------------------------------------- /src/NetworkPrimitives/NetworkPrimitives - Backup.Ipv4.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(NPVer_IPv4) 6 | NetworkPrimitives.Ipv4 7 | network; networking; subnet; ip-address; ipaddress; ip address; networking.primitives; 8 | Lightweight package for working with IPv4 addresses, ranges, and subnets. 9 | Release 10 | AnyCPU 11 | True 12 | bin\Release\NetworkPrimitives.Ipv4.xml 13 | 14 | 15 | 16 | 17 | <_Parameter1>NetworkPrimitives.Ipv4.Tests 18 | 19 | 20 | 21 | bin\Release\NetworkPrimitives.Ipv4.xml 22 | 23 | 24 | true 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/NetworkPrimitives/NetworkPrimitives.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NetworkPrimitives 5 | network; networking; subnet; ip-address; ipaddress; ip address; mac address; networking.primitives; network.primitives; primitives; 6 | Lightweight package for working with networking types such as IPv4 addresses, IPv6 addresses, ranges, and subnets. 7 | bin\Release\NetworkPrimitives.xml 8 | 9 | 10 | 11 | 12 | <_Parameter1>NetworkPrimitives.Tests 13 | 14 | 15 | 16 | 17 | 18 | <_Parameter1>$(GITHUB_SHA) 19 | <_Parameter2>$(TIMESTAMP) 20 | <_Parameter3>$(GITHUB_RUN_ID) 21 | <_Parameter4>$(GITHUB_REF_NAME) 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | 39 | 40 | 41 | Ipv4SubnetDictionary.cs 42 | 43 | 44 | Ipv4SubnetDictionary.cs 45 | 46 | 47 | Ipv4SubnetDictionary.cs 48 | 49 | 50 | Ipv4SubnetDictionary.cs 51 | 52 | 53 | Ipv4SubnetDictionary.cs 54 | 55 | 56 | Ipv4SubnetDictionary.cs 57 | 58 | 59 | Ipv4SubnetDictionary.cs 60 | 61 | 62 | Ipv4SubnetDictionary.cs 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/NetworkPrimitives/NetworkPrimitives.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | False 8 | True 9 | True 10 | True 11 | True 12 | True 13 | True 14 | True 15 | True 16 | True 17 | True 18 | True -------------------------------------------------------------------------------- /src/NetworkPrimitives/PublicAPI/net5.0/PublicAPI.Shipped.txt: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | -------------------------------------------------------------------------------- /src/NetworkPrimitives/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | obj\$(MSBuildProjectName)\ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | false 17 | enable 18 | NetworkPrimitives.Tests 19 | NetworkPrimitives.Tests 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | range-test-cases.json 28 | 29 | 30 | randomips.json 31 | 32 | 33 | EmbeddedResourceUtils.cs 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/EmbeddedResourceUtils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace NetworkPrimitives.Tests 7 | { 8 | public static class EmbeddedResourceUtils 9 | { 10 | public static string? ReadFromResourceFile(string endingFileName) 11 | { 12 | var assembly = Assembly.GetExecutingAssembly(); 13 | var manifestResourceNames = assembly.GetManifestResourceNames(); 14 | foreach (var resourceName in manifestResourceNames) 15 | { 16 | var fileNameFromResourceName = EmbeddedResourceUtils._GetFileNameFromResourceName(resourceName); 17 | if (fileNameFromResourceName is null || !fileNameFromResourceName.EndsWith(endingFileName)) 18 | { 19 | continue; 20 | } 21 | using var manifestResourceStream = assembly.GetManifestResourceStream(resourceName); 22 | if (manifestResourceStream is null) 23 | { 24 | continue; 25 | } 26 | using var streamReader = new StreamReader(manifestResourceStream); 27 | return streamReader.ReadToEnd(); 28 | } 29 | 30 | return null; 31 | } 32 | 33 | // https://stackoverflow.com/a/32176198/3764804 34 | private static string? _GetFileNameFromResourceName(string resourceName) 35 | { 36 | var stringBuilder = new StringBuilder(); 37 | var escapeDot = false; 38 | var haveExtension = false; 39 | 40 | for (var resourceNameIndex = resourceName.Length - 1; resourceNameIndex >= 0; resourceNameIndex--) 41 | { 42 | switch (resourceName[resourceNameIndex]) 43 | { 44 | case '_': 45 | escapeDot = true; 46 | continue; 47 | case '.': 48 | { 49 | if (!escapeDot) 50 | { 51 | if (haveExtension) 52 | { 53 | stringBuilder.Append('\\'); 54 | continue; 55 | } 56 | haveExtension = true; 57 | } 58 | break; 59 | } 60 | default: 61 | escapeDot = false; 62 | break; 63 | } 64 | 65 | stringBuilder.Append(resourceName[resourceNameIndex]); 66 | } 67 | 68 | var fileName = Path.GetDirectoryName(stringBuilder.ToString()); 69 | return fileName is null ? null : new string(fileName.Reverse().ToArray()); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /tests/GenerateTestCases.ps1: -------------------------------------------------------------------------------- 1 |  2 | $path = "C:\Users\mikec\RiderProjects\NetworkPrimitives\tests\randomips.csv" 3 | $out = "C:\Users\mikec\RiderProjects\NetworkPrimitives\tests\randomips.json" 4 | Import-Csv $path | Foreach-Object { 5 | [PSCustomObject]@{ 6 | 'IpString' = $_.'IP String' 7 | 'Ip' = $_.IP 8 | 'Cidr' = $_.Cidr 9 | 'Mask' = $_.'Mask (Hex)' 10 | 'MaskString' = $_.'Mask (IP)' 11 | 'TotalHosts' = $_.'# Num Hosts' 12 | 'UsableHosts' = $_.'# Usable' 13 | 'Network' = $_.'Network Address' 14 | 'FirstUsable' = $_.'First Usable' 15 | 'LastUsable' = $_.'Last Usable' 16 | 'Broadcast' = $_.Broadcast 17 | } 18 | } | ConvertTo-Json | Set-Content $out -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Common/EndianSwapTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using NUnit.Framework; 5 | using NetworkPrimitives; 6 | 7 | 8 | namespace NetworkPrimitives.Tests.Common 9 | { 10 | [TestFixture] 11 | public class EndianSwapTests 12 | { 13 | [Test] 14 | [TestCase((uint)0x00112233, (uint)0x33221100)] 15 | [TestCase(0xFF116600, (uint)0x006611FF)] 16 | [Category("numbers")] 17 | public void TestEndianSwap(uint input, uint expected) 18 | { 19 | Assert.AreEqual(expected, input.SwapEndian()); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Common/ListSpanTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using NetworkPrimitives.Utilities; 7 | using NUnit.Framework; 8 | 9 | namespace NetworkPrimitives.Tests.Common 10 | { 11 | [TestFixture] 12 | public class ListSpanTests 13 | { 14 | public readonly IReadOnlyList TestData = Enumerable.Range(0, 1000).ToList().AsReadOnly(); 15 | 16 | [Test] 17 | public void TestSliceWithLength( 18 | [Random(0, 1000, 5)] int start, 19 | [Random(0, 1000, 5)] int length 20 | ) 21 | { 22 | var span = new ReadOnlyListSpan(this.TestData); 23 | Assume.That(start + length < span.Length); 24 | Assert.DoesNotThrow(() => span = span.Slice(start, length)); 25 | var expected = this.TestData.Skip(start).Take(length).ToArray(); 26 | var actual = span.ToEnumerable().ToArray(); 27 | Assert.That(actual, Is.EquivalentTo(expected)); 28 | } 29 | 30 | [Test] 31 | public void TestSlice( 32 | [Random(0, 1000, 5)] int start 33 | ) 34 | { 35 | var span = new ReadOnlyListSpan(this.TestData); 36 | Assert.DoesNotThrow(() => span = span[start..]); 37 | var expected = this.TestData.Skip(start).ToArray(); 38 | var actual = span.ToEnumerable().ToArray(); 39 | Assert.That(actual, Is.EquivalentTo(expected)); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Cidr/ComparisonTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using NetworkPrimitives.Ipv4; 6 | using NUnit.Framework; 7 | 8 | namespace NetworkPrimitives.Tests.Ipv4.Cidr 9 | { 10 | public class ComparisonTests : Ipv4CidrTests 11 | { 12 | 13 | [Test] 14 | public void TestNullEquality() 15 | { 16 | Assert.That(Ipv4Cidr.TryParse(24, out var cidr)); 17 | Assert.That(cidr.CompareTo(null), Is.EqualTo(1)); 18 | Assert.That(cidr.Equals(null), Is.EqualTo(false)); 19 | } 20 | 21 | [Test] 22 | public void TestInvalidEquals() 23 | { 24 | object foo = "Foo"; 25 | Assert.That(Ipv4Cidr.TryParse(24, out var cidr)); 26 | Assert.Throws(() => _ = cidr.CompareTo(foo)); 27 | Assert.That(_ = cidr.Equals(foo), Is.EqualTo(false)); 28 | } 29 | 30 | [Test] 31 | [TestCase(null)] 32 | [TestCase("Foo")] 33 | [TestCase((ulong)24)] 34 | public void TestEquality(object? value) 35 | { 36 | var cidr = Ipv4Cidr.Parse(24); 37 | Assert.IsFalse(cidr.Equals(value)); 38 | } 39 | 40 | 41 | [Test] 42 | [TestCaseSource(nameof(TestCases))] 43 | [SuppressMessage("ReSharper", "EqualExpressionComparison")] 44 | public void TestEquality(Ipv4CidrTestCase testCase) 45 | { 46 | var (value, _, _, _) = testCase; 47 | object objValue = value; 48 | Assume.That(Ipv4Cidr.TryParse(value.ToString(), out var cidr), Is.True); 49 | Assert.Multiple(() => 50 | { 51 | Assert.IsTrue(cidr.Equals(objValue)); 52 | Assert.IsTrue(cidr.Equals(cidr)); 53 | Assert.IsTrue(cidr == value); 54 | Assert.IsFalse(cidr != value); 55 | Assert.IsTrue(value == cidr); 56 | Assert.IsFalse(value != cidr); 57 | #pragma warning disable CS1718 // Comparison made to same variable 58 | Assert.IsTrue(cidr == cidr); 59 | Assert.IsFalse(cidr != cidr); 60 | #pragma warning restore CS1718 // Comparison made to same variable 61 | Assert.AreEqual(value.GetHashCode(), cidr.GetHashCode()); 62 | }); 63 | } 64 | 65 | 66 | [Test] 67 | [TestCaseSource(nameof(TestCases))] 68 | [SuppressMessage("ReSharper", "EqualExpressionComparison")] 69 | public void TestOperators(Ipv4CidrTestCase testCase) 70 | { 71 | var (value, _, _, _) = testCase; 72 | Assume.That(Ipv4Cidr.TryParse(value.ToString(), out var cidr)); 73 | Assert.Multiple(() => 74 | { 75 | Assert.That(Ipv4Cidr.Parse(0) <= cidr); 76 | Assert.That(Ipv4Cidr.Parse(0).CompareTo(cidr) <= 0); 77 | 78 | Assert.That(cidr >= Ipv4Cidr.Parse(0)); 79 | Assert.That(cidr.CompareTo(Ipv4Cidr.Parse(0)) >= 0); 80 | 81 | Assert.That(Ipv4Cidr.Parse(32) >= cidr); 82 | Assert.That(Ipv4Cidr.Parse(32).CompareTo(cidr) >= 0); 83 | 84 | Assert.That(cidr <= Ipv4Cidr.Parse(32)); 85 | Assert.That(cidr.CompareTo(Ipv4Cidr.Parse(32)) <= 0); 86 | 87 | Assert.That(0 <= cidr); 88 | 89 | Assert.That(cidr >= 0); 90 | Assert.That(cidr.CompareTo(0) >= 0); 91 | 92 | Assert.That(32 >= cidr); 93 | 94 | Assert.That(cidr <= 32); 95 | Assert.That(cidr.CompareTo(32) <= 0); 96 | 97 | if (value > 0) 98 | { 99 | Assert.That(Ipv4Cidr.Parse(0).CompareTo(cidr) < 0); 100 | Assert.That(Ipv4Cidr.Parse(0) < cidr); 101 | 102 | Assert.That(cidr.CompareTo(Ipv4Cidr.Parse(0)) > 0); 103 | Assert.That(cidr > Ipv4Cidr.Parse(0)); 104 | 105 | Assert.That(0 < cidr); 106 | 107 | Assert.That(cidr.CompareTo(0) > 0); 108 | Assert.That(cidr > 0); 109 | } 110 | if (value < 32) 111 | { 112 | Assert.That(cidr.CompareTo(Ipv4Cidr.Parse(32)) < 0); 113 | Assert.That(cidr < Ipv4Cidr.Parse(32)); 114 | 115 | Assert.That(Ipv4Cidr.Parse(32).CompareTo(cidr) > 0); 116 | Assert.That(Ipv4Cidr.Parse(32) > cidr); 117 | 118 | Assert.That(cidr.CompareTo(32) < 0); 119 | Assert.That(cidr < 32); 120 | 121 | Assert.That(32 > cidr); 122 | } 123 | }); 124 | } 125 | 126 | 127 | 128 | [Test] 129 | [Pairwise] 130 | public void TestObjectComparisons( 131 | [ValueSource(nameof(TestCases))] Ipv4CidrTestCase a, 132 | [ValueSource(nameof(TestCases))] Ipv4CidrTestCase b 133 | ) 134 | { 135 | var (aByte, _, _, _) = a; 136 | var (bByte, _, _, bMaskInt) = b; 137 | Assert.That(Ipv4Cidr.TryParse(aByte, out var aCidr)); 138 | Assert.That(Ipv4Cidr.TryParse(bByte, out var bCidr)); 139 | Assert.That(Ipv4SubnetMask.TryParse(bMaskInt, out var bMask)); 140 | 141 | object bCidrAsObject = bCidr; 142 | object bMaskAsObject = bMask; 143 | object bByteAsObject = bByte; 144 | 145 | var expectedEqual = aByte.Equals(bByte); 146 | var expectedCompare = aByte.CompareTo(bByte); 147 | 148 | Assert.That(aCidr.Equals(bCidrAsObject), Is.EqualTo(expectedEqual)); 149 | Assert.That(aCidr.Equals(bMaskAsObject), Is.EqualTo(expectedEqual)); 150 | Assert.That(aCidr.Equals(bByteAsObject), Is.EqualTo(expectedEqual)); 151 | 152 | Assert.That(aCidr.CompareTo(bCidrAsObject), Is.EqualTo(expectedCompare)); 153 | Assert.That(aCidr.CompareTo(bMaskAsObject), Is.EqualTo(expectedCompare)); 154 | Assert.That(aCidr.CompareTo(bByteAsObject), Is.EqualTo(expectedCompare)); 155 | } 156 | 157 | 158 | } 159 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Cidr/Ipv4CidrTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Linq; 8 | using NetworkPrimitives.Ipv4; 9 | using NUnit.Framework; 10 | 11 | namespace NetworkPrimitives.Tests.Ipv4.Cidr 12 | { 13 | [TestFixture] 14 | public abstract class Ipv4CidrTests 15 | { 16 | public static IReadOnlyList InvalidCidrs = Enumerable.Range(0, 256).Where(x => x >= 33).Select(x => (byte)x).ToArray(); 17 | public static IReadOnlyList TestCases = new[] 18 | { 19 | new Ipv4CidrTestCase(32, 1, 1, 0xFFFFFFFF), 20 | new Ipv4CidrTestCase(31, 2, 2, 0xFFFFFFFE), 21 | new Ipv4CidrTestCase(30, 4, 2, 0xFFFFFFFC), 22 | new Ipv4CidrTestCase(29, 8, 6, 0xFFFFFFF8), 23 | new Ipv4CidrTestCase(28, 16, 14, 0xFFFFFFF0), 24 | new Ipv4CidrTestCase(27, 32, 30, 0xFFFFFFE0), 25 | new Ipv4CidrTestCase(26, 64, 62, 0xFFFFFFC0), 26 | new Ipv4CidrTestCase(25, 128, 126, 0xFFFFFF80), 27 | new Ipv4CidrTestCase(24, 256, 254, 0xFFFFFF00), 28 | new Ipv4CidrTestCase(23, 512, 510, 0xFFFFFE00), 29 | new Ipv4CidrTestCase(22, 1024, 1022, 0xFFFFFC00), 30 | new Ipv4CidrTestCase(21, 2048, 2046, 0xFFFFF800), 31 | new Ipv4CidrTestCase(20, 4096, 4094, 0xFFFFF000), 32 | new Ipv4CidrTestCase(19, 8192, 8190, 0xFFFFE000), 33 | new Ipv4CidrTestCase(18, 16384, 16382, 0xFFFFC000), 34 | new Ipv4CidrTestCase(17, 32768, 32766, 0xFFFF8000), 35 | new Ipv4CidrTestCase(16, 65536, 65534, 0xFFFF0000), 36 | new Ipv4CidrTestCase(15, 131072, 131070, 0xFFFE0000), 37 | new Ipv4CidrTestCase(14, 262144, 262142, 0xFFFC0000), 38 | new Ipv4CidrTestCase(13, 524288, 524286, 0xFFF80000), 39 | new Ipv4CidrTestCase(12, 1048576, 1048574, 0xFFF00000), 40 | new Ipv4CidrTestCase(11, 2097152, 2097150, 0xFFE00000), 41 | new Ipv4CidrTestCase(10, 4194304, 4194302, 0xFFC00000), 42 | new Ipv4CidrTestCase(9, 8388608, 8388606, 0xFF800000), 43 | new Ipv4CidrTestCase(8, 16777216, 16777214, 0xFF000000), 44 | new Ipv4CidrTestCase(7, 33554432, 33554430, 0xFE000000), 45 | new Ipv4CidrTestCase(6, 67108864, 67108862, 0xFC000000), 46 | new Ipv4CidrTestCase(5, 134217728, 134217726, 0xF8000000), 47 | new Ipv4CidrTestCase(4, 268435456, 268435454, 0xF0000000), 48 | new Ipv4CidrTestCase(3, 536870912, 536870910, 0xE0000000), 49 | new Ipv4CidrTestCase(2, 1073741824, 1073741822, 0xC0000000), 50 | new Ipv4CidrTestCase(1, 2147483648, 2147483646, 0x80000000), 51 | new Ipv4CidrTestCase(0, 4294967296, 4294967294, 0x00000000), 52 | }; 53 | } 54 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Cidr/ParsingTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using NetworkPrimitives.Ipv4; 5 | using NUnit.Framework; 6 | 7 | namespace NetworkPrimitives.Tests.Ipv4.Cidr 8 | { 9 | public class ParsingTests : Ipv4CidrTests 10 | { 11 | [Test] 12 | [TestCaseSource(nameof(TestCases))] 13 | public void TestParsing(Ipv4CidrTestCase testCase) 14 | { 15 | Assert.That(Ipv4Cidr.TryParse(testCase.Cidr.ToString(), out _), Is.True); 16 | } 17 | 18 | 19 | [Test] 20 | [TestCase(null)] 21 | [TestCase("")] 22 | [TestCase("foo")] 23 | [TestCase("12foo")] 24 | public void TestInvalidStrings(string? input) 25 | { 26 | Assert.Multiple(() => 27 | { 28 | Assert.Throws(() => Ipv4Cidr.Parse(input)); 29 | Assert.IsFalse(Ipv4Cidr.TryParse(input, out _)); 30 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER 31 | Assert.Throws(() => 32 | { 33 | ReadOnlySpan span = input; 34 | Ipv4Cidr.Parse(span); 35 | }); 36 | ReadOnlySpan span = input; 37 | Assert.IsFalse(Ipv4Cidr.TryParse(span, out _)); 38 | #endif 39 | }); 40 | } 41 | 42 | 43 | [Test] 44 | [TestCaseSource(nameof(InvalidCidrs))] 45 | public void TestInvalidBytes(byte cidr) 46 | { 47 | Assert.Multiple(() => 48 | { 49 | Assert.False(Ipv4Cidr.TryParse(cidr, out _)); 50 | Assert.False(Ipv4Cidr.TryParse(cidr.ToString(), out _)); 51 | Assert.Throws(() => Ipv4Cidr.Parse(cidr)); 52 | Assert.Throws(() => Ipv4Cidr.Parse(cidr.ToString())); 53 | 54 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER 55 | Assert.Throws(() => 56 | { 57 | ReadOnlySpan span = cidr.ToString(); 58 | Ipv4Cidr.Parse(span); 59 | }); 60 | Assert.False(Ipv4Cidr.TryParse(cidr.ToString().AsSpan(), out _)); 61 | #endif 62 | }); 63 | } 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Cidr/ValueTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using NetworkPrimitives.Ipv4; 5 | using NUnit.Framework; 6 | 7 | namespace NetworkPrimitives.Tests.Ipv4.Cidr 8 | { 9 | public class ValueTests : Ipv4CidrTests 10 | { 11 | [Test] 12 | [TestCaseSource(nameof(TestCases))] 13 | public void TestValues(Ipv4CidrTestCase testCase) 14 | { 15 | var (value, totalHosts, usableHosts, mask) = testCase; 16 | Assume.That(Ipv4Cidr.TryParse(value.ToString(), out var cidr), Is.True); 17 | Assert.Multiple(() => 18 | { 19 | Assert.AreEqual(value, (byte)cidr); 20 | Assert.AreEqual(totalHosts, cidr.TotalHosts); 21 | Assert.AreEqual(usableHosts, cidr.UsableHosts); 22 | Assert.AreEqual(value.ToString(), cidr.ToString()); 23 | Assert.AreEqual(mask, cidr.ToSubnetMask().Value); 24 | }); 25 | } 26 | 27 | 28 | 29 | [Test] 30 | [TestCaseSource(nameof(TestCases))] 31 | public void TestWildcardMasks(Ipv4CidrTestCase testCase) 32 | { 33 | var (value, _, _, maskValue) = testCase; 34 | Assume.That(Ipv4Cidr.TryParse(value.ToString(), out var cidr)); 35 | Assume.That(Ipv4SubnetMask.TryParse(maskValue, out var mask)); 36 | Assert.Multiple(() => 37 | { 38 | Assert.That(cidr.ToWildcardMask().Value, Is.EqualTo(~maskValue)); 39 | Assert.That(cidr.Equals(mask)); 40 | Assert.That(cidr.CompareTo(value), Is.EqualTo(0)); 41 | }); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Ipv4AddressTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using NetworkPrimitives.Ipv4; 9 | using NUnit.Framework; 10 | 11 | namespace NetworkPrimitives.Tests.Ipv4 12 | { 13 | [TestFixture] 14 | public class Ipv4AddressTests 15 | { 16 | 17 | public static IReadOnlyList TestCases { get; } 18 | = Ipv4TestCaseProvider.LoadTestCases("randomips.json"); 19 | 20 | 21 | [Test] 22 | [TestCaseSource(nameof(Ipv4AddressTests.TestCases))] 23 | public void TestSuccessfulParse(Ipv4TestCase testCase) 24 | { 25 | Assert.That(IPAddress.TryParse(testCase.IpString, out var netIp)); 26 | Assert.Multiple(() => 27 | { 28 | Assert.That(Ipv4Address.TryParse(testCase.IpString, out var address)); 29 | 30 | Assert.That(Ipv4Address.TryParse(netIp, out var address2)); 31 | Assert.That(address2, Is.EqualTo(address)); 32 | 33 | #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER 34 | Assert.That(Ipv4Address.TryParse((ReadOnlySpan)testCase.IpString, out address2)); 35 | Assert.That(address2, Is.EqualTo(address)); 36 | #endif 37 | 38 | Assert.That(address.Value, Is.EqualTo(testCase.Ip)); 39 | Assert.That(address.ToString(), Is.EqualTo(testCase.IpString)); 40 | }); 41 | } 42 | 43 | 44 | [Test] 45 | [TestCase("2001:db8:85a3::8a2e:370:7334")] 46 | public void TestFailedSystemNetParse(string input) 47 | { 48 | Assume.That(IPAddress.TryParse(input, out var netAddress)); 49 | 50 | Assert.Multiple(() => 51 | { 52 | Assert.Throws(() => _ = Ipv4Address.Parse(netAddress)); 53 | Assert.That(Ipv4Address.TryParse(netAddress, out _), Is.False); 54 | }); 55 | } 56 | 57 | 58 | [Test] 59 | [TestCaseSource(nameof(Ipv4AddressTests.TestCases))] 60 | public void TestEquality(Ipv4TestCase testCase) 61 | { 62 | Assert.That(Ipv4Address.TryParse(testCase.IpString, out var address)); 63 | Assert.That(Ipv4Address.TryParse(testCase.FirstUsable, out var _)); 64 | 65 | Assert.AreEqual(testCase.Ip.GetHashCode(), address.GetHashCode()); 66 | } 67 | 68 | 69 | [Test] 70 | [TestCase("38.221.101.187")] 71 | public void TestOctets2(string ipString) 72 | { 73 | Assert.That(Ipv4Address.TryParse(ipString, out var address)); 74 | Assert.Multiple(() => 75 | { 76 | var expectedBytes = GetExpectedBytes(ipString); 77 | Span span = stackalloc byte[4]; 78 | var actualOctetArray = address.GetBytes(); 79 | Assert.IsTrue(address.TryWriteBytes(span, out var bytesWritten)); 80 | Assert.AreEqual(4, bytesWritten); 81 | Assert.IsTrue(span.EqualToArray(actualOctetArray)); 82 | 83 | for (var i = -5; i < 10; ++i) 84 | { 85 | if (i is >= 0 and <= 3) 86 | { 87 | Assert.AreEqual(expectedBytes[i], span[i]); 88 | Assert.AreEqual(expectedBytes[i], address[i]); 89 | Assert.AreEqual(expectedBytes[i], address.GetOctet(i)); 90 | } 91 | else 92 | { 93 | Assert.Throws(() => _ = address[i]); 94 | Assert.Throws(() => _ = address.GetOctet(i)); 95 | } 96 | } 97 | }); 98 | 99 | 100 | static byte[] GetExpectedBytes(string text) 101 | => text.Split('.').Select(byte.Parse).ToArray(); 102 | } 103 | [Test] 104 | [TestCaseSource(nameof(Ipv4AddressTests.TestCases))] 105 | public void TestOctets(Ipv4TestCase testCase) 106 | { 107 | Assert.That(Ipv4Address.TryParse(testCase.IpString, out var address)); 108 | Assert.Multiple(() => 109 | { 110 | var expectedBytes = GetExpectedBytes(testCase.IpString); 111 | Span span = stackalloc byte[4]; 112 | var actualOctetArray = address.GetBytes(); 113 | Assert.IsTrue(address.TryWriteBytes(span, out var bytesWritten)); 114 | Assert.AreEqual(4, bytesWritten); 115 | Assert.IsTrue(span.EqualToArray(actualOctetArray)); 116 | 117 | for (var i = -5; i < 10; ++i) 118 | { 119 | if (i is >= 0 and <= 3) 120 | { 121 | Assert.AreEqual(expectedBytes[i], span[i]); 122 | Assert.AreEqual(expectedBytes[i], address[i]); 123 | Assert.AreEqual(expectedBytes[i], address.GetOctet(i)); 124 | } 125 | else 126 | { 127 | Assert.Throws(() => _ = address[i]); 128 | Assert.Throws(() => _ = address.GetOctet(i)); 129 | } 130 | } 131 | }); 132 | 133 | 134 | static byte[] GetExpectedBytes(string text) 135 | => text.Split('.').Select(byte.Parse).ToArray(); 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Ipv4MatchTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using NetworkPrimitives.Ipv4; 6 | using NUnit.Framework; 7 | 8 | namespace NetworkPrimitives.Tests.Ipv4 9 | { 10 | public record Ipv4WildcardMaskRangeTestCase(string Input); 11 | 12 | 13 | [TestFixture] 14 | public class Ipv4MatchTests 15 | { 16 | public static IReadOnlyList RangeTestCases { get; } = new[] 17 | { 18 | new Ipv4WildcardMaskRangeTestCase("10.0.0.0 0.0.0.5"), 19 | }; 20 | 21 | [Test] 22 | [TestCaseSource(nameof(RangeTestCases))] 23 | public void TestParse(Ipv4WildcardMaskRangeTestCase testCase) 24 | { 25 | Assert.That(Ipv4NetworkMatch.TryParse(testCase.Input, out _)); 26 | } 27 | 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Ipv4SubnetMaskTests.cs: -------------------------------------------------------------------------------- 1 | using NetworkPrimitives.Ipv4; 2 | using NUnit.Framework; 3 | 4 | namespace NetworkPrimitives.Tests.Ipv4 5 | { 6 | [TestFixture] 7 | public class Ipv4SubnetMaskTests 8 | { 9 | [Test] 10 | [TestCase("0.0.0.0")] 11 | [TestCase("128.0.0.0")] 12 | [TestCase("192.0.0.0")] 13 | [TestCase("224.0.0.0")] 14 | [TestCase("240.0.0.0")] 15 | [TestCase("248.0.0.0")] 16 | [TestCase("252.0.0.0")] 17 | [TestCase("254.0.0.0")] 18 | [TestCase("255.0.0.0")] 19 | [TestCase("255.128.0.0")] 20 | [TestCase("255.192.0.0")] 21 | [TestCase("255.224.0.0")] 22 | [TestCase("255.240.0.0")] 23 | [TestCase("255.248.0.0")] 24 | [TestCase("255.252.0.0")] 25 | [TestCase("255.254.0.0")] 26 | [TestCase("255.255.0.0")] 27 | [TestCase("255.255.128.0")] 28 | [TestCase("255.255.192.0")] 29 | [TestCase("255.255.224.0")] 30 | [TestCase("255.255.240.0")] 31 | [TestCase("255.255.248.0")] 32 | [TestCase("255.255.252.0")] 33 | [TestCase("255.255.254.0")] 34 | [TestCase("255.255.255.0")] 35 | [TestCase("255.255.255.128")] 36 | [TestCase("255.255.255.192")] 37 | [TestCase("255.255.255.224")] 38 | [TestCase("255.255.255.240")] 39 | [TestCase("255.255.255.248")] 40 | [TestCase("255.255.255.252")] 41 | [TestCase("255.255.255.254")] 42 | [TestCase("255.255.255.255")] 43 | public void TestParse(string input) 44 | { 45 | Assert.AreEqual(true, Ipv4SubnetMask.TryParse(input, out var mask)); 46 | Assert.AreEqual(input, mask.ToString()); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Ipv4TestCase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text.Json; 8 | using NetworkPrimitives.Ipv4; 9 | 10 | namespace NetworkPrimitives.Tests.Ipv4 11 | { 12 | public record Ipv4CidrTestCase(byte Cidr, ulong TotalHosts, uint UsableHosts, uint Mask); 13 | 14 | 15 | public record Ipv4TestCaseStrings( 16 | string IpString, 17 | string MaskString, 18 | 19 | string Ip, 20 | string Cidr, 21 | string Mask, 22 | string TotalHosts, 23 | string UsableHosts, 24 | string Network, 25 | string FirstUsable, 26 | string LastUsable, 27 | string Broadcast 28 | ); 29 | 30 | public static class Ipv4TestCaseProvider 31 | { 32 | public static IReadOnlyList LoadTestCases(string path) 33 | { 34 | var jsonString = EmbeddedResourceUtils.ReadFromResourceFile(path) ?? "[]"; 35 | var strings = JsonSerializer.Deserialize(jsonString) 36 | ?? Array.Empty(); 37 | return strings.Select(ParseTestCase).ToList(); 38 | } 39 | private static Ipv4TestCase ParseTestCase(Ipv4TestCaseStrings arg) 40 | { 41 | return new Ipv4TestCase( 42 | IpString: arg.IpString, 43 | MaskString: arg.MaskString, 44 | Ip: uint.Parse(arg.Ip, NumberStyles.HexNumber, CultureInfo.CurrentCulture), 45 | Cidr: byte.Parse(arg.Cidr), 46 | Mask: uint.Parse(arg.Mask, NumberStyles.HexNumber, CultureInfo.CurrentCulture), 47 | TotalHosts: ulong.Parse(arg.TotalHosts), 48 | UsableHosts: uint.Parse(arg.UsableHosts), 49 | Network: uint.Parse(arg.Network, NumberStyles.HexNumber, CultureInfo.CurrentCulture), 50 | FirstUsable: uint.Parse(arg.FirstUsable, NumberStyles.HexNumber, CultureInfo.CurrentCulture), 51 | LastUsable: uint.Parse(arg.LastUsable, NumberStyles.HexNumber, CultureInfo.CurrentCulture), 52 | Broadcast: uint.Parse(arg.Broadcast, NumberStyles.HexNumber, CultureInfo.CurrentCulture) 53 | ); 54 | } 55 | } 56 | 57 | public record Ipv4TestCase( 58 | string IpString, 59 | string MaskString, 60 | 61 | uint Ip, 62 | byte Cidr, 63 | uint Mask, 64 | ulong TotalHosts, 65 | uint UsableHosts, 66 | uint Network, 67 | uint FirstUsable, 68 | uint LastUsable, 69 | uint Broadcast 70 | ) 71 | { 72 | public string SubnetInput => $"{IpString}/{Cidr.ToString()}"; 73 | public string SubnetExpected => $"{new IPAddress(Network.SwapEndianIfLittleEndian())}/{Cidr.ToString()}"; 74 | } 75 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/Ipv4WildcardMaskTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using NetworkPrimitives.Ipv4; 6 | using NUnit.Framework; 7 | 8 | namespace NetworkPrimitives.Tests.Ipv4 9 | { 10 | 11 | 12 | [TestFixture] 13 | public class Ipv4WildcardMaskTests 14 | { 15 | 16 | 17 | 18 | [Test] 19 | [TestCase("0.0.0.255")] 20 | public void TestParse(string input) 21 | { 22 | Assert.Multiple(() => 23 | { 24 | Assert.That(Ipv4WildcardMask.TryParse(input, out var mask)); 25 | Assert.That(mask.ToString(), Is.EqualTo(input)); 26 | }); 27 | } 28 | 29 | [Test] 30 | [TestCase("0.0.0.255", (ulong)256)] 31 | [TestCase("0.0.0.235", (ulong)64)] 32 | [TestCase("0.0.0.15", (ulong)16)] 33 | public void TestCounts(string input, ulong hostCount) 34 | { 35 | Assume.That(Ipv4WildcardMask.TryParse(input, out var mask)); 36 | 37 | Assert.That(mask.HostCount, Is.EqualTo(hostCount)); 38 | } 39 | 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/RangeList/AddressListSpanTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using NetworkPrimitives.Ipv4; 5 | using NUnit.Framework; 6 | 7 | namespace NetworkPrimitives.Tests.Ipv4.RangeList 8 | { 9 | public class AddressListSpanTests : RangeListTests 10 | { 11 | [Test] 12 | [TestCaseSource(nameof(TestCases))] 13 | public void TestIndexer2(RangeListTestCase testCase) 14 | { 15 | var (input, _, expectedAddresses) = testCase; 16 | 17 | Assert.That(Ipv4AddressRangeList.TryParse(input, out var rangeList)); 18 | } 19 | [Test] 20 | [TestCaseSource(nameof(TestCases))] 21 | public void TestIndexer(RangeListTestCase testCase) 22 | { 23 | var (input, _, expectedAddresses) = testCase; 24 | 25 | Assert.That(Ipv4AddressRangeList.TryParse(input, out var rangeList)); 26 | Assert.That(rangeList, Is.Not.Null); 27 | rangeList = rangeList ?? throw new InvalidOperationException(); 28 | 29 | var addresses = rangeList.GetAllAddresses(); 30 | 31 | Assert.That(addresses.Length, Is.EqualTo(expectedAddresses.Count)); 32 | 33 | for (var i = 0; i < expectedAddresses.Count; ++i) 34 | { 35 | Assert.That(Ipv4Address.TryParse(expectedAddresses[i], out var expected)); 36 | Assert.That(addresses[i], Is.EqualTo(expected)); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/RangeList/Parsing.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using NetworkPrimitives.Ipv4; 5 | using NUnit.Framework; 6 | 7 | namespace NetworkPrimitives.Tests.Ipv4.RangeList 8 | { 9 | public class Parsing : RangeListTests 10 | { 11 | 12 | [Test] 13 | [TestCaseSource(nameof(TestCases))] 14 | public void TestParse(RangeListTestCase testCase) 15 | { 16 | var (input, _, _) = testCase; 17 | var span = input.AsSpan(); 18 | var length = span.Length; 19 | 20 | Assert.That(Ipv4AddressRangeList.TryParse(input, out var charsRead, out _)); 21 | Assert.That(charsRead, Is.EqualTo(length)); 22 | 23 | Assert.That(Ipv4AddressRangeList.TryParse(input, out _)); 24 | Assert.That(charsRead, Is.EqualTo(length)); 25 | 26 | Assert.That(Ipv4AddressRangeList.TryParse(span, out charsRead, out _)); 27 | Assert.That(charsRead, Is.EqualTo(length)); 28 | 29 | Assert.That(Ipv4AddressRangeList.TryParse(span, out _)); 30 | Assert.That(charsRead, Is.EqualTo(length)); 31 | 32 | Assert.DoesNotThrow(() => Ipv4AddressRangeList.Parse(input)); 33 | Assert.DoesNotThrow(() => Ipv4AddressRangeList.Parse(input.AsSpan())); 34 | } 35 | 36 | 37 | [Test] 38 | [TestCaseSource(nameof(TestCases))] 39 | public void TestExpectedRanges(RangeListTestCase testCase) 40 | { 41 | var (input, expectedRanges, _) = testCase; 42 | 43 | Assert.That(Ipv4AddressRangeList.TryParse(input, out var rangeList)); 44 | Assert.That(rangeList, Is.Not.Null); 45 | rangeList = rangeList ?? throw new InvalidOperationException(); 46 | Assert.That(rangeList.Count, Is.EqualTo(expectedRanges.Count)); 47 | 48 | for (var i = 0; i < expectedRanges.Count; ++i) 49 | { 50 | Assert.That(Ipv4AddressRange.TryParse(expectedRanges[i], out var expected)); 51 | Assert.That(expected == rangeList[i]); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/RangeList/RangeListTestCase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetworkPrimitives.Tests.Ipv4.RangeList 4 | { 5 | public record RangeListTestCase( 6 | string Input, 7 | IReadOnlyList ExpectedRanges, 8 | IReadOnlyList ExpectedIps 9 | ); 10 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv4/RangeList/RangeListTests.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.Json; 7 | using NetworkPrimitives.Tests; 8 | using NetworkPrimitives.Tests.Ipv4; 9 | using NUnit.Framework; 10 | 11 | namespace NetworkPrimitives.Tests.Ipv4.RangeList 12 | { 13 | [TestFixture] 14 | public abstract class RangeListTests 15 | { 16 | public static IReadOnlyList LoadTestCases(string path) 17 | { 18 | var jsonString = EmbeddedResourceUtils.ReadFromResourceFile(path) ?? "[]"; 19 | IReadOnlyList? ret = JsonSerializer.Deserialize(jsonString)?.ToList(); 20 | return ret ?? Array.Empty(); 21 | } 22 | 23 | public static IReadOnlyList TestCases { get; } 24 | = LoadTestCases("range-test-cases.json"); 25 | 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/Ipv6/Ipv6AddressTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using NetworkPrimitives.Ipv6; 3 | using NUnit.Framework; 4 | 5 | namespace NetworkPrimitives.Tests.Ipv6 6 | { 7 | [TestFixture] 8 | public class Ipv6AddressTests 9 | { 10 | [Test] 11 | [TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] 12 | public void TestParse(string input) 13 | { 14 | Assert.AreEqual(true, Ipv6Address.TryParse(input, out var _)); 15 | } 16 | [Test] 17 | [TestCase("2001:db8:85a3:::8a2e:370:7334")] 18 | public void TestParseFailed(string input) 19 | { 20 | Assert.AreEqual(false, Ipv6Address.TryParse(input, out var _)); 21 | } 22 | [Test] 23 | [TestCase("2001:db8:85a3::8a2e:370:7334")] 24 | public void TestToString(string input) 25 | { 26 | Ipv6Address.TryParse(input, out var address); 27 | var systemNetAddress = IPAddress.Parse(input); 28 | Assert.AreEqual(systemNetAddress.ToString(), address.ToString()); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/NetworkPrimitives.Tests.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | NetworkPrimitives.Tests 5 | NetworkPrimitives.Tests 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/NetworkPrimitives.Tests/NetworkPrimitives.Tests.Framework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net48 4 | NetworkPrimitives.Tests 5 | NetworkPrimitives.Tests 6 | 7 | 8 | 9 | all 10 | runtime; build; native; contentfiles; analyzers; buildtransitive 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/range-test-cases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Input": "10.0.0.0", 4 | "ExpectedRanges": [ 5 | "10.0.0.0/32" 6 | ], 7 | "ExpectedIps": [ 8 | "10.0.0.0" 9 | ] 10 | }, 11 | { 12 | "Input": "10.0.0.0-3", 13 | "ExpectedRanges": [ 14 | "10.0.0.0/30" 15 | ], 16 | "ExpectedIps": [ 17 | "10.0.0.0", 18 | "10.0.0.1", 19 | "10.0.0.2", 20 | "10.0.0.3" 21 | ] 22 | }, 23 | { 24 | "Input": "10.0.0.0-3\n10.0.1.0-3", 25 | "ExpectedRanges": [ 26 | "10.0.0.0/30", 27 | "10.0.1.0/30" 28 | ], 29 | "ExpectedIps": [ 30 | "10.0.0.0", 31 | "10.0.0.1", 32 | "10.0.0.2", 33 | "10.0.0.3", 34 | "10.0.1.0", 35 | "10.0.1.1", 36 | "10.0.1.2", 37 | "10.0.1.3" 38 | ] 39 | }, 40 | { 41 | "Input": "10.0.0.0/31\n10.1.0.126-129", 42 | "ExpectedRanges": [ 43 | "10.0.0.0/31", 44 | "10.1.0.126-129" 45 | ], 46 | "ExpectedIps": [ 47 | "10.0.0.0", 48 | "10.0.0.1", 49 | "10.1.0.126", 50 | "10.1.0.127", 51 | "10.1.0.128", 52 | "10.1.0.129" 53 | ] 54 | } 55 | ] --------------------------------------------------------------------------------