Skip to content

Random Port Generator — C# source

Generate one or many random TCP/UDP port numbers across registered, ephemeral, or the full range - optionally unique. Runs entirely in your browser with crypto-grade randomness.

This is the C# implementation — the same logic the interactive tool runs, in a shareable, citable form.

// random-port-generator — C# port: random/dynamic port picker.
//
// Display port of the CosmoDev Random Port Generator tool — same contract as
// cli/random-port-generator/random-port-generator.go (the live Go twin) and
// src/lib/random-port.ts (the canonical TypeScript implementation). The
// default rng draws 32 bits from RandomNumberGenerator (a CSPRNG) scaled
// into [0, 1) — the shape of the TS crypto.getRandomValues default; invalid
// custom ranges throw ArgumentException, mirroring the TS throw.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;

namespace RandomPortGenerator;

enum PortRange { Any, Registered, Ephemeral, Custom }

/// PortOptions in the TS lib (count/unique cover RandomPorts).
class PortOptions
{
    public PortRange Range = PortRange.Registered; // the TS default (registered ports)
    public int? Min;  // custom-range lower bound
    public int? Max;  // custom-range upper bound
    public int Count = 1;
    public bool Unique;
    public Func<double>? Rng; // injectable random in [0, 1)
}

static class RandomPort
{
    static readonly Dictionary<PortRange, (int Lo, int Hi)> Ranges = new()
    {
        [PortRange.Any] = (1, 65_535),
        [PortRange.Registered] = (1024, 49_151),
        [PortRange.Ephemeral] = (49_152, 65_535),
    };

    /// defaultRng() in the TS lib: 32 CSPRNG bits / 2^32, like the TS
    /// crypto.getRandomValues(new Uint32Array(1))[0] / 2**32.
    static double DefaultRng()
    {
        Span<byte> buf = stackalloc byte[4];
        RandomNumberGenerator.Fill(buf);
        return BitConverter.ToUInt32(buf) / 4_294_967_296.0;
    }

    /// resolveRange() in the TS lib.
    public static (int Lo, int Hi) ResolveRange(PortOptions opts) =>
        opts.Range == PortRange.Custom
            ? (opts.Min ?? 1, opts.Max ?? 65_535)
            : Ranges[opts.Range];

    /// assertRange() in the TS lib — the TS Error becomes an exception.
    static void AssertRange(int lo, int hi)
    {
        if (lo < 0 || lo > 65_535 || hi > 65_535 || lo > hi)
            throw new ArgumentException($"Invalid port range {lo}-{hi}");
    }

    /// randomPort() in the TS lib — Port() in the Go twin.
    public static int Port(PortOptions? opts = null)
    {
        opts ??= new PortOptions();
        var rng = opts.Rng ?? DefaultRng;
        var (lo, hi) = ResolveRange(opts);
        AssertRange(lo, hi);
        return lo + (int)Math.Floor(rng() * (hi - lo + 1));
    }

    /// randomPorts() in the TS lib — Ports() in the Go twin.
    public static List<int> Ports(PortOptions? opts = null)
    {
        opts ??= new PortOptions();
        var count = Math.Max(1, opts.Count);
        var (lo, hi) = ResolveRange(opts);
        AssertRange(lo, hi);
        if (!opts.Unique)
            return Enumerable.Repeat(0, count).Select(_ => Port(opts)).ToList();

        // Fisher-Yates partial shuffle over the range to pick n unique ports.
        var capacity = hi - lo + 1;
        var n = Math.Min(count, capacity);
        var rng = opts.Rng ?? DefaultRng;
        var pool = new List<int>(capacity);
        for (var p = lo; p <= hi; p++) pool.Add(p);
        for (var i = 0; i < n; i++)
        {
            var j = i + (int)Math.Floor(rng() * (capacity - i));
            (pool[i], pool[j]) = (pool[j], pool[i]);
        }
        return pool.GetRange(0, n);
    }
}

public static class Program
{
    public static void Main()
    {
        // Vectors from src/lib/random-port.test.ts — a fixed rng pins every draw.
        double Zero() => 0.0;
        Console.WriteLine($"registered+0 = {RandomPort.Port(new PortOptions { Rng = Zero })}"); // 1024
        Console.WriteLine($"any+0 = {RandomPort.Port(new PortOptions { Range = PortRange.Any, Rng = Zero })}"); // 1
        Console.WriteLine($"ephemeral+0 = {RandomPort.Port(new PortOptions { Range = PortRange.Ephemeral, Rng = Zero })}"); // 49152
        Console.WriteLine(
            $"custom 8000-8000 @0.9 = {RandomPort.Port(new PortOptions { Range = PortRange.Custom, Min = 8000, Max = 8000, Rng = () => 0.9 })}"); // 8000
        var (lo, hi) = RandomPort.ResolveRange(new PortOptions { Range = PortRange.Custom });
        Console.WriteLine($"resolveRange(custom defaults) = {lo}..{hi}"); // 1..65535
        Console.WriteLine($"count=5 @0 = [{string.Join(", ", RandomPort.Ports(new PortOptions { Count = 5, Rng = Zero }))}]"); // 1024 x5
        Console.WriteLine(
            $"unique 3 in 1..10 @0.5 = [{string.Join(", ", RandomPort.Ports(new PortOptions { Count = 3, Unique = true, Range = PortRange.Custom, Min = 1, Max = 10, Rng = () => 0.5 }))}]"); // 6, 1, 7
        Console.WriteLine(
            $"unique 5 in 1..3 @0.5 = [{string.Join(", ", RandomPort.Ports(new PortOptions { Count = 5, Unique = true, Range = PortRange.Custom, Min = 1, Max = 3, Rng = () => 0.5 }))}]"); // capped at capacity 3
        var p = RandomPort.Port(); // default CSPRNG, registered range
        Console.WriteLine($"default rng registered = {p} (in 1024..49151)");
        foreach (var bad in new[] {
                     new PortOptions { Range = PortRange.Custom, Min = 70000 }, // min above 65535
                     new PortOptions { Range = PortRange.Custom, Min = 100, Max = 50 }, // min above max
                 })
        {
            try { RandomPort.Port(bad); }
            catch (ArgumentException e) { Console.WriteLine($"invalid: {e.Message}"); }
        }
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →