Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
## NEXT

## 0.2.0
* Add methods in `IpAddressAndPrefix` interface for parsing `ByteArray` representing address and subnet mask in X509 `IpAddressName`
* `fromX509Octets`
* `toX509Octets`
* Revised generic type arguments
* Introduce `CidrNumber` optimized for CIDR operations
* `CidrNumber.V4` for IPv4
Expand Down
12 changes: 12 additions & 0 deletions cidre/api/android/cidre.api
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public final class at/asitplus/cidre/IpAddress$V6$Companion : at/asitplus/cidre/
}

public abstract interface class at/asitplus/cidre/IpAddressAndPrefix {
public static final field Companion Lat/asitplus/cidre/IpAddressAndPrefix$Companion;
public abstract fun getAddress ()Lat/asitplus/cidre/IpAddress;
public fun getFamily ()Lat/asitplus/cidre/IpFamily;
public fun getHostMask ()[B
Expand All @@ -118,12 +119,17 @@ public abstract interface class at/asitplus/cidre/IpAddressAndPrefix {
public abstract fun isLinkLocal ()Z
public abstract fun isLoopback ()Z
public abstract fun isMulticast ()Z
public fun toX509Octets ()[B
}

public final class at/asitplus/cidre/IpAddressAndPrefix$Companion {
}

public final class at/asitplus/cidre/IpAddressAndPrefix$DefaultImpls {
public static fun getFamily (Lat/asitplus/cidre/IpAddressAndPrefix;)Lat/asitplus/cidre/IpFamily;
public static fun getHostMask (Lat/asitplus/cidre/IpAddressAndPrefix;)[B
public static fun getNumberOfHostBits-pVg5ArA (Lat/asitplus/cidre/IpAddressAndPrefix;)I
public static fun toX509Octets (Lat/asitplus/cidre/IpAddressAndPrefix;)[B
}

public abstract interface class at/asitplus/cidre/IpAddressAndPrefix$V4 : at/asitplus/cidre/IpAddressAndPrefix {
Expand All @@ -140,6 +146,7 @@ public final class at/asitplus/cidre/IpAddressAndPrefix$V4$DefaultImpls {
public static fun getNumberOfHostBits-pVg5ArA (Lat/asitplus/cidre/IpAddressAndPrefix$V4;)I
public static fun netmaskToString (Lat/asitplus/cidre/IpAddressAndPrefix$V4;)Ljava/lang/String;
public static fun toString (Lat/asitplus/cidre/IpAddressAndPrefix$V4;Z)Ljava/lang/String;
public static fun toX509Octets (Lat/asitplus/cidre/IpAddressAndPrefix$V4;)[B
}

public abstract interface class at/asitplus/cidre/IpAddressAndPrefix$V6 : at/asitplus/cidre/IpAddressAndPrefix {
Expand All @@ -160,6 +167,7 @@ public final class at/asitplus/cidre/IpAddressAndPrefix$V6$DefaultImpls {
public static fun getHostMask (Lat/asitplus/cidre/IpAddressAndPrefix$V6;)[B
public static fun getNumberOfHostBits-pVg5ArA (Lat/asitplus/cidre/IpAddressAndPrefix$V6;)I
public static fun toString (Lat/asitplus/cidre/IpAddressAndPrefix$V6;Z)Ljava/lang/String;
public static fun toX509Octets (Lat/asitplus/cidre/IpAddressAndPrefix$V6;)[B
}

public final class at/asitplus/cidre/IpAddressAndPrefixKt {
Expand Down Expand Up @@ -213,9 +221,11 @@ public abstract class at/asitplus/cidre/IpInterface : at/asitplus/cidre/IpAddres
public fun isLoopback ()Z
public fun isMulticast ()Z
public fun toString ()Ljava/lang/String;
public fun toX509Octets ()[B
}

public final class at/asitplus/cidre/IpInterface$Companion {
public final fun fromX509Octets ([B)Lat/asitplus/cidre/IpInterface;
public final fun invoke (Ljava/lang/String;)Lat/asitplus/cidre/IpInterface;
public final fun invoke-Qn1smSk (Lat/asitplus/cidre/IpAddress;I)Lat/asitplus/cidre/IpInterface;
}
Expand Down Expand Up @@ -278,10 +288,12 @@ public abstract class at/asitplus/cidre/IpNetwork : at/asitplus/cidre/IpAddressA
public final fun overlaps (Lat/asitplus/cidre/IpNetwork;)Z
public final fun plus (Lat/asitplus/cidre/IpNetwork;)Lat/asitplus/cidre/IpNetwork;
public fun toString ()Ljava/lang/String;
public fun toX509Octets ()[B
}

public final class at/asitplus/cidre/IpNetwork$Companion {
public final fun forAddress-Qn1smSk (Lat/asitplus/cidre/IpAddress;I)Lat/asitplus/cidre/IpNetwork;
public final fun fromX509Octets ([B)Lat/asitplus/cidre/IpNetwork;
public final fun invoke (Ljava/lang/String;Z)Lat/asitplus/cidre/IpNetwork;
public static synthetic fun invoke$default (Lat/asitplus/cidre/IpNetwork$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lat/asitplus/cidre/IpNetwork;
public final fun invoke-OsBMiQA (Lat/asitplus/cidre/IpAddress;IZ)Lat/asitplus/cidre/IpNetwork;
Expand Down
7 changes: 7 additions & 0 deletions cidre/api/cidre.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ sealed interface <#A: kotlin/Number, #B: at.asitplus.cidre.byteops/CidrNumber<#B
open val numberOfHostBits // at.asitplus.cidre/IpAddressAndPrefix.numberOfHostBits|{}numberOfHostBits[0]
open fun <get-numberOfHostBits>(): kotlin/UInt // at.asitplus.cidre/IpAddressAndPrefix.numberOfHostBits.<get-numberOfHostBits>|<get-numberOfHostBits>(){}[0]

open fun toX509Octets(): kotlin/ByteArray // at.asitplus.cidre/IpAddressAndPrefix.toX509Octets|toX509Octets(){}[0]

sealed interface V4 : at.asitplus.cidre/IpAddressAndPrefix<kotlin/Byte, at.asitplus.cidre.byteops/CidrNumber.V4> { // at.asitplus.cidre/IpAddressAndPrefix.V4|null[0]
abstract val address // at.asitplus.cidre/IpAddressAndPrefix.V4.address|{}address[0]
abstract fun <get-address>(): at.asitplus.cidre/IpAddress.V4 // at.asitplus.cidre/IpAddressAndPrefix.V4.address.<get-address>|<get-address>(){}[0]
Expand Down Expand Up @@ -157,6 +159,8 @@ sealed interface <#A: kotlin/Number, #B: at.asitplus.cidre.byteops/CidrNumber<#B

open fun toString(kotlin/Boolean): kotlin/String // at.asitplus.cidre/IpAddressAndPrefix.V6.toString|toString(kotlin.Boolean){}[0]
}

final object Companion // at.asitplus.cidre/IpAddressAndPrefix.Companion|null[0]
}

sealed interface at.asitplus.cidre/IpFamily { // at.asitplus.cidre/IpFamily|null[0]
Expand Down Expand Up @@ -328,6 +332,7 @@ sealed class <#A: kotlin/Number, #B: at.asitplus.cidre.byteops/CidrNumber<#B>> a
open fun <get-prefix>(): kotlin/UInt // at.asitplus.cidre/IpInterface.prefix.<get-prefix>|<get-prefix>(){}[0]

open fun toString(): kotlin/String // at.asitplus.cidre/IpInterface.toString|toString(){}[0]
open fun toX509Octets(): kotlin/ByteArray // at.asitplus.cidre/IpInterface.toX509Octets|toX509Octets(){}[0]

final class V4 : at.asitplus.cidre/IpInterface<kotlin/Byte, at.asitplus.cidre.byteops/CidrNumber.V4> { // at.asitplus.cidre/IpInterface.V4|null[0]
constructor <init>(at.asitplus.cidre/IpAddress.V4, kotlin/UInt) // at.asitplus.cidre/IpInterface.V4.<init>|<init>(at.asitplus.cidre.IpAddress.V4;kotlin.UInt){}[0]
Expand All @@ -354,6 +359,7 @@ sealed class <#A: kotlin/Number, #B: at.asitplus.cidre.byteops/CidrNumber<#B>> a

final object Companion { // at.asitplus.cidre/IpInterface.Companion|null[0]
final fun <#A2: kotlin/Number, #B2: at.asitplus.cidre.byteops/CidrNumber<#B2>> invoke(at.asitplus.cidre/IpAddress<#A2, #B2>, kotlin/UInt): at.asitplus.cidre/IpInterface<#A2, #B2> // at.asitplus.cidre/IpInterface.Companion.invoke|invoke(at.asitplus.cidre.IpAddress<0:0,0:1>;kotlin.UInt){0§<kotlin.Number>;1§<at.asitplus.cidre.byteops.CidrNumber<0:1>>}[0]
final fun fromX509Octets(kotlin/ByteArray): at.asitplus.cidre/IpInterface<*, *> // at.asitplus.cidre/IpInterface.Companion.fromX509Octets|fromX509Octets(kotlin.ByteArray){}[0]
final fun invoke(kotlin/String): at.asitplus.cidre/IpInterface<*, *> // at.asitplus.cidre/IpInterface.Companion.invoke|invoke(kotlin.String){}[0]
}
}
Expand Down Expand Up @@ -517,6 +523,7 @@ sealed class <#A: kotlin/Number, #B: at.asitplus.cidre.byteops/CidrNumber<#B>> a
final object Companion { // at.asitplus.cidre/IpNetwork.Companion|null[0]
final fun <#A2: kotlin/Number, #B2: at.asitplus.cidre.byteops/CidrNumber<#B2>> forAddress(at.asitplus.cidre/IpAddress<#A2, #B2>, kotlin/UInt): at.asitplus.cidre/IpNetwork<#A2, #B2> // at.asitplus.cidre/IpNetwork.Companion.forAddress|forAddress(at.asitplus.cidre.IpAddress<0:0,0:1>;kotlin.UInt){0§<kotlin.Number>;1§<at.asitplus.cidre.byteops.CidrNumber<0:1>>}[0]
final fun <#A2: kotlin/Number, #B2: at.asitplus.cidre.byteops/CidrNumber<#B2>> invoke(at.asitplus.cidre/IpAddress<#A2, #B2>, kotlin/UInt, kotlin/Boolean = ...): at.asitplus.cidre/IpNetwork<#A2, #B2> // at.asitplus.cidre/IpNetwork.Companion.invoke|invoke(at.asitplus.cidre.IpAddress<0:0,0:1>;kotlin.UInt;kotlin.Boolean){0§<kotlin.Number>;1§<at.asitplus.cidre.byteops.CidrNumber<0:1>>}[0]
final fun fromX509Octets(kotlin/ByteArray): at.asitplus.cidre/IpNetwork<*, *> // at.asitplus.cidre/IpNetwork.Companion.fromX509Octets|fromX509Octets(kotlin.ByteArray){}[0]
final fun invoke(kotlin/String, kotlin/Boolean = ...): at.asitplus.cidre/IpNetwork<*, *> // at.asitplus.cidre/IpNetwork.Companion.invoke|invoke(kotlin.String;kotlin.Boolean){}[0]
}
}
Expand Down
12 changes: 12 additions & 0 deletions cidre/api/jvm/cidre.api
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public final class at/asitplus/cidre/IpAddress$V6$Companion : at/asitplus/cidre/
}

public abstract interface class at/asitplus/cidre/IpAddressAndPrefix {
public static final field Companion Lat/asitplus/cidre/IpAddressAndPrefix$Companion;
public abstract fun getAddress ()Lat/asitplus/cidre/IpAddress;
public fun getFamily ()Lat/asitplus/cidre/IpFamily;
public fun getHostMask ()[B
Expand All @@ -109,12 +110,17 @@ public abstract interface class at/asitplus/cidre/IpAddressAndPrefix {
public abstract fun isLinkLocal ()Z
public abstract fun isLoopback ()Z
public abstract fun isMulticast ()Z
public fun toX509Octets ()[B
}

public final class at/asitplus/cidre/IpAddressAndPrefix$Companion {
}

public final class at/asitplus/cidre/IpAddressAndPrefix$DefaultImpls {
public static fun getFamily (Lat/asitplus/cidre/IpAddressAndPrefix;)Lat/asitplus/cidre/IpFamily;
public static fun getHostMask (Lat/asitplus/cidre/IpAddressAndPrefix;)[B
public static fun getNumberOfHostBits-pVg5ArA (Lat/asitplus/cidre/IpAddressAndPrefix;)I
public static fun toX509Octets (Lat/asitplus/cidre/IpAddressAndPrefix;)[B
}

public abstract interface class at/asitplus/cidre/IpAddressAndPrefix$V4 : at/asitplus/cidre/IpAddressAndPrefix {
Expand All @@ -131,6 +137,7 @@ public final class at/asitplus/cidre/IpAddressAndPrefix$V4$DefaultImpls {
public static fun getNumberOfHostBits-pVg5ArA (Lat/asitplus/cidre/IpAddressAndPrefix$V4;)I
public static fun netmaskToString (Lat/asitplus/cidre/IpAddressAndPrefix$V4;)Ljava/lang/String;
public static fun toString (Lat/asitplus/cidre/IpAddressAndPrefix$V4;Z)Ljava/lang/String;
public static fun toX509Octets (Lat/asitplus/cidre/IpAddressAndPrefix$V4;)[B
}

public abstract interface class at/asitplus/cidre/IpAddressAndPrefix$V6 : at/asitplus/cidre/IpAddressAndPrefix {
Expand All @@ -151,6 +158,7 @@ public final class at/asitplus/cidre/IpAddressAndPrefix$V6$DefaultImpls {
public static fun getHostMask (Lat/asitplus/cidre/IpAddressAndPrefix$V6;)[B
public static fun getNumberOfHostBits-pVg5ArA (Lat/asitplus/cidre/IpAddressAndPrefix$V6;)I
public static fun toString (Lat/asitplus/cidre/IpAddressAndPrefix$V6;Z)Ljava/lang/String;
public static fun toX509Octets (Lat/asitplus/cidre/IpAddressAndPrefix$V6;)[B
}

public final class at/asitplus/cidre/IpAddressAndPrefixKt {
Expand Down Expand Up @@ -204,9 +212,11 @@ public abstract class at/asitplus/cidre/IpInterface : at/asitplus/cidre/IpAddres
public fun isLoopback ()Z
public fun isMulticast ()Z
public fun toString ()Ljava/lang/String;
public fun toX509Octets ()[B
}

public final class at/asitplus/cidre/IpInterface$Companion {
public final fun fromX509Octets ([B)Lat/asitplus/cidre/IpInterface;
public final fun invoke (Ljava/lang/String;)Lat/asitplus/cidre/IpInterface;
public final fun invoke-Qn1smSk (Lat/asitplus/cidre/IpAddress;I)Lat/asitplus/cidre/IpInterface;
}
Expand Down Expand Up @@ -269,10 +279,12 @@ public abstract class at/asitplus/cidre/IpNetwork : at/asitplus/cidre/IpAddressA
public final fun overlaps (Lat/asitplus/cidre/IpNetwork;)Z
public final fun plus (Lat/asitplus/cidre/IpNetwork;)Lat/asitplus/cidre/IpNetwork;
public fun toString ()Ljava/lang/String;
public fun toX509Octets ()[B
}

public final class at/asitplus/cidre/IpNetwork$Companion {
public final fun forAddress-Qn1smSk (Lat/asitplus/cidre/IpAddress;I)Lat/asitplus/cidre/IpNetwork;
public final fun fromX509Octets ([B)Lat/asitplus/cidre/IpNetwork;
public final fun invoke (Ljava/lang/String;Z)Lat/asitplus/cidre/IpNetwork;
public static synthetic fun invoke$default (Lat/asitplus/cidre/IpNetwork$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lat/asitplus/cidre/IpNetwork;
public final fun invoke-OsBMiQA (Lat/asitplus/cidre/IpAddress;IZ)Lat/asitplus/cidre/IpNetwork;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package at.asitplus.cidre

import at.asitplus.cidre.byteops.CidrNumber
import at.asitplus.cidre.byteops.invInPlace
import at.asitplus.cidre.byteops.toPrefix
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

Expand Down Expand Up @@ -44,6 +45,39 @@ sealed interface IpAddressAndPrefix<N : Number, S : CidrNumber<S>> {
/**`true` if this is (part of) the [IpNetwork.SpecialRanges.multicast] network */
val isMulticast: Boolean

/**
* Encodes this network into X.509 iPAddressName ByteArray (RFC 5280).
* IPv4: [4 bytes base address][4 bytes subnet mask] (8 bytes)
* IPv6: [16 bytes base address][16 bytes subnet mask] (32 bytes)
*/
fun toX509Octets(): ByteArray = address.octets + netmask

companion object {

/**
* Low-level helper used by [IpNetwork.fromX509Octets] and [IpInterface.fromX509Octets]
* to extract base address and CIDR prefix
* IPv4: [4 bytes base address][4 bytes subnet mask] (8 bytes)
* IPv6: [16 bytes base address][16 bytes subnet mask] (32 bytes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not render correctly, because […] is just MD syntax for a href with an internal link target of the same name as the visible text

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* IPv4: [4 bytes base address][4 bytes subnet mask] (8 bytes)
* IPv6: [16 bytes base address][16 bytes subnet mask] (32 bytes)
* * IPv4 byte layout: `AAAANNNN`, where `A` ist an address octet and `N` is a netmask octet (8 bytes total)
* * IPv6 byte layout: `AAAAAAAAAAAAAAAANNNNNNNNNNNNNNNN`, where `A` ist an address octet and `N` is a netmask octet(32 bytes total)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fee free to reformat, introduce spaces of whatever, if you feel it makes it more legible

*/
internal fun parseX509Octets(bytes: ByteArray): Pair<IpAddress<*, *>, Prefix> =
when (bytes.size) {
2 * IpFamily.V4.numberOfOctets -> {
val address = bytes.copyOfRange(0, 4)
val mask = bytes.copyOfRange(4, 8)
IpAddress.V4(address) to mask.toPrefix()
}

2 * IpFamily.V6.numberOfOctets -> {
val address = bytes.copyOfRange(0, 16)
val mask = bytes.copyOfRange(16, 32)
IpAddress.V6(address) to mask.toPrefix()
}

else -> throw IllegalArgumentException("Invalid iPAddress length: ${bytes.size}")
}
}

/**
* Sealed base interface of [IpAddress.V4] and [IpInterface.V4] with IPv4-specific add-ons.
*
Expand Down
17 changes: 17 additions & 0 deletions cidre/src/commonMain/kotlin/at/asitplus/cidre/IpInterface.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.asitplus.cidre

import at.asitplus.cidre.IpAddressAndPrefix.Companion.parseX509Octets
import at.asitplus.cidre.byteops.CidrNumber


Expand All @@ -13,6 +14,8 @@ constructor(override val prefix: Prefix, val network: IpNetwork<N, S>) :

override fun toString(): String = "$address/$prefix"

override fun toX509Octets(): ByteArray = super.toX509Octets()

companion object {
@Suppress("UNCHECKED_CAST")
internal fun <N : Number, S: CidrNumber<S>> unsafe(
Expand All @@ -39,6 +42,20 @@ constructor(override val prefix: Prefix, val network: IpNetwork<N, S>) :
val (addr, prefix) = parseIpAndPrefix(stringRepresentation)
return IpInterface(addr, prefix)
}

/**
* Decodes an IpInterface from X.509 iPAddressName ByteArray (RFC 5280).
* IPv4: [4 bytes base address][4 bytes subnet mask] (8 bytes)
* IPv6: [16 bytes base address][16 bytes subnet mask] (32 bytes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

*/
@Throws(IllegalArgumentException::class)
fun fromX509Octets(bytes: ByteArray): IpInterface<*, *> {
val (address, prefix) = parseX509Octets(bytes)
return when (address) {
is IpAddress.V4 -> V4(address, prefix)
is IpAddress.V6 -> V6(address, prefix)
}
}
}

class V4 internal constructor(override val address: IpAddress.V4, prefix: Prefix, network: IpNetwork.V4) :
Expand Down
14 changes: 14 additions & 0 deletions cidre/src/commonMain/kotlin/at/asitplus/cidre/IpNetwork.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.asitplus.cidre

import at.asitplus.cidre.IpAddressAndPrefix.Companion.parseX509Octets
import at.asitplus.cidre.byteops.CidrNumber
import at.asitplus.cidre.byteops.and
import at.asitplus.cidre.byteops.or
Expand Down Expand Up @@ -528,6 +529,19 @@ constructor(address: IpAddress<N, S>, override val prefix: Prefix, strict: Boole
) as IpNetwork<N, S>
}

/**
* Decodes an IpNetwork from X.509 iPAddressName ByteArray (RFC 5280).
* IPv4: [4 bytes base address][4 bytes subnet mask] (8 bytes)
* IPv6: [16 bytes base address][16 bytes subnet mask] (32 bytes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

*/
fun fromX509Octets(bytes: ByteArray): IpNetwork<*, *> {
val (addr, prefix) = parseX509Octets(bytes)
return when (addr) {
is IpAddress.V4 -> V4(addr, prefix)
is IpAddress.V6 -> V6(addr, prefix)
}
}

}


Expand Down
Loading