Skip to content

Subnet / CIDR Calculator — Kotlin source

Compute IPv4 subnet details from a CIDR block - network, broadcast, host range, netmask, wildcard, host count, IP class, and private-range detection. Split a block into smaller subnets, all in your browser.

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

// subnet-cidr — Kotlin port: IPv4 subnet/CIDR math (dotted-quad ⇄ UInt, subnet details, CIDR splitting). Language: Kotlin (JVM 1.9+). Port of src/lib/subnet-cidr.ts — same logic as this dir's python.py / go.go; invalid input yields null rather than throwing.

data class SubnetInfo(
    val network: String, val broadcast: String, val firstHost: String, val lastHost: String,
    val hostCount: ULong, val prefix: Int, val ipClass: Char, val isPrivate: Boolean,
)

object SubnetCidr {
    // The TS /^\d+$/ check — rejects signs, hex, whitespace, and the empty string.
    private fun allDigits(s: String): Boolean = s.isNotEmpty() && s.all { it in '0'..'9' }

    // "a.b.c.d" → UInt. null for a wrong octet count, junk octets, or an octet > 255.
    fun ipToInt(ip: String): UInt? {
        val parts = ip.split(".")
        if (parts.size != 4) return null
        var result = 0u
        for (part in parts) {
            if (!allDigits(part) || part.length > 3) return null
            val octet = part.toInt()
            if (octet > 255) return null
            result = result * 256u + octet.toUInt()
        }
        return result
    }

    fun intToIp(v: UInt): String =
        "${v shr 24}.${(v shr 16) and 255u}.${(v shr 8) and 255u}.${v and 255u}"

    private fun maskFromPrefix(p: Int): UInt = if (p == 0) 0u else 0xFFFFFFFFu shl (32 - p)

    // "ip/prefix" → (canonical ip, prefix). A second slash or a prefix > 32 is invalid.
    private fun parseCidr(cidr: String): Pair<String, Int>? {
        val slash = cidr.indexOf('/')
        if (slash < 0) return null
        val prefixPart = cidr.substring(slash + 1)
        if ('/' in prefixPart || !allDigits(prefixPart) || prefixPart.length > 2) return null
        val prefix = prefixPart.toInt()
        val ip = ipToInt(cidr.substring(0, slash)) ?: return null
        return intToIp(ip) to prefix
    }

    fun subnetDetails(cidr: String): SubnetInfo? {
        val (ip, prefix) = parseCidr(cidr) ?: return null
        if (prefix > 32) return null
        val mask = maskFromPrefix(prefix)
        val wildcard = mask.inv()
        val network = ipToInt(ip)!! and mask
        val broadcast = network or wildcard
        // /31 (point-to-point) and /32 (single host) have no host/broadcast split.
        val first = if (prefix >= 31) network else network + 1u
        val last = if (prefix >= 31) broadcast else broadcast - 1u
        val hosts = if (prefix >= 32) 1uL else if (prefix == 31) 2uL else (1uL shl (32 - prefix)) - 2uL
        val o1 = network shr 24
        val o2 = (network shr 16) and 255u
        val klass = if (o1 < 128u) 'A' else if (o1 < 192u) 'B' else if (o1 < 224u) 'C' else '-' // D/E → '-'
        val priv = o1 == 10u || (o1 == 172u && o2 in 16u..31u) || (o1 == 192u && o2 == 168u)
        return SubnetInfo(intToIp(network), intToIp(broadcast), intToIp(first), intToIp(last),
            hosts, prefix, klass, priv)
    }

    // Split into 2^(newPrefix-prefix) children; empty list for invalid input or range.
    fun splitSubnet(cidr: String, newPrefix: Int): List<String> {
        val parsed = parseCidr(cidr) ?: return emptyList()
        val prefix = parsed.second
        if (newPrefix < prefix || newPrefix > 32) return emptyList()
        val network = ipToInt(parsed.first)!! and maskFromPrefix(prefix)
        val block = 1u shl (32 - newPrefix)
        return (0 until (1 shl (newPrefix - prefix)))
            .map { intToIp(network + it.toUInt() * block) + "/$newPrefix" }
    }
}

fun main() {
    val d = SubnetCidr.subnetDetails("192.168.1.100/24") ?: return
    println("192.168.1.100/24 net=${d.network} bcast=${d.broadcast} first=${d.firstHost} " +
            "last=${d.lastHost} hosts=${d.hostCount} class=${d.ipClass} private=${d.isPrivate}")
    SubnetCidr.splitSubnet("192.168.1.0/24", 26).forEach { println("  split /26 → $it") }
}

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 →