Skip to content

Commit

Permalink
Add wrapper classes (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
05nelsonm authored Dec 29, 2023
1 parent 4561333 commit 5bd3d88
Show file tree
Hide file tree
Showing 28 changed files with 4,764 additions and 2 deletions.
328 changes: 328 additions & 0 deletions library/runtime-api/api/runtime-api.api

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions library/runtime-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ kmpConfiguration {
dependencies {
api(libs.kmp.tor.core.api)
implementation(libs.kmp.tor.core.resource)
implementation(libs.encoding.base16)
implementation(libs.encoding.base32)
implementation(libs.encoding.base64)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -15,4 +15,7 @@
**/
package io.matthewnelson.kmp.tor.runtime.api

internal fun stub() { /* no-op */ }
public interface Destroyable {
public fun destroy()
public fun isDestroyed(): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2023 Matthew Nelson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package io.matthewnelson.kmp.tor.runtime.api.address

import kotlin.jvm.JvmField

/**
* Base abstraction for all address types
*
* @see [IPAddress]
* @see [OnionAddress]
* @see [ProxyAddress]
* */
public sealed class Address(
@JvmField
public val value: String,
): Comparable<Address> {

/**
* Returns the [value] in it's canonicalized hostname form
*
* e.g.
*
* println("127.0.0.1"
* .toIPAddressV4()
* .canonicalHostname()
* )
* // 127.0.0.1
*
* println("::1"
* .toIPAddressV6()
* .canonicalHostname()
* )
* // [::1]
*
* println("http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion"
* .toOnionAddressV3()
* .canonicalHostname()
* )
* // 2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion
*
* println("http://127.0.0.1:8081/path"
* .toProxyAddress()
* .canonicalHostName()
* )
* // 127.0.0.1
* */
public abstract fun canonicalHostname(): String

public final override fun compareTo(other: Address): Int = value.compareTo(other.value)

public final override fun equals(other: Any?): Boolean {
if (other === this) return true
if (other !is Address) return false
if (other::class != this::class) return false
return other.value == value
}

public final override fun hashCode(): Int {
var result = 17
result = result * 31 + this::class.hashCode()
result = result * 31 + value.hashCode()
return result
}

public final override fun toString(): String = value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Copyright (c) 2023 Matthew Nelson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package io.matthewnelson.kmp.tor.runtime.api.address

import io.matthewnelson.kmp.tor.runtime.api.address.IPAddress.V4.Companion.toIPAddressV4OrNull
import io.matthewnelson.kmp.tor.runtime.api.address.IPAddress.V6.Companion.toIPAddressV6OrNull
import io.matthewnelson.kmp.tor.runtime.api.internal.findHostnameAndPortFromURL
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic

/**
* Base abstraction for denoting a String value as an ip address
* */
public sealed class IPAddress private constructor(value: String): Address(value) {

public companion object {

/**
* Parses a String for its IPv4 or IPv6 address.
*
* String can be either a URL containing the IP address, or the
* IPv4/IPv6 address itself.
*
* @return [IPAddress]
* @throws [IllegalArgumentException] if no IP address is found
* */
@JvmStatic
@JvmName("get")
@Throws(IllegalArgumentException::class)
public fun String.toIPAddress(): IPAddress {
return toIPAddressOrNull()
?: throw IllegalArgumentException("$this does not contain an IP address")
}

/**
* Parses a String for its IPv4 or IPv6 address.
*
* String can be either a URL containing the IP address, or the
* IPv4/IPv6 address itself.
*
* @return [IPAddress] or null
* */
@JvmStatic
@JvmName("getOrNull")
public fun String.toIPAddressOrNull(): IPAddress? {
return toIPAddressV4OrNull()
?: toIPAddressV6OrNull()
}
}

/**
* Holder for an IPv4 address
* */
public class V4 private constructor(value: String): IPAddress(value) {

public override fun canonicalHostname(): String = value

public companion object {

/**
* Parses a String for its IPv4 address.
*
* String can be either a URL containing the IPv4 address, or the
* IPv4 address itself.
*
* @return [IPAddress.V4]
* @throws [IllegalArgumentException] if no IPv4 address is found
* */
@JvmStatic
@JvmName("get")
@Throws(IllegalArgumentException::class)
public fun String.toIPAddressV4(): V4 {
return toIPAddressV4OrNull()
?: throw IllegalArgumentException("$this does not contain an IPv4 address")
}

/**
* Parses a String for its IPv4 address.
*
* String can be either a URL containing the IPv4 address, or the
* IPv4 address itself.
*
* @return [IPAddress.V4] or null
* */
@JvmStatic
@JvmName("getOrNull")
public fun String.toIPAddressV4OrNull(): V4? {
val stripped = findHostnameAndPortFromURL()
.substringBeforeLast(':')

if (!stripped.matches(REGEX)) return null
return V4(stripped)
}

// https://ihateregex.io/expr/ip/
@Suppress("RegExpSimplifiable")
private val REGEX: Regex = Regex(pattern =
"(" +
"\\b25[0-5]|" +
"\\b2[0-4][0-9]|" +
"\\b[01]?[0-9][0-9]?)(\\.(25[0-5]|" +
"2[0-4][0-9]|" +
"[01]?[0-9][0-9]?)" +
"){3}"
)
}
}

/**
* Holder for an IPv6 address
* */
public class V6 private constructor(value: String): IPAddress(value) {

public override fun canonicalHostname(): String = "[$value]"

public companion object {

/**
* Parses a String for its IPv6 address.
*
* String can be either a URL containing the IPv6 address, or the
* IPv6 address itself.
*
* @return [IPAddress.V6]
* @throws [IllegalArgumentException] if no IPv6 address is found
* */
@JvmStatic
@JvmName("get")
@Throws(IllegalArgumentException::class)
public fun String.toIPAddressV6(): V6 {
return toIPAddressV6OrNull()
?: throw IllegalArgumentException("$this does not contain an IPv6 address")
}

/**
* Parses a String for its IPv6 address.
*
* String can be either a URL containing the IPv6 address, or the
* IPv6 address itself.
*
* @return [IPAddress.V6] or null
* */
@JvmStatic
@JvmName("getOrNull")
public fun String.toIPAddressV6OrNull(): V6? {
var stripped = findHostnameAndPortFromURL()

// is canonical with port >> [::1]:8080
if (stripped.startsWith('[') && stripped.contains("]:")) {
stripped = stripped.substringBeforeLast(':')
}

if (stripped.startsWith('[') && stripped.endsWith(']')) {
stripped = stripped.drop(1).dropLast(1)
}

if (!stripped.matches(REGEX)) return null
return V6(stripped)
}

// https://ihateregex.io/expr/ipv6/
@Suppress("RegExpSimplifiable")
private val REGEX: Regex = Regex(pattern =
"(" +
"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" +
"([0-9a-fA-F]{1,4}:){1,7}:|" +
"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" +
"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" +
"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" +
"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" +
"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" +
"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" +
":((:[0-9a-fA-F]{1,4}){1,7}|:)|" +
"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" +
"::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|" +
"(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" +
"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" +
"([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|" +
"(2[0-4]|" +
"1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|" +
"(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" +
")"
)
}
}
}
Loading

0 comments on commit 5bd3d88

Please sign in to comment.