Skip to content

Smdn.Fundamental.PortNumber version 3.0.0

Compare
Choose a tag to compare
@smdn smdn released this 31 Mar 17:23
· 529 commits to main since this release
740fb69

Released package

Release notes

The full release notes are available at gist.

Change log

Change log in this release:

API changes

API changes in this release:
diff --git a/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net45.apilist.cs b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net45.apilist.cs
new file mode 100644
index 00000000..1d0f62f5
--- /dev/null
+++ b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net45.apilist.cs
@@ -0,0 +1,35 @@
+// Smdn.Fundamental.PortNumber.dll (Smdn.Fundamental.PortNumber-3.0.0)
+//   Name: Smdn.Fundamental.PortNumber
+//   AssemblyVersion: 3.0.0.0
+//   InformationalVersion: 3.0.0+6f2d0f65a01b169e05b6e4adc7526ce8bbb67e78
+//   TargetFramework: .NETFramework,Version=v4.5
+//   Configuration: Release
+//   Referenced assemblies:
+//     System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+//     System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+//     mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+#nullable enable annotations
+
+using System;
+using System.Collections.Generic;
+
+namespace Smdn.Net {
+  public static class PortNumberUtils {
+    public const int MaxIanaDynamicPort = 65535;
+    public const int MaxIanaSystemPort = 1023;
+    public const int MaxIanaUserPort = 49151;
+    public const int MinIanaDynamicPort = 49152;
+    public const int MinIanaSystemPort = 0;
+    public const int MinIanaUserPort = 1024;
+
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<int>? exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, int exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts() {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts(Predicate<int>? exceptPort) {}
+    public static bool TryFindAvailablePort(Predicate<int>? exceptPort, out int port) {}
+    public static bool TryFindAvailablePort(out int port) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (/~https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net472.apilist.cs b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net472.apilist.cs
new file mode 100644
index 00000000..6dbeff66
--- /dev/null
+++ b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net472.apilist.cs
@@ -0,0 +1,35 @@
+// Smdn.Fundamental.PortNumber.dll (Smdn.Fundamental.PortNumber-3.0.0)
+//   Name: Smdn.Fundamental.PortNumber
+//   AssemblyVersion: 3.0.0.0
+//   InformationalVersion: 3.0.0+6f2d0f65a01b169e05b6e4adc7526ce8bbb67e78
+//   TargetFramework: .NETFramework,Version=v4.7.2
+//   Configuration: Release
+//   Referenced assemblies:
+//     System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+//     System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+//     mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+#nullable enable annotations
+
+using System;
+using System.Collections.Generic;
+
+namespace Smdn.Net {
+  public static class PortNumberUtils {
+    public const int MaxIanaDynamicPort = 65535;
+    public const int MaxIanaSystemPort = 1023;
+    public const int MaxIanaUserPort = 49151;
+    public const int MinIanaDynamicPort = 49152;
+    public const int MinIanaSystemPort = 0;
+    public const int MinIanaUserPort = 1024;
+
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<int>? exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, int exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts() {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts(Predicate<int>? exceptPort) {}
+    public static bool TryFindAvailablePort(Predicate<int>? exceptPort, out int port) {}
+    public static bool TryFindAvailablePort(out int port) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (/~https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net6.0.apilist.cs b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net6.0.apilist.cs
new file mode 100644
index 00000000..6326ecc4
--- /dev/null
+++ b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-net6.0.apilist.cs
@@ -0,0 +1,37 @@
+// Smdn.Fundamental.PortNumber.dll (Smdn.Fundamental.PortNumber-3.0.0)
+//   Name: Smdn.Fundamental.PortNumber
+//   AssemblyVersion: 3.0.0.0
+//   InformationalVersion: 3.0.0+6f2d0f65a01b169e05b6e4adc7526ce8bbb67e78
+//   TargetFramework: .NETCoreApp,Version=v6.0
+//   Configuration: Release
+//   Referenced assemblies:
+//     System.Collections, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Linq, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Net.NetworkInformation, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Net.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+#nullable enable annotations
+
+using System;
+using System.Collections.Generic;
+
+namespace Smdn.Net {
+  public static class PortNumberUtils {
+    public const int MaxIanaDynamicPort = 65535;
+    public const int MaxIanaSystemPort = 1023;
+    public const int MaxIanaUserPort = 49151;
+    public const int MinIanaDynamicPort = 49152;
+    public const int MinIanaSystemPort = 0;
+    public const int MinIanaUserPort = 1024;
+
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<int>? exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, int exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts() {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts(Predicate<int>? exceptPort) {}
+    public static bool TryFindAvailablePort(Predicate<int>? exceptPort, out int port) {}
+    public static bool TryFindAvailablePort(out int port) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (/~https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-netstandard2.0.apilist.cs b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-netstandard2.0.apilist.cs
new file mode 100644
index 00000000..2b3be9ea
--- /dev/null
+++ b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-netstandard2.0.apilist.cs
@@ -0,0 +1,33 @@
+// Smdn.Fundamental.PortNumber.dll (Smdn.Fundamental.PortNumber-3.0.0)
+//   Name: Smdn.Fundamental.PortNumber
+//   AssemblyVersion: 3.0.0.0
+//   InformationalVersion: 3.0.0+6f2d0f65a01b169e05b6e4adc7526ce8bbb67e78
+//   TargetFramework: .NETStandard,Version=v2.0
+//   Configuration: Release
+//   Referenced assemblies:
+//     netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+#nullable enable annotations
+
+using System;
+using System.Collections.Generic;
+
+namespace Smdn.Net {
+  public static class PortNumberUtils {
+    public const int MaxIanaDynamicPort = 65535;
+    public const int MaxIanaSystemPort = 1023;
+    public const int MaxIanaUserPort = 49151;
+    public const int MinIanaDynamicPort = 49152;
+    public const int MinIanaSystemPort = 0;
+    public const int MinIanaUserPort = 1024;
+
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<int>? exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, int exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts() {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts(Predicate<int>? exceptPort) {}
+    public static bool TryFindAvailablePort(Predicate<int>? exceptPort, out int port) {}
+    public static bool TryFindAvailablePort(out int port) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (/~https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-netstandard2.1.apilist.cs
new file mode 100644
index 00000000..d2b8e324
--- /dev/null
+++ b/doc/api-list/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber-netstandard2.1.apilist.cs
@@ -0,0 +1,33 @@
+// Smdn.Fundamental.PortNumber.dll (Smdn.Fundamental.PortNumber-3.0.0)
+//   Name: Smdn.Fundamental.PortNumber
+//   AssemblyVersion: 3.0.0.0
+//   InformationalVersion: 3.0.0+6f2d0f65a01b169e05b6e4adc7526ce8bbb67e78
+//   TargetFramework: .NETStandard,Version=v2.1
+//   Configuration: Release
+//   Referenced assemblies:
+//     netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+#nullable enable annotations
+
+using System;
+using System.Collections.Generic;
+
+namespace Smdn.Net {
+  public static class PortNumberUtils {
+    public const int MaxIanaDynamicPort = 65535;
+    public const int MaxIanaSystemPort = 1023;
+    public const int MaxIanaUserPort = 49151;
+    public const int MinIanaDynamicPort = 49152;
+    public const int MinIanaSystemPort = 0;
+    public const int MinIanaUserPort = 1024;
+
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, Predicate<int>? exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static TService CreateServiceWithAvailablePort<TService>(Func<int, TService> createService, int exceptPort, Predicate<Exception> isPortInUseException) {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts() {}
+    public static IEnumerable<int> EnumerateIanaDynamicPorts(Predicate<int>? exceptPort) {}
+    public static bool TryFindAvailablePort(Predicate<int>? exceptPort, out int port) {}
+    public static bool TryFindAvailablePort(out int port) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (/~https://github.com/smdn/Smdn.Reflection.ReverseGenerating)

Full changes

Full changes in this release:
diff --git a/src/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber.csproj b/src/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber.csproj
new file mode 100644
index 00000000..e59860e1
--- /dev/null
+++ b/src/Smdn.Fundamental.PortNumber/Smdn.Fundamental.PortNumber.csproj
@@ -0,0 +1,23 @@
+<!--
+SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+SPDX-License-Identifier: MIT
+-->
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net6.0;net472;net45;netstandard2.1;netstandard2.0</TargetFrameworks>
+    <VersionPrefix>3.0.0</VersionPrefix>
+    <VersionSuffix></VersionSuffix>
+    <!-- <PackageValidationBaselineVersion>3.0.0</PackageValidationBaselineVersion> -->
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <PropertyGroup Label="assembly attributes">
+    <CopyrightYear>2023</CopyrightYear>
+  </PropertyGroup>
+
+  <PropertyGroup Label="package properties">
+    <PackageTags>utility;port-number</PackageTags>
+  </PropertyGroup>
+
+</Project>
diff --git a/src/Smdn.Fundamental.PortNumber/Smdn.Net/PortNumberUtils.cs b/src/Smdn.Fundamental.PortNumber/Smdn.Net/PortNumberUtils.cs
new file mode 100644
index 00000000..63d7cabf
--- /dev/null
+++ b/src/Smdn.Fundamental.PortNumber/Smdn.Net/PortNumberUtils.cs
@@ -0,0 +1,155 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#if NET472_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_0_OR_GREATER || NET5_0_OR_GREATER
+#define SYSTEM_LINQ_ENUMERABLE_TOHASHSET
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.NetworkInformation;
+
+namespace Smdn.Net;
+
+public static class PortNumberUtils {
+  // IANA suggested range for dynamic or private ports
+  // ref: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
+  public const int MinIanaSystemPort = 0;
+  public const int MaxIanaSystemPort = 1023;
+  public const int MinIanaUserPort = 1024;
+  public const int MaxIanaUserPort = 49151;
+  public const int MinIanaDynamicPort = 49152;
+  public const int MaxIanaDynamicPort = 65535;
+
+  private static readonly Lazy<bool> isGetActiveTcpListenersAvailable = new(
+    valueFactory: () => {
+      try {
+        IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
+
+        return true;
+      }
+      catch {
+        return false;
+      }
+    },
+    isThreadSafe: true
+  );
+
+  public static TService CreateServiceWithAvailablePort<TService>(
+    Func<int, TService> createService,
+    Predicate<Exception> isPortInUseException
+  )
+    => CreateServiceWithAvailablePort(
+      createService: createService,
+      exceptPort: null,
+      isPortInUseException: isPortInUseException
+    );
+
+  public static TService CreateServiceWithAvailablePort<TService>(
+    Func<int, TService> createService,
+    int exceptPort,
+    Predicate<Exception> isPortInUseException
+  )
+    => CreateServiceWithAvailablePort(
+      createService: createService,
+      exceptPort: p => p == exceptPort,
+      isPortInUseException: isPortInUseException
+    );
+
+  public static TService CreateServiceWithAvailablePort<TService>(
+    Func<int, TService> createService,
+    Predicate<int>? exceptPort,
+    Predicate<Exception> isPortInUseException
+  )
+  {
+    if (createService is null)
+      throw new ArgumentNullException(nameof(createService));
+    if (isPortInUseException is null)
+      throw new ArgumentNullException(nameof(isPortInUseException));
+
+    if (isGetActiveTcpListenersAvailable.Value) {
+      if (TryFindAvailablePort(exceptPort, out var unusedPort)) {
+        try {
+          return createService(unusedPort);
+        }
+        catch (Exception ex) when (isPortInUseException(ex)) {
+          // ignore and continue
+        }
+      }
+    }
+
+    foreach (var dynamicPort in EnumerateIanaDynamicPorts(exceptPort)) {
+      try {
+        return createService(dynamicPort);
+      }
+      catch (Exception ex) when (isPortInUseException(ex)) {
+        continue; // ignore and continue
+      }
+    }
+
+    throw new InvalidOperationException("could not find any available port");
+  }
+
+  public static IEnumerable<int> EnumerateIanaDynamicPorts()
+    => EnumerateIanaDynamicPorts(exceptPort: null);
+
+  public static IEnumerable<int> EnumerateIanaDynamicPorts(
+    Predicate<int>? exceptPort
+  )
+  {
+    var dynamicPorts = Enumerable.Range(
+      start: MinIanaDynamicPort,
+      count: MaxIanaDynamicPort - MinIanaDynamicPort + 1
+    );
+
+    if (exceptPort is not null)
+      dynamicPorts = dynamicPorts.Where(port => !exceptPort(port));
+
+    return dynamicPorts;
+  }
+
+  // ref: https://stackoverflow.com/questions/223063/how-can-i-create-an-httplistener-class-on-a-random-port-in-c
+  public static bool TryFindAvailablePort(
+    out int port
+  )
+    => TryFindAvailablePort(
+      exceptPort: null,
+      port: out port
+    );
+
+  public static bool TryFindAvailablePort(
+    Predicate<int>? exceptPort,
+    out int port
+  )
+  {
+    port = default;
+
+    if (!isGetActiveTcpListenersAvailable.Value)
+      return false;
+
+    var activeListeners = IPGlobalProperties
+      .GetIPGlobalProperties()
+      .GetActiveTcpListeners();
+#if SYSTEM_LINQ_ENUMERABLE_TOHASHSET
+    var listeningPorts = activeListeners
+      .Select(static endPoint => endPoint.Port)
+      .ToHashSet();
+#else
+    var listeningPorts = new HashSet<int>(
+      activeListeners.Select(static endPoint => endPoint.Port)
+    );
+#endif
+
+    for (var p = MinIanaDynamicPort; p <= MaxIanaDynamicPort; p++) {
+      if (exceptPort is not null && exceptPort(p))
+        continue;
+
+      if (!listeningPorts.Contains(p)) {
+        port = p;
+        return true;
+      }
+    }
+
+    return false;
+  }
+}

Notes

Full Changelog: releases/Smdn.MSBuild.ProjectAssets.Library-1.4.7...releases/Smdn.Fundamental.PortNumber-3.0.0