Skip to content

Random Port Generator — Kotlin 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 Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.

// random-port-generator — Kotlin 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). An
// injectable RNG makes generation deterministic (the TS test surface);
// invalid custom ranges throw IllegalArgumentException, mirroring the TS
// throw. The default RNG is the JDK SecureRandom — a CSPRNG standing in for
// the TS crypto.getRandomValues default.
import java.security.SecureRandom
import kotlin.math.floor
import kotlin.math.max
import kotlin.math.min

/** Named ranges — `Range` in the Go twin; REGISTERED is the zero-value default. */
enum class PortRange(val lo: Int, val hi: Int) {
    ANY(1, 65535),
    REGISTERED(1024, 49151), // the TS default (registered ports)
    EPHEMERAL(49152, 65535), // dynamic/client ports
    CUSTOM(-1, -1), // bounds come from min/max
}

/** PortOptions in the TS lib (count/unique cover randomPorts). */
data class PortOptions(
    val range: PortRange = PortRange.REGISTERED,
    val min: Int? = null, // custom-range lower bound
    val max: Int? = null, // custom-range upper bound
    val count: Int = 1,
    val unique: Boolean = false,
    val rng: (() -> Double)? = null, // injectable random in [0, 1)
)

private val SECURE = SecureRandom()

/** defaultRng() in the TS lib: a CSPRNG draw in [0, 1). */
private fun defaultRng(): Double = SECURE.nextDouble()

/** resolveRange() in the TS lib. */
fun resolveRange(opts: PortOptions): Pair<Int, Int> = when (opts.range) {
    PortRange.CUSTOM -> (opts.min ?: 1) to (opts.max ?: 65535)
    else -> opts.range.lo to opts.range.hi
}

/** assertRange() in the TS lib — the TS Error becomes an exception. */
private fun assertRange(lo: Int, hi: Int) {
    if (lo < 0 || lo > 65535 || hi > 65535 || lo > hi) {
        throw IllegalArgumentException("Invalid port range $lo-$hi")
    }
}

/** randomPort() in the TS lib — Port() in the Go twin. */
fun randomPort(opts: PortOptions = PortOptions()): Int {
    val rng = opts.rng ?: ::defaultRng
    val (lo, hi) = resolveRange(opts)
    assertRange(lo, hi)
    return lo + floor(rng() * (hi - lo + 1)).toInt()
}

/** randomPorts() in the TS lib — Ports() in the Go twin. */
fun randomPorts(opts: PortOptions = PortOptions()): List<Int> {
    val count = max(1, opts.count)
    val (lo, hi) = resolveRange(opts)
    assertRange(lo, hi)
    if (!opts.unique) return List(count) { randomPort(opts) }

    // Fisher-Yates partial shuffle over the range to pick n unique ports.
    val capacity = hi - lo + 1
    val n = min(count, capacity)
    val rng = opts.rng ?: ::defaultRng
    val pool = IntArray(capacity) { lo + it }
    for (i in 0 until n) {
        val j = i + floor(rng() * (capacity - i)).toInt()
        val t = pool[i]
        pool[i] = pool[j]
        pool[j] = t
    }
    return pool.slice(0 until n)
}

fun main() {
    // Vectors from src/lib/random-port.test.ts — a fixed rng pins every draw.
    val zero = { 0.0 }
    println("registered+0 = ${randomPort(PortOptions(rng = zero))}") // 1024
    println("any+0 = ${randomPort(PortOptions(PortRange.ANY, rng = zero))}") // 1
    println("ephemeral+0 = ${randomPort(PortOptions(PortRange.EPHEMERAL, rng = zero))}") // 49152
    println(
        "custom 8000-8000 @0.9 = " +
            randomPort(PortOptions(PortRange.CUSTOM, min = 8000, max = 8000, rng = { 0.9 })),
    ) // 8000
    println("resolveRange(custom defaults) = ${resolveRange(PortOptions(PortRange.CUSTOM))}") // (1, 65535)
    println("count=5 @0 = ${randomPorts(PortOptions(count = 5, rng = zero))}") // [1024 x5]
    val unique = PortOptions(
        count = 3, unique = true, range = PortRange.CUSTOM, min = 1, max = 10, rng = { 0.5 },
    )
    println("unique 3 in 1..10 @0.5 = ${randomPorts(unique)}") // [6, 1, 7]
    println("unique 5 in 1..3 @0.5 = ${randomPorts(unique.copy(count = 5, min = 1, max = 3))}") // capped at capacity 3

    val p = randomPort() // default CSPRNG, registered range
    println("default rng registered = $p (in 1024..49151)")
    listOf(
        PortOptions(PortRange.CUSTOM, min = 70000) to "min above 65535",
        PortOptions(PortRange.CUSTOM, min = 100, max = 50) to "min above max",
    ).forEach { (bad, why) ->
        runCatching { randomPort(bad) }.onFailure { println("invalid: ${it.message} ($why)") }
    }
}

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 →