diff --git a/okhttp-dnsoverhttps/api/okhttp-dnsoverhttps.api b/okhttp-dnsoverhttps/api/okhttp-dnsoverhttps.api index 534cebe09d44..1356ed3e41f3 100644 --- a/okhttp-dnsoverhttps/api/okhttp-dnsoverhttps.api +++ b/okhttp-dnsoverhttps/api/okhttp-dnsoverhttps.api @@ -1,11 +1,11 @@ -public final class okhttp3/dnsoverhttps/DnsOverHttps : okhttp3/Dns, okhttp3/Dns2 { +public final class okhttp3/dnsoverhttps/DnsOverHttps : okhttp3/Dns { public static final field Companion Lokhttp3/dnsoverhttps/DnsOverHttps$Companion; public static final field MAX_RESPONSE_SIZE I public final fun client ()Lokhttp3/OkHttpClient; public final fun includeHttps ()Z public final fun includeIPv6 ()Z public fun lookup (Ljava/lang/String;)Ljava/util/List; - public fun newCall (Lokhttp3/Dns2$Request;)Lokhttp3/Dns2$Call; + public fun newCall (Lokhttp3/Dns$Request;)Lokhttp3/Dns$Call; public final fun post ()Z public final fun resolvePrivateAddresses ()Z public final fun resolvePublicAddresses ()Z diff --git a/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/-DnsOverHttpsCall.kt b/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/-DnsOverHttpsCall.kt index 0a4dfe214107..ade8c55da7e2 100644 --- a/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/-DnsOverHttpsCall.kt +++ b/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/-DnsOverHttpsCall.kt @@ -22,7 +22,7 @@ import java.io.IOException import java.util.concurrent.atomic.AtomicReference import okhttp3.Call import okhttp3.Callback -import okhttp3.Dns2 +import okhttp3.Dns import okhttp3.Protocol import okhttp3.Response import okhttp3.internal.OkHttpInternalApi @@ -33,18 +33,18 @@ import okhttp3.internal.testAndSet // TODO: honor Https.priority and Https.targetName. Create new calls! /** - * Implements [Dns2.Call] by making multiple HTTPS calls. + * Implements [Dns.Call] by making multiple HTTPS calls. */ internal class DnsOverHttpsCall( - override val request: Dns2.Request, + override val request: Dns.Request, private val calls: List, private val canceledException: IOException?, -) : Dns2.Call, +) : Dns.Call, Callback { @Volatile private var canceled = false private val state = AtomicReference(State.Idle) - override fun enqueue(callback: Dns2.Callback) { + override fun enqueue(callback: Dns.Callback) { val running = State.Running(callback, calls) val previous = state.testAndSet(running) { it is State.Idle } @@ -115,11 +115,11 @@ internal class DnsOverHttpsCall( return onFailure(call, e) } - val dns2Records = + val dnsRecords = resourceRecords.map { resourceRecord -> when (resourceRecord) { is ResourceRecord.Https -> { - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = request.hostname, alpnIds = resourceRecord.alpnIds?.mapNotNull { alpnId -> @@ -136,7 +136,7 @@ internal class DnsOverHttpsCall( } is ResourceRecord.IpAddress -> { - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = request.hostname, address = resourceRecord.address, ) @@ -170,11 +170,11 @@ internal class DnsOverHttpsCall( val emitDelayedFailures = lastRunningCall && previous.delayedFailures.isNotEmpty() val lastEvent = lastRunningCall && !emitDelayedFailures - if (dns2Records.isNotEmpty() || lastEvent) { + if (dnsRecords.isNotEmpty() || lastEvent) { previous.callback.onRecords( call = this, last = lastEvent, - records = dns2Records, + records = dnsRecords, ) } if (emitDelayedFailures) { @@ -203,7 +203,7 @@ internal class DnsOverHttpsCall( object Idle : State class Running( - val callback: Dns2.Callback, + val callback: Dns.Callback, val runningCalls: List, val delayedFailures: List = listOf(), ) : State @@ -212,7 +212,7 @@ internal class DnsOverHttpsCall( } } -internal fun Dns2.Callback.onFailure( +internal fun Dns.Callback.onFailure( call: DnsOverHttpsCall, exceptions: List, ) { diff --git a/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt b/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt index bbe34b39d243..0343df75c25e 100644 --- a/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt +++ b/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt @@ -22,7 +22,6 @@ import java.util.concurrent.CountDownLatch import okhttp3.Call import okhttp3.Callback import okhttp3.Dns -import okhttp3.Dns2 import okhttp3.HttpUrl import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType @@ -49,9 +48,8 @@ class DnsOverHttps internal constructor( @get:JvmName("post") val post: Boolean, @get:JvmName("resolvePrivateAddresses") val resolvePrivateAddresses: Boolean, @get:JvmName("resolvePublicAddresses") val resolvePublicAddresses: Boolean, -) : Dns, - Dns2 { - override fun newCall(request: Dns2.Request): Dns2.Call { +) : Dns { + override fun newCall(request: Dns.Request): Dns.Call { val calls = callsList(request.hostname) val canceledException = validate(request.hostname) @@ -71,7 +69,7 @@ class DnsOverHttps internal constructor( /** * Returns an exception if [hostname] should not be resolved. * - * We **return** this exception rather than throwing it because in the [Dns2.Callback] case we want + * We **return** this exception rather than throwing it because in the [Dns.Callback] case we want * `onFailure()` to be called on a dispatcher thread and not synchronously. */ private fun validate(hostname: String): UnknownHostException? { diff --git a/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt b/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt index 099dc1428e9b..f5cd1f6001e5 100644 --- a/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt +++ b/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt @@ -37,7 +37,7 @@ import okhttp3.CallEvent import okhttp3.CallEvent.CacheHit import okhttp3.CallEvent.CacheMiss import okhttp3.Dispatcher -import okhttp3.Dns2 +import okhttp3.Dns import okhttp3.EventRecorder import okhttp3.Headers.Companion.headersOf import okhttp3.Interceptor @@ -346,7 +346,7 @@ class DnsOverHttpsTest( ), ) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) val dnsEvents = call.execute() assertThat(dnsEvents.take()).isEqualTo( @@ -354,7 +354,7 @@ class DnsOverHttpsTest( last = false, records = listOf( - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "lysine.dev", alpnIds = listOf(Protocol.HTTP_2), port = 8843, @@ -373,7 +373,7 @@ class DnsOverHttpsTest( last = false, records = listOf( - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = "lysine.dev", address = InetAddress.getByName("1:2::3:4"), ), @@ -385,7 +385,7 @@ class DnsOverHttpsTest( last = true, records = listOf( - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = "lysine.dev", address = InetAddress.getByName("10.20.30.40"), ), @@ -417,7 +417,7 @@ class DnsOverHttpsTest( ), ) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) val dnsEvents = call.execute() assertThat(dnsEvents.take()).isEqualTo( @@ -425,7 +425,7 @@ class DnsOverHttpsTest( last = false, records = listOf( - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = "lysine.dev", address = InetAddress.getByName("11:22::33:44"), ), @@ -437,7 +437,7 @@ class DnsOverHttpsTest( last = false, records = listOf( - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = "lysine.dev", address = InetAddress.getByName("10.20.30.40"), ), @@ -465,7 +465,7 @@ class DnsOverHttpsTest( ), ) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) val dnsEvents = call.execute() assertThat(dnsEvents.take()).isEqualTo( @@ -473,7 +473,7 @@ class DnsOverHttpsTest( last = false, records = listOf( - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = "lysine.dev", address = InetAddress.getByName("10.20.30.40"), ), @@ -498,7 +498,7 @@ class DnsOverHttpsTest( ), ) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) val dnsEvents = call.execute() assertThat(dnsEvents.take()).isEqualTo( @@ -506,7 +506,7 @@ class DnsOverHttpsTest( last = true, records = listOf( - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = "lysine.dev", address = InetAddress.getByName("10.20.30.40"), ), @@ -521,7 +521,7 @@ class DnsOverHttpsTest( dns = buildLocalhost(bootstrapClient, includeIPv6 = true, includeHttps = true) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) val dnsEvents = call.execute() assertThat(dnsEvents.take()).isEqualTo( @@ -538,7 +538,7 @@ class DnsOverHttpsTest( dns = buildLocalhost(bootstrapClient, includeIPv6 = true, includeHttps = true) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) call.cancel() assertThat(call.isCanceled()).isTrue() val dnsEvents = call.execute() @@ -551,7 +551,7 @@ class DnsOverHttpsTest( assumeTrue(entryPoint == EntryPoint.NewCall) dns = buildLocalhost(bootstrapClient, includeIPv6 = true, includeHttps = true) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) interceptor = Interceptor { chain -> @@ -578,7 +578,7 @@ class DnsOverHttpsTest( ), ) - val call = dns.newCall(Dns2.Request("lysine.dev")) + val call = dns.newCall(Dns.Request("lysine.dev")) interceptor = object : Interceptor { @@ -599,7 +599,7 @@ class DnsOverHttpsTest( last = false, records = listOf( - Dns2.Record.IpAddress( + Dns.Record.IpAddress( hostname = "lysine.dev", address = InetAddress.getByName("11:22::33:44"), ), diff --git a/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsTesting.kt b/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsTesting.kt index f765be4d68ea..206db2c93d60 100644 --- a/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsTesting.kt +++ b/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsTesting.kt @@ -19,13 +19,13 @@ import java.net.InetAddress import java.net.UnknownHostException import java.util.concurrent.BlockingDeque import java.util.concurrent.LinkedBlockingDeque -import okhttp3.Dns2 +import okhttp3.Dns import okio.IOException sealed interface DnsEvent { data class Records( val last: Boolean, - val records: List, + val records: List, ) : DnsEvent data class Failure( @@ -33,21 +33,21 @@ sealed interface DnsEvent { ) : DnsEvent } -internal fun Dns2.Call.execute(): BlockingDeque { +internal fun Dns.Call.execute(): BlockingDeque { val result = LinkedBlockingDeque() enqueue( - object : Dns2.Callback { + object : Dns.Callback { override fun onRecords( - call: Dns2.Call, + call: Dns.Call, last: Boolean, - records: List, + records: List, ) { result.put(DnsEvent.Records(last, records)) } override fun onFailure( - call: Dns2.Call, + call: Dns.Call, e: IOException, ) { result.put(DnsEvent.Failure(e)) @@ -73,7 +73,7 @@ operator fun DnsOverHttps.invoke( EntryPoint.NewCall -> { buildList { - val dnsEvents = newCall(Dns2.Request(hostname)).execute() + val dnsEvents = newCall(Dns.Request(hostname)).execute() while (true) { when (val dnsEvent = dnsEvents.take()) { is DnsEvent.Failure -> { @@ -83,7 +83,7 @@ operator fun DnsOverHttps.invoke( is DnsEvent.Records -> { addAll( dnsEvent.records - .filterIsInstance() + .filterIsInstance() .map { it.address }, ) diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpClientTestRule.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpClientTestRule.kt index 6a3c31d5f393..347c66675eea 100644 --- a/okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpClientTestRule.kt +++ b/okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpClientTestRule.kt @@ -29,6 +29,7 @@ import java.util.logging.Level import java.util.logging.LogManager import java.util.logging.LogRecord import java.util.logging.Logger +import okhttp3.OkHttpClientTestRule.Companion.plus import okhttp3.internal.buildConnectionPool import okhttp3.internal.concurrent.TaskRunner import okhttp3.internal.concurrent.withLock diff --git a/okhttp/api/android/okhttp.api b/okhttp/api/android/okhttp.api index 477d838aa4d4..52d93bbab938 100644 --- a/okhttp/api/android/okhttp.api +++ b/okhttp/api/android/okhttp.api @@ -480,32 +480,29 @@ public abstract interface class okhttp3/Dns { public static final field Companion Lokhttp3/Dns$Companion; public static final field SYSTEM Lokhttp3/Dns; public abstract fun lookup (Ljava/lang/String;)Ljava/util/List; + public fun newCall (Lokhttp3/Dns$Request;)Lokhttp3/Dns$Call; } -public final class okhttp3/Dns$Companion { -} - -public abstract interface class okhttp3/Dns2 { - public abstract fun newCall (Lokhttp3/Dns2$Request;)Lokhttp3/Dns2$Call; -} - -public abstract interface class okhttp3/Dns2$Call { +public abstract interface class okhttp3/Dns$Call { public abstract fun cancel ()V - public abstract fun enqueue (Lokhttp3/Dns2$Callback;)V - public abstract fun getRequest ()Lokhttp3/Dns2$Request; + public abstract fun enqueue (Lokhttp3/Dns$Callback;)V + public abstract fun getRequest ()Lokhttp3/Dns$Request; public abstract fun isCanceled ()Z } -public abstract interface class okhttp3/Dns2$Callback { - public abstract fun onFailure (Lokhttp3/Dns2$Call;Ljava/io/IOException;)V - public abstract fun onRecords (Lokhttp3/Dns2$Call;ZLjava/util/List;)V +public abstract interface class okhttp3/Dns$Callback { + public abstract fun onFailure (Lokhttp3/Dns$Call;Ljava/io/IOException;)V + public abstract fun onRecords (Lokhttp3/Dns$Call;ZLjava/util/List;)V +} + +public final class okhttp3/Dns$Companion { } -public abstract class okhttp3/Dns2$Record { +public abstract class okhttp3/Dns$Record { public abstract fun getHostname ()Ljava/lang/String; } -public final class okhttp3/Dns2$Record$IpAddress : okhttp3/Dns2$Record { +public final class okhttp3/Dns$Record$IpAddress : okhttp3/Dns$Record { public fun (Ljava/lang/String;Ljava/net/InetAddress;)V public final fun address ()Ljava/net/InetAddress; public fun equals (Ljava/lang/Object;)Z @@ -515,7 +512,7 @@ public final class okhttp3/Dns2$Record$IpAddress : okhttp3/Dns2$Record { public fun toString ()Ljava/lang/String; } -public final class okhttp3/Dns2$Record$ServiceMetadata : okhttp3/Dns2$Record { +public final class okhttp3/Dns$Record$ServiceMetadata : okhttp3/Dns$Record { public fun (Ljava/lang/String;Ljava/util/List;ILjava/util/List;Lokio/ByteString;)V public synthetic fun (Ljava/lang/String;Ljava/util/List;ILjava/util/List;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun alpnIds ()Ljava/util/List; @@ -529,7 +526,7 @@ public final class okhttp3/Dns2$Record$ServiceMetadata : okhttp3/Dns2$Record { public fun toString ()Ljava/lang/String; } -public final class okhttp3/Dns2$Request { +public final class okhttp3/Dns$Request { public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;I)V public synthetic fun (Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/okhttp/api/jvm/okhttp.api b/okhttp/api/jvm/okhttp.api index 7e10ac5fc93b..c1d30a4a2725 100644 --- a/okhttp/api/jvm/okhttp.api +++ b/okhttp/api/jvm/okhttp.api @@ -480,32 +480,29 @@ public abstract interface class okhttp3/Dns { public static final field Companion Lokhttp3/Dns$Companion; public static final field SYSTEM Lokhttp3/Dns; public abstract fun lookup (Ljava/lang/String;)Ljava/util/List; + public fun newCall (Lokhttp3/Dns$Request;)Lokhttp3/Dns$Call; } -public final class okhttp3/Dns$Companion { -} - -public abstract interface class okhttp3/Dns2 { - public abstract fun newCall (Lokhttp3/Dns2$Request;)Lokhttp3/Dns2$Call; -} - -public abstract interface class okhttp3/Dns2$Call { +public abstract interface class okhttp3/Dns$Call { public abstract fun cancel ()V - public abstract fun enqueue (Lokhttp3/Dns2$Callback;)V - public abstract fun getRequest ()Lokhttp3/Dns2$Request; + public abstract fun enqueue (Lokhttp3/Dns$Callback;)V + public abstract fun getRequest ()Lokhttp3/Dns$Request; public abstract fun isCanceled ()Z } -public abstract interface class okhttp3/Dns2$Callback { - public abstract fun onFailure (Lokhttp3/Dns2$Call;Ljava/io/IOException;)V - public abstract fun onRecords (Lokhttp3/Dns2$Call;ZLjava/util/List;)V +public abstract interface class okhttp3/Dns$Callback { + public abstract fun onFailure (Lokhttp3/Dns$Call;Ljava/io/IOException;)V + public abstract fun onRecords (Lokhttp3/Dns$Call;ZLjava/util/List;)V +} + +public final class okhttp3/Dns$Companion { } -public abstract class okhttp3/Dns2$Record { +public abstract class okhttp3/Dns$Record { public abstract fun getHostname ()Ljava/lang/String; } -public final class okhttp3/Dns2$Record$IpAddress : okhttp3/Dns2$Record { +public final class okhttp3/Dns$Record$IpAddress : okhttp3/Dns$Record { public fun (Ljava/lang/String;Ljava/net/InetAddress;)V public final fun address ()Ljava/net/InetAddress; public fun equals (Ljava/lang/Object;)Z @@ -515,7 +512,7 @@ public final class okhttp3/Dns2$Record$IpAddress : okhttp3/Dns2$Record { public fun toString ()Ljava/lang/String; } -public final class okhttp3/Dns2$Record$ServiceMetadata : okhttp3/Dns2$Record { +public final class okhttp3/Dns$Record$ServiceMetadata : okhttp3/Dns$Record { public fun (Ljava/lang/String;Ljava/util/List;ILjava/util/List;Lokio/ByteString;)V public synthetic fun (Ljava/lang/String;Ljava/util/List;ILjava/util/List;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun alpnIds ()Ljava/util/List; @@ -529,7 +526,7 @@ public final class okhttp3/Dns2$Record$ServiceMetadata : okhttp3/Dns2$Record { public fun toString ()Ljava/lang/String; } -public final class okhttp3/Dns2$Request { +public final class okhttp3/Dns$Request { public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;I)V public synthetic fun (Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dns.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dns.kt index d7fdd38d564c..1010c02dfcd0 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dns.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dns.kt @@ -17,25 +17,260 @@ package okhttp3 import java.net.InetAddress import java.net.UnknownHostException -import okhttp3.Dns.Companion.SYSTEM +import okhttp3.internal.concurrent.TaskRunner +import okhttp3.internal.dns.LookupDnsCall +import okhttp3.internal.toCanonicalHost +import okio.ByteString +import okio.IOException /** - * A domain name service that resolves IP addresses for host names. Most applications will use the - * [system DNS service][SYSTEM], which is the default. Some applications may provide their own - * implementation to use a different DNS server, to prefer IPv6 addresses, to prefer IPv4 addresses, - * or to force a specific known IP address. + * Loads IP addresses and service metadata for a hostname. + * + * This interface is exclusively concerned with collecting the information necessary to establish + * new HTTP and HTTPS connections. Typical implementations will read `A` (IPv4), `AAAA` (IPv6) and + * `HTTPS` (service metadata) records only. Use a different API to read other record types, or to + * write DNS records. + * + * [Dns.SYSTEM] is the default implementation. * * Implementations of this interface must be safe for concurrent use. */ fun interface Dns { /** - * Returns the IP addresses of `hostname`, in the order they will be attempted by OkHttp. If a - * connection to an address fails, OkHttp will retry the connection with the next address until - * either a connection is made, the set of IP addresses is exhausted, or a limit is exceeded. + * Returns the IP addresses of `hostname`. + * + * If a connection to an address fails, OkHttp will retry the connection with the next address + * until either a connection is made, the set of IP addresses is exhausted, or a limit is + * exceeded. */ @Throws(UnknownHostException::class) fun lookup(hostname: String): List + /** Recursively resolve [request] to learn IP addresses and service metadata to connect. */ + fun newCall(request: Request): Call = LookupDnsCall(TaskRunner.INSTANCE, this, request) + + interface Call { + val request: Request + + fun enqueue(callback: Callback) + + /** This does nothing if the call is already complete. */ + fun cancel() + + fun isCanceled(): Boolean + } + + interface Callback { + /** + * @param last true if this is the last list of records for this address. That is a terminal + * event and no further calls to this callback will be made for this call. + * @param records a possibly-empty set of records received from the name server. + */ + fun onRecords( + call: Call, + last: Boolean, + records: List, + ) + + /** + * This is a terminal event and no further calls to this callback will be made for this call. + */ + fun onFailure( + call: Call, + e: IOException, + ) + } + + class Request + @JvmOverloads + constructor( + hostname: String, + port: Int = -1, + ) { + /** + * The host from a URL like `www.publicobject.com` or `ietf.org`. It may also be a + * Punycode-encoded name like `xn--n3h.net`. + * + * Hostnames must satisfy the following constraints: + * + * * All characters must be printable ASCII characters. + * * These characters are forbidden: `#`, `%`, `/`, `:`, `?`, `@`, `[`, `\`, and `]`. + * * The string length must be in [1..253]. + * * After splitting the string into `.`-separated labels, each label must be in [1..63]. A + * single trailing `.` is permitted. + */ + @get:JvmName("hostname") + val hostname: String = + hostname.toCanonicalHost() + ?: throw IllegalArgumentException("unexpected hostname: $hostname") + + /** + * The port to query for record types that support it. + * + * In an HTTPS DNS query, the port and hostname are combined like `_8443.api.example.com`. The + * port segment is omitted if it is 443. This scheme is called + * [AttrLeaf](https://www.rfc-editor.org/info/rfc8552/). + */ + @get:JvmName("port") + val port: Int = + when (port) { + -1 -> 443 + in 1..65535 -> port + else -> throw IllegalArgumentException("unexpected port: $port") + } + + override fun equals(other: Any?) = other is Request && other.hostname == hostname && other.port == port + + override fun hashCode() = (31 * hostname.hashCode()) + port + + override fun toString(): String = + when (port) { + 443 -> hostname + else -> "$hostname:$port" + } + } + + sealed class Record private constructor() { + /** + * The hostname that this record applies to. + * + * A single hostname (`api.example.com`) may be served by multiple supporting servers. For + * example, this is useful for geographic distribution (`us-east.api.example.com` and + * `eu-central.api.example.com`), and for change management (`blue.api.example.com`, + * `green.api.example.com`). + * + * This field will be different from [Request.hostname] if it is served by alternate hostname, + * as above. This will be that original hostname if no alternate hostnames are in use, or if the + * underlying DNS resolver doesn't expose that data. + */ + abstract val hostname: String + + /** An IPv4 or IPv6 address for this host. */ + class IpAddress( + hostname: String, + @get:JvmName("address") val address: InetAddress, + ) : Record() { + @get:JvmName("hostname") + override val hostname: String = + hostname.toCanonicalHost() + ?: throw IllegalArgumentException("unexpected hostname: $hostname") + + override fun equals(other: Any?) = other is IpAddress && other.hostname == hostname && other.address == address + + override fun hashCode() = (31 * hostname.hashCode()) + address.hashCode() + + override fun toString() = "$hostname/${address.hostAddress}" + } + + /** + * Advice from the hostname owner on how to connect to maximize compatibility and security. + * + * Each [ServiceMetadata] record may apply to all supporting servers, or to a subset of them. If + * multiple servers are in use (as in the `us-east.api.example.com` + * [example][Record.hostname]), those hostnames must be themselves resolved to get the IP + * addresses to connect to. If [ipAddressHints] is non-empty, those addresses are available + * without to an additional DNS round trip. (The additional round trip is still useful for + * completeness.) + * + * If this record is present on the DNS results for an `http://` request, the client should + * simulate a 307 redirect to the equivalent `https://` URL. From RFC 9460, part 9.5: + * + * If an HTTPS RR query for this "https" URL re RRs or any compatible ServiceMode HTTPS RRs, + * the client SHOULD behave as if it has received an HTTP 307 (Temporary Redirect) status code + * with this "https" URL in the "Location" field. + */ + class ServiceMetadata( + hostname: String, + /** + * The protocols supported by this server. When an input URL's hostname is served by multiple + * servers, use this to select a server that supports the client's available protocols. + * + * This value is created by composing two service params. For the "http" protocol, this takes + * the `HTTPS` record's `alpn` value and then add the default [Protocol.HTTP_1_1] unless + * `no-default-alpn` is present. + * + * If the service returns an unrecognized [Protocol], that element is discarded. + */ + alpnIds: List? = null, + /** + * The socket should connect to this port, even if the URL has a different port. This will be + * [Request.port] unless an override is specified. + */ + port: Int = -1, + /** + * The IP of the servers known to support this record. If empty, assume all records with the + * same [hostname] also support this configuration. + */ + ipAddressHints: List = listOf(), + /** + * The Encrypted Client Hello (ECH) data. This is encoded according to RFC 9848 and RFC 9849. + */ + @get:JvmName("echConfigList") + val echConfigList: ByteString? = null, + ) : Record() { + @get:JvmName("hostname") + override val hostname: String = + hostname.toCanonicalHost() + ?: throw IllegalArgumentException("unexpected hostname: $hostname") + + @get:JvmName("port") + val port: Int = + when (port) { + -1 -> 443 + in 1..65535 -> port + else -> throw IllegalArgumentException("unexpected port: $port") + } + + @get:JvmName("alpnIds") + val alpnIds: List? = alpnIds?.toList() // Defensive copy. + + @get:JvmName("ipAddressHints") + val ipAddressHints: List = ipAddressHints.toList() // Defensive copy. + + override fun equals(other: Any?): Boolean = + other is ServiceMetadata && + other.hostname == hostname && + other.alpnIds == alpnIds && + other.port == port && + other.ipAddressHints == ipAddressHints && + other.echConfigList == echConfigList + + override fun hashCode(): Int { + var result = 17 + result = 31 * result + hostname.hashCode() + result = 31 * result + alpnIds.hashCode() + result = 31 * result + port + result = 31 * result + ipAddressHints.hashCode() + result = 31 * result + echConfigList.hashCode() + return result + } + + override fun toString(): String = + buildString(32) { + append("ServiceMetadata{") + append(hostname) + if (alpnIds != null) { + append(", alpnIds=") + append(alpnIds) + } + if (port != 443) { + append(", port=") + append(port) + } + if (ipAddressHints.isNotEmpty()) { + append(", ipAddressHints=[") + append(ipAddressHints.joinToString { it.hostAddress }) + append("]") + } + if (echConfigList != null) { + append(", echConfigList=") + append(echConfigList.hex()) + } + append("}") + } + } + } + companion object { /** * A DNS that uses [InetAddress.getAllByName] to ask the underlying operating system to diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dns2.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dns2.kt deleted file mode 100644 index f27b0fc48bdd..000000000000 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dns2.kt +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2026 OkHttp Authors - * - * 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 - * - * http://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 okhttp3 - -import java.net.InetAddress -import okhttp3.internal.toCanonicalHost -import okio.ByteString -import okio.IOException - -/** - * Loads IP addresses and service metadata for a hostname. - * - * This interface is exclusively concerned with collecting the information necessary to establish - * new HTTP and HTTPS connections. Typical implementations will read `A` (IPv4), `AAAA` (IPv6) and - * `HTTPS` (service metadata) records only. Use a different API to read other record types, or to - * write DNS records. - * - * Implementations of this interface must be safe for concurrent use. - */ -interface Dns2 { - /** Recursively resolve [request] to learn IP addresses and service metadata to connect. */ - fun newCall(request: Request): Call - - interface Call { - val request: Request - - fun enqueue(callback: Callback) - - /** This does nothing if the call is already complete. */ - fun cancel() - - fun isCanceled(): Boolean - } - - interface Callback { - /** - * @param last true if this is the last list of records for this address. That is a terminal - * event and no further calls to this callback will be made for this call. - * @param records a possibly-empty set of records received from the name server. - */ - fun onRecords( - call: Call, - last: Boolean, - records: List, - ) - - /** - * This is a terminal event and no further calls to this callback will be made for this call. - */ - fun onFailure( - call: Call, - e: IOException, - ) - } - - class Request - @JvmOverloads - constructor( - hostname: String, - port: Int = -1, - ) { - /** - * The host from a URL like `www.publicobject.com` or `ietf.org`. It may also be a - * Punycode-encoded name like `xn--n3h.net`. - * - * Hostnames must satisfy the following constraints: - * - * * All characters must be printable ASCII characters. - * * These characters are forbidden: `#`, `%`, `/`, `:`, `?`, `@`, `[`, `\`, and `]`. - * * The string length must be in [1..253]. - * * After splitting the string into `.`-separated labels, each label must be in [1..63]. A - * single trailing `.` is permitted. - */ - @get:JvmName("hostname") - val hostname: String = - hostname.toCanonicalHost() - ?: throw IllegalArgumentException("unexpected hostname: $hostname") - - /** - * The port to query for record types that support it. - * - * In an HTTPS DNS query, the port and hostname are combined like `_8443.api.example.com`. The - * port segment is omitted if it is 443. This scheme is called - * [AttrLeaf](https://www.rfc-editor.org/info/rfc8552/). - */ - @get:JvmName("port") - val port: Int = - when (port) { - -1 -> 443 - in 1..65535 -> port - else -> throw IllegalArgumentException("unexpected port: $port") - } - - override fun equals(other: Any?) = other is Request && other.hostname == hostname && other.port == port - - override fun hashCode() = (31 * hostname.hashCode()) + port - - override fun toString(): String = - when (port) { - 443 -> hostname - else -> "$hostname:$port" - } - } - - sealed class Record private constructor() { - /** - * The hostname that this record applies to. - * - * A single hostname (`api.example.com`) may be served by multiple supporting servers. For - * example, this is useful for geographic distribution (`us-east.api.example.com` and - * `eu-central.api.example.com`), and for change management (`blue.api.example.com`, - * `green.api.example.com`). - * - * This field will be different from [Request.hostname] if it is served by alternate hostname, - * as above. This will be that original hostname if no alternate hostnames are in use, or if the - * underlying DNS resolver doesn't expose that data. - */ - abstract val hostname: String - - /** An IPv4 or IPv6 address for this host. */ - class IpAddress( - hostname: String, - @get:JvmName("address") val address: InetAddress, - ) : Record() { - @get:JvmName("hostname") - override val hostname: String = - hostname.toCanonicalHost() - ?: throw IllegalArgumentException("unexpected hostname: $hostname") - - override fun equals(other: Any?) = other is IpAddress && other.hostname == hostname && other.address == address - - override fun hashCode() = (31 * hostname.hashCode()) + address.hashCode() - - override fun toString() = "$hostname/${address.hostAddress}" - } - - /** - * Advice from the hostname owner on how to connect to maximize compatibility and security. - * - * Each [ServiceMetadata] record may apply to all supporting servers, or to a subset of them. If - * multiple servers are in use (as in the `us-east.api.example.com` - * [example][Record.hostname]), those hostnames must be themselves resolved to get the IP - * addresses to connect to. If [ipAddressHints] is non-empty, those addresses are available - * without to an additional DNS round trip. (The additional round trip is still useful for - * completeness.) - * - * If this record is present on the DNS results for an `http://` request, the client should - * simulate a 307 redirect to the equivalent `https://` URL. From RFC 9460, part 9.5: - * - * If an HTTPS RR query for this "https" URL re RRs or any compatible ServiceMode HTTPS RRs, - * the client SHOULD behave as if it has received an HTTP 307 (Temporary Redirect) status code - * with this "https" URL in the "Location" field. - */ - class ServiceMetadata( - hostname: String, - /** - * The protocols supported by this server. When an input URL's hostname is served by multiple - * servers, use this to select a server that supports the client's available protocols. - * - * This value is created by composing two service params. For the "http" protocol, this takes - * the `HTTPS` record's `alpn` value and then add the default [Protocol.HTTP_1_1] unless - * `no-default-alpn` is present. - * - * If the service returns an unrecognized [Protocol], that element is discarded. - */ - alpnIds: List? = null, - /** - * The socket should connect to this port, even if the URL has a different port. This will be - * [Request.port] unless an override is specified. - */ - port: Int = -1, - /** - * The IP of the servers known to support this record. If empty, assume all records with the - * same [hostname] also support this configuration. - */ - ipAddressHints: List = listOf(), - /** - * The Encrypted Client Hello (ECH) data. This is encoded according to RFC 9848 and RFC 9849. - */ - @get:JvmName("echConfigList") - val echConfigList: ByteString? = null, - ) : Record() { - @get:JvmName("hostname") - override val hostname: String = - hostname.toCanonicalHost() - ?: throw IllegalArgumentException("unexpected hostname: $hostname") - - @get:JvmName("port") - val port: Int = - when (port) { - -1 -> 443 - in 1..65535 -> port - else -> throw IllegalArgumentException("unexpected port: $port") - } - - @get:JvmName("alpnIds") - val alpnIds: List? = alpnIds?.toList() // Defensive copy. - - @get:JvmName("ipAddressHints") - val ipAddressHints: List = ipAddressHints.toList() // Defensive copy. - - override fun equals(other: Any?): Boolean = - other is ServiceMetadata && - other.hostname == hostname && - other.alpnIds == alpnIds && - other.port == port && - other.ipAddressHints == ipAddressHints && - other.echConfigList == echConfigList - - override fun hashCode(): Int { - var result = 17 - result = 31 * result + hostname.hashCode() - result = 31 * result + alpnIds.hashCode() - result = 31 * result + port - result = 31 * result + ipAddressHints.hashCode() - result = 31 * result + echConfigList.hashCode() - return result - } - - override fun toString(): String = - buildString(32) { - append("ServiceMetadata{") - append(hostname) - if (alpnIds != null) { - append(", alpnIds=") - append(alpnIds) - } - if (port != 443) { - append(", port=") - append(port) - } - if (ipAddressHints.isNotEmpty()) { - append(", ipAddressHints=[") - append(ipAddressHints.joinToString { it.hostAddress }) - append("]") - } - if (echConfigList != null) { - append(", echConfigList=") - append(echConfigList.hex()) - } - append("}") - } - } - } -} diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-LookupDnsCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-LookupDnsCall.kt new file mode 100644 index 000000000000..63bba954dd88 --- /dev/null +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-LookupDnsCall.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2026 OkHttp Authors + * + * 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 + * + * http://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. + */ +@file:Suppress("ktlint:standard:filename") +@file:OptIn(OkHttpInternalApi::class) + +package okhttp3.internal.dns + +import java.io.IOException +import java.net.UnknownHostException +import java.util.concurrent.atomic.AtomicReference +import okhttp3.Dns +import okhttp3.internal.OkHttpInternalApi +import okhttp3.internal.concurrent.Task +import okhttp3.internal.concurrent.TaskRunner +import okhttp3.internal.testAndSet + +/** + * Adapts the blocking [Dns.lookup] function (introduced in 2015) to the non-blocking [Dns.newCall] + * API (introduced in 2026), using [TaskRunner] to provide a thread for background execution. + * + * When canceled, the callback is immediately notified but the in-flight call is run to completion + * and discarded. + */ +internal class LookupDnsCall( + private val taskRunner: TaskRunner, + private val delegate: Dns, + override val request: Dns.Request, +) : Task("${request.hostname} dns", cancelable = false), + Dns.Call { + private val state = AtomicReference(State.Idle) + + override fun enqueue(callback: Dns.Callback) { + val previous = state.testAndSet(State.Running(callback)) { it is State.Idle } + when (previous) { + is State.Idle -> taskRunner.newQueue().schedule(this) + is State.Running, State.Complete -> throw IllegalStateException("already enqueued") + State.Canceled -> callback.onFailure(this, IOException("canceled")) + } + } + + override fun cancel() { + val previous = state.testAndSet(State.Canceled) { it is State.Running || it == State.Idle } + (previous as? State.Running)?.callback?.onFailure(this, IOException("canceled")) + } + + override fun isCanceled() = state.get() is State.Canceled + + override fun runOnce(): Long { + try { + val inetAddresses = delegate.lookup(request.hostname) + val previous = state.testAndSet(State.Complete) { it is State.Running } + (previous as? State.Running)?.callback?.onRecords( + call = this, + last = true, + records = inetAddresses.map { Dns.Record.IpAddress(request.hostname, it) }, + ) + } catch (e: UnknownHostException) { + val previous = state.testAndSet(State.Complete) { it is State.Running } + (previous as? State.Running)?.callback?.onFailure( + call = this, + e = e, + ) + } + return -1L + } + + private sealed interface State { + object Idle : State + + class Running( + val callback: Dns.Callback, + ) : State + + object Complete : State + + object Canceled : State + } +} diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/DnsAsDns2.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/DnsAsDns2.kt deleted file mode 100644 index 878878e705e2..000000000000 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/DnsAsDns2.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2026 OkHttp Authors - * - * 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 - * - * http://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. - */ -@file:OptIn(OkHttpInternalApi::class) - -package okhttp3.internal.dns - -import java.io.IOException -import java.net.UnknownHostException -import java.util.concurrent.atomic.AtomicReference -import okhttp3.Dns -import okhttp3.Dns2 -import okhttp3.internal.OkHttpInternalApi -import okhttp3.internal.concurrent.Task -import okhttp3.internal.concurrent.TaskRunner -import okhttp3.internal.testAndSet - -/** - * Adapts the blocking [Dns] interface to the non-blocking [Dns2] interface, using [TaskRunner] to - * provide a thread for background execution. - * - * When canceled, the callback is immediately notified but the in-flight call is run to completion - * and discarded. - */ -internal class DnsAsDns2( - private val taskRunner: TaskRunner, - private val delegate: Dns, -) : Dns2, - Dns by delegate { - override fun newCall(request: Dns2.Request): Dns2.Call = Call(request) - - override fun equals(other: Any?) = other is DnsAsDns2 && other.delegate == delegate - - override fun hashCode() = 53 * delegate.hashCode() - - override fun toString() = "DnsAsDns2($delegate)" - - private inner class Call( - override val request: Dns2.Request, - ) : Task("DnsAsDns2", cancelable = false), - Dns2.Call { - private val state = AtomicReference(State.Idle) - - override fun enqueue(callback: Dns2.Callback) { - val previous = state.testAndSet(State.Running(callback)) { it is State.Idle } - when (previous) { - is State.Idle -> taskRunner.newQueue().schedule(this) - is State.Running, State.Complete -> throw IllegalStateException("already enqueued") - State.Canceled -> callback.onFailure(this, IOException("canceled")) - } - } - - override fun cancel() { - val previous = state.testAndSet(State.Canceled) { it is State.Running || it == State.Idle } - (previous as? State.Running)?.callback?.onFailure(this, IOException("canceled")) - } - - override fun isCanceled() = state.get() is State.Canceled - - override fun runOnce(): Long { - try { - val inetAddresses = delegate.lookup(request.hostname) - val previous = state.testAndSet(State.Complete) { it is State.Running } - (previous as? State.Running)?.callback?.onRecords( - call = this, - last = true, - records = inetAddresses.map { Dns2.Record.IpAddress(request.hostname, it) }, - ) - } catch (e: UnknownHostException) { - val previous = state.testAndSet(State.Complete) { it is State.Running } - (previous as? State.Running)?.callback?.onFailure( - call = this, - e = e, - ) - } - return -1L - } - } - - private sealed interface State { - object Idle : State - - class Running( - val callback: Dns2.Callback, - ) : State - - object Complete : State - - object Canceled : State - } -} diff --git a/okhttp/src/commonTest/kotlin/okhttp3/Dns2Test.kt b/okhttp/src/commonTest/kotlin/okhttp3/DnsValuesTest.kt similarity index 63% rename from okhttp/src/commonTest/kotlin/okhttp3/Dns2Test.kt rename to okhttp/src/commonTest/kotlin/okhttp3/DnsValuesTest.kt index 6bfc54c36f2e..cff0dd4fd62a 100644 --- a/okhttp/src/commonTest/kotlin/okhttp3/Dns2Test.kt +++ b/okhttp/src/commonTest/kotlin/okhttp3/DnsValuesTest.kt @@ -24,65 +24,65 @@ import kotlin.test.Test import kotlin.test.assertFailsWith import okio.ByteString.Companion.encodeUtf8 -class Dns2Test { +class DnsValuesTest { @Test fun `request canonical hostname`() { - assertThat(Dns2.Request(hostname = "EXAMPLE.COM").hostname) + assertThat(Dns.Request(hostname = "EXAMPLE.COM").hostname) .isEqualTo("example.com") } @Test fun `request invalid hostname`() { assertFailsWith { - Dns2.Request(hostname = "") + Dns.Request(hostname = "") } assertFailsWith { - Dns2.Request(hostname = "a".repeat(64)) + Dns.Request(hostname = "a".repeat(64)) } assertFailsWith { - Dns2.Request(hostname = ("${"a".repeat(55)}.".repeat(5))) + Dns.Request(hostname = ("${"a".repeat(55)}.".repeat(5))) } assertFailsWith { - Dns2.Request(hostname = "a?b") + Dns.Request(hostname = "a?b") } } @Test fun `request invalid port`() { assertFailsWith { - Dns2.Request(hostname = "example.com", port = -2) + Dns.Request(hostname = "example.com", port = -2) } assertFailsWith { - Dns2.Request(hostname = "example.com", port = 0) + Dns.Request(hostname = "example.com", port = 0) } assertFailsWith { - Dns2.Request(hostname = "example.com", port = 65536) + Dns.Request(hostname = "example.com", port = 65536) } } @Test fun `default port`() { - assertThat(Dns2.Request(hostname = "example.com", port = -1).port) + assertThat(Dns.Request(hostname = "example.com", port = -1).port) .isEqualTo(443) } @Test fun `request equals and hashCode`() { - assertThat(Dns2.Request(hostname = "example.com", port = 443)) - .isEqualTo(Dns2.Request(hostname = "example.com", port = 443)) - assertThat(Dns2.Request(hostname = "example.com", port = 443).hashCode()) - .isEqualTo(Dns2.Request(hostname = "example.com", port = 443).hashCode()) - assertThat(Dns2.Request(hostname = "example.com", port = 443)) - .isNotEqualTo(Dns2.Request(hostname = "example.com", port = 8443)) - assertThat(Dns2.Request(hostname = "example.com", port = 443)) - .isNotEqualTo(Dns2.Request(hostname = "example.net", port = 443)) + assertThat(Dns.Request(hostname = "example.com", port = 443)) + .isEqualTo(Dns.Request(hostname = "example.com", port = 443)) + assertThat(Dns.Request(hostname = "example.com", port = 443).hashCode()) + .isEqualTo(Dns.Request(hostname = "example.com", port = 443).hashCode()) + assertThat(Dns.Request(hostname = "example.com", port = 443)) + .isNotEqualTo(Dns.Request(hostname = "example.com", port = 8443)) + assertThat(Dns.Request(hostname = "example.com", port = 443)) + .isNotEqualTo(Dns.Request(hostname = "example.net", port = 443)) } @Test fun `request to string`() { - assertThat(Dns2.Request(hostname = "example.com", port = 443).toString()) + assertThat(Dns.Request(hostname = "example.com", port = 443).toString()) .isEqualTo("example.com") - assertThat(Dns2.Request(hostname = "example.com", port = 8443).toString()) + assertThat(Dns.Request(hostname = "example.com", port = 8443).toString()) .isEqualTo("example.com:8443") } @@ -90,16 +90,16 @@ class Dns2Test { fun `ip address record invalid hostname`() { val address = InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1)) assertFailsWith { - Dns2.Record.IpAddress(hostname = "", address = address) + Dns.Record.IpAddress(hostname = "", address = address) } assertFailsWith { - Dns2.Record.IpAddress(hostname = "a".repeat(64), address = address) + Dns.Record.IpAddress(hostname = "a".repeat(64), address = address) } assertFailsWith { - Dns2.Record.IpAddress(hostname = ("${"a".repeat(55)}.".repeat(5)), address = address) + Dns.Record.IpAddress(hostname = ("${"a".repeat(55)}.".repeat(5)), address = address) } assertFailsWith { - Dns2.Record.IpAddress(hostname = "a?b", address = address) + Dns.Record.IpAddress(hostname = "a?b", address = address) } } @@ -107,60 +107,60 @@ class Dns2Test { fun `ip address equals and hashCode`() { val address1 = InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1)) val address2 = InetAddress.getByAddress(byteArrayOf(127, 0, 0, 2)) - assertThat(Dns2.Record.IpAddress(hostname = "example.com", address = address1)) - .isEqualTo(Dns2.Record.IpAddress(hostname = "example.com", address = address1)) - assertThat(Dns2.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) - .isEqualTo(Dns2.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) - assertThat(Dns2.Record.IpAddress(hostname = "example.com", address = address1)) - .isNotEqualTo(Dns2.Record.IpAddress(hostname = "example.com", address = address2)) - assertThat(Dns2.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) - .isNotEqualTo(Dns2.Record.IpAddress(hostname = "example.com", address = address2).hashCode()) - assertThat(Dns2.Record.IpAddress(hostname = "example.com", address = address1)) - .isNotEqualTo(Dns2.Record.IpAddress(hostname = "example.net", address = address1)) - assertThat(Dns2.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) - .isNotEqualTo(Dns2.Record.IpAddress(hostname = "example.net", address = address1).hashCode()) + assertThat(Dns.Record.IpAddress(hostname = "example.com", address = address1)) + .isEqualTo(Dns.Record.IpAddress(hostname = "example.com", address = address1)) + assertThat(Dns.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) + .isEqualTo(Dns.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) + assertThat(Dns.Record.IpAddress(hostname = "example.com", address = address1)) + .isNotEqualTo(Dns.Record.IpAddress(hostname = "example.com", address = address2)) + assertThat(Dns.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) + .isNotEqualTo(Dns.Record.IpAddress(hostname = "example.com", address = address2).hashCode()) + assertThat(Dns.Record.IpAddress(hostname = "example.com", address = address1)) + .isNotEqualTo(Dns.Record.IpAddress(hostname = "example.net", address = address1)) + assertThat(Dns.Record.IpAddress(hostname = "example.com", address = address1).hashCode()) + .isNotEqualTo(Dns.Record.IpAddress(hostname = "example.net", address = address1).hashCode()) } @Test fun `ip address toString`() { val address = InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1)) - assertThat(Dns2.Record.IpAddress(hostname = "example.com", address = address).toString()) + assertThat(Dns.Record.IpAddress(hostname = "example.com", address = address).toString()) .isEqualTo("example.com/127.0.0.1") } @Test fun `service metadata invalid hostname`() { assertFailsWith { - Dns2.Record.ServiceMetadata(hostname = "") + Dns.Record.ServiceMetadata(hostname = "") } assertFailsWith { - Dns2.Record.ServiceMetadata(hostname = "a".repeat(64)) + Dns.Record.ServiceMetadata(hostname = "a".repeat(64)) } assertFailsWith { - Dns2.Record.ServiceMetadata(hostname = ("${"a".repeat(55)}.".repeat(5))) + Dns.Record.ServiceMetadata(hostname = ("${"a".repeat(55)}.".repeat(5))) } assertFailsWith { - Dns2.Record.ServiceMetadata(hostname = "a?b") + Dns.Record.ServiceMetadata(hostname = "a?b") } } @Test fun `service metadata invalid port`() { assertFailsWith { - Dns2.Record.ServiceMetadata(hostname = "example.com", port = -2) + Dns.Record.ServiceMetadata(hostname = "example.com", port = -2) } assertFailsWith { - Dns2.Record.ServiceMetadata(hostname = "example.com", port = 0) + Dns.Record.ServiceMetadata(hostname = "example.com", port = 0) } assertFailsWith { - Dns2.Record.ServiceMetadata(hostname = "example.com", port = 65536) + Dns.Record.ServiceMetadata(hostname = "example.com", port = 65536) } } @Test fun `service metadata equals and hashCode`() { val original = - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", port = 443, alpnIds = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2), @@ -168,7 +168,7 @@ class Dns2Test { echConfigList = "hello I am ECH config".encodeUtf8(), ) assertThat(original).isEqualTo( - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", port = 443, alpnIds = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2), @@ -177,7 +177,7 @@ class Dns2Test { ), ) assertThat(original.hashCode()).isEqualTo( - Dns2.Record + Dns.Record .ServiceMetadata( hostname = "example.com", port = 443, @@ -187,35 +187,35 @@ class Dns2Test { ).hashCode(), ) assertThat(original).isNotIn( - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.net", port = 443, alpnIds = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2), ipAddressHints = listOf(InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1))), echConfigList = "hello I am ECH config".encodeUtf8(), ), - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", port = 8443, alpnIds = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2), ipAddressHints = listOf(InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1))), echConfigList = "hello I am ECH config".encodeUtf8(), ), - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", port = 443, alpnIds = listOf(Protocol.HTTP_1_1), ipAddressHints = listOf(InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1))), echConfigList = "hello I am ECH config".encodeUtf8(), ), - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", port = 443, alpnIds = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2), ipAddressHints = listOf(InetAddress.getByAddress(byteArrayOf(127, 0, 0, 2))), echConfigList = "hello I am ECH config".encodeUtf8(), ), - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", port = 443, alpnIds = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2), @@ -228,14 +228,14 @@ class Dns2Test { @Test fun `service metadata toString`() { val empty = - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", ) assertThat(empty.toString()).isEqualTo( "ServiceMetadata{example.com}", ) val full = - Dns2.Record.ServiceMetadata( + Dns.Record.ServiceMetadata( hostname = "example.com", port = 443, alpnIds = listOf(Protocol.HTTP_1_1, Protocol.HTTP_2), diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/DnsAsDns2Test.kt b/okhttp/src/jvmTest/kotlin/okhttp3/LookupDnsCallTest.kt similarity index 80% rename from okhttp/src/jvmTest/kotlin/okhttp3/DnsAsDns2Test.kt rename to okhttp/src/jvmTest/kotlin/okhttp3/LookupDnsCallTest.kt index d27b8707e8cc..591560d81fa3 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/DnsAsDns2Test.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/LookupDnsCallTest.kt @@ -21,24 +21,22 @@ import assertk.assertions.isNull import java.net.InetAddress import java.net.UnknownHostException import java.util.concurrent.LinkedBlockingDeque +import kotlin.test.Test import okhttp3.internal.concurrent.TaskFaker -import okhttp3.internal.dns.DnsAsDns2 +import okhttp3.internal.concurrent.TaskRunner +import okhttp3.internal.dns.LookupDnsCall import okio.IOException -import org.junit.jupiter.api.Test -class DnsAsDns2Test { +class LookupDnsCallTest { private val dns = BlockingFakeDns() private val taskFaker = TaskFaker() - private val dns2 = - DnsAsDns2( - taskRunner = taskFaker.taskRunner, - delegate = dns, - ) + private val taskRunner: TaskRunner + get() = taskFaker.taskRunner private val callback = RecordingCallback() @Test fun `happy path`() { - val call = dns2.newCall(Dns2.Request("lysine.dev")) + val call = LookupDnsCall(taskRunner, dns, Dns.Request("lysine.dev")) call.enqueue(callback) val address1 = InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1)) @@ -52,7 +50,7 @@ class DnsAsDns2Test { @Test fun `delegate throws`() { - val call = dns2.newCall(Dns2.Request("lysine.dev")) + val call = LookupDnsCall(taskRunner, dns, Dns.Request("lysine.dev")) call.enqueue(callback) dns.put(UnknownHostException("boom!")) @@ -65,7 +63,7 @@ class DnsAsDns2Test { @Test fun `call canceled before callback is enqueued`() { - val call = dns2.newCall(Dns2.Request("lysine.dev")) + val call = LookupDnsCall(taskRunner, dns, Dns.Request("lysine.dev")) call.cancel() call.enqueue(callback) @@ -73,10 +71,10 @@ class DnsAsDns2Test { .isEqualTo("onFailure(canceled)") } - /** Most importantly, the cancel is delivered to the [Dns2.Callback] immediately. */ + /** Most importantly, the cancel is delivered to the [Dns.Callback] immediately. */ @Test fun `call canceled before results are returned`() { - val call = dns2.newCall(Dns2.Request("lysine.dev")) + val call = LookupDnsCall(taskRunner, dns, Dns.Request("lysine.dev")) call.enqueue(callback) call.cancel() @@ -89,7 +87,7 @@ class DnsAsDns2Test { @Test fun `call canceled after results are returned`() { - val call = dns2.newCall(Dns2.Request("lysine.dev")) + val call = LookupDnsCall(taskRunner, dns, Dns.Request("lysine.dev")) call.enqueue(callback) val address1 = InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1)) @@ -106,7 +104,7 @@ class DnsAsDns2Test { @Test fun `results after cancel are not delivered`() { - val call = dns2.newCall(Dns2.Request("lysine.dev")) + val call = LookupDnsCall(taskRunner, dns, Dns.Request("lysine.dev")) call.enqueue(callback) call.cancel() @@ -123,7 +121,7 @@ class DnsAsDns2Test { @Test fun `exception after cancel is not delivered`() { - val call = dns2.newCall(Dns2.Request("lysine.dev")) + val call = LookupDnsCall(taskRunner, dns, Dns.Request("lysine.dev")) call.enqueue(callback) call.cancel() @@ -151,19 +149,19 @@ class DnsAsDns2Test { } } - class RecordingCallback : Dns2.Callback { + class RecordingCallback : Dns.Callback { val events = LinkedBlockingDeque() override fun onRecords( - call: Dns2.Call, + call: Dns.Call, last: Boolean, - records: List, + records: List, ) { events.put("onRecords(last=$last, records=$records)") } override fun onFailure( - call: Dns2.Call, + call: Dns.Call, e: IOException, ) { events.put("onFailure(${e.message})")