Random Port Generator — Zig 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 Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.
// random-port-generator — Zig 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 is std.crypto.random (a CSPRNG standing in for the TS
// crypto.getRandomValues default); invalid custom ranges return
// error.InvalidPortRange instead of throwing.
const std = @import("std");
/// Named ranges — `Range` in the Go twin; registered is the zero-value
/// default, matching the Go twin and the TS default (registered ports).
pub const PortRange = enum { registered, any, ephemeral, custom };
/// PortOptions in the TS lib (count/unique cover randomPorts).
pub const PortOptions = struct {
range: PortRange = .registered,
min: i32 = 1, // custom-range lower bound (full-range default, like min ?? 1 in TS)
max: i32 = 65535, // custom-range upper bound (max ?? 65535 in TS)
count: usize = 1,
unique: bool = false,
rng: ?*const fn () f64 = null, // injectable random in [0, 1)
};
pub const PortError = error{ InvalidPortRange, OutOfMemory };
/// defaultRng() in the TS lib: a CSPRNG draw in [0, 1).
fn defaultRng() f64 {
return std.crypto.random.float(f64);
}
/// resolveRange() in the TS lib.
pub fn resolveRange(opts: PortOptions) struct { lo: i32, hi: i32 } {
return switch (opts.range) {
.custom => .{ .lo = opts.min, .hi = opts.max },
.any => .{ .lo = 1, .hi = 65535 },
.ephemeral => .{ .lo = 49152, .hi = 65535 },
.registered => .{ .lo = 1024, .hi = 49151 },
};
}
/// assertRange() in the TS lib — the TS throw becomes error.InvalidPortRange.
fn assertRange(lo: i32, hi: i32) PortError!void {
if (lo < 0 or lo > 65535 or hi > 65535 or lo > hi) return error.InvalidPortRange;
}
/// randomPort() in the TS lib — Port() in the Go twin.
pub fn randomPort(opts: PortOptions) PortError!i32 {
const rng = opts.rng orelse defaultRng;
const r = resolveRange(opts);
try assertRange(r.lo, r.hi);
const span: f64 = @floatFromInt(r.hi - r.lo + 1);
return r.lo + @as(i32, @intFromFloat(@floor(rng() * span)));
}
/// randomPorts() in the TS lib — Ports() in the Go twin. Caller frees.
pub fn randomPorts(allocator: std.mem.Allocator, opts: PortOptions) PortError![]i32 {
const count = @max(1, opts.count);
const r = resolveRange(opts);
try assertRange(r.lo, r.hi);
if (!opts.unique) {
const out = try allocator.alloc(i32, count);
for (out) |*p| p.* = try randomPort(opts);
return out;
}
// Fisher-Yates partial shuffle over the range to pick n unique ports.
const capacity: usize = @intCast(r.hi - r.lo + 1);
const n = @min(count, capacity);
const rng = opts.rng orelse defaultRng;
const pool = try allocator.alloc(i32, capacity);
defer allocator.free(pool);
for (pool, 0..) |*p, i| p.* = r.lo + @as(i32, @intCast(i));
var i: usize = 0;
while (i < n) : (i += 1) {
const span: f64 = @floatFromInt(capacity - i);
const j = i + @as(usize, @intFromFloat(@floor(rng() * span)));
std.mem.swap(i32, &pool[i], &pool[j]);
}
const out = try allocator.alloc(i32, n);
@memcpy(out, pool[0..n]);
return out;
}
fn zeroRng() f64 {
return 0.0;
}
fn halfRng() f64 {
return 0.5;
}
fn pointNineRng() f64 {
return 0.9;
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
// Vectors from src/lib/random-port.test.ts — a fixed rng pins every draw.
try stdout.print("registered+0 = {d}\n", .{try randomPort(.{ .rng = zeroRng })}); // 1024
try stdout.print("any+0 = {d}\n", .{try randomPort(.{ .range = .any, .rng = zeroRng })}); // 1
try stdout.print("ephemeral+0 = {d}\n", .{try randomPort(.{ .range = .ephemeral, .rng = zeroRng })}); // 49152
try stdout.print("custom 8000-8000 @0.9 = {d}\n", .{try randomPort(.{
.range = .custom,
.min = 8000,
.max = 8000,
.rng = pointNineRng,
})}); // 8000
const d = resolveRange(.{ .range = .custom });
try stdout.print("resolveRange(custom defaults) = {d}..{d}\n", .{ d.lo, d.hi }); // 1..65535
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const a = arena.allocator();
const five = try randomPorts(a, .{ .count = 5, .rng = zeroRng });
try stdout.print("count=5 @0 = {any}\n", .{five}); // 1024 x5
const uniq = try randomPorts(a, .{
.count = 3,
.unique = true,
.range = .custom,
.min = 1,
.max = 10,
.rng = halfRng,
});
try stdout.print("unique 3 in 1..10 @0.5 = {any}\n", .{uniq}); // 6, 1, 7
const capped = try randomPorts(a, .{
.count = 5,
.unique = true,
.range = .custom,
.min = 1,
.max = 3,
.rng = halfRng,
});
try stdout.print("unique 5 in 1..3 @0.5 = {any}\n", .{capped}); // capped at capacity 3
const p = try randomPort(.{}); // default CSPRNG, registered range
try stdout.print("default rng registered = {d} (in 1024..49151)\n", .{p});
// Invalid custom ranges — the TS throw, as error values.
if (randomPort(.{ .range = .custom, .min = 70000 })) |got| { // min above 65535
try stdout.print("unexpected {d}\n", .{got});
} else |err| try stdout.print("invalid: {s}\n", .{@errorName(err)});
if (randomPort(.{ .range = .custom, .min = 100, .max = 50 })) |got| { // min above max
try stdout.print("unexpected {d}\n", .{got});
} else |err| try stdout.print("invalid: {s}\n", .{@errorName(err)});
}
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 →