diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 365a70b22b..3486c8e707 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -164,7 +164,7 @@ jobs: shell: bash run: | # FIXME K2. Re-enable warnings as errors after this warning is removed: https://youtrack.jetbrains.com/issue/KT-68532 - # echo "kotlinWarningsAsErrors=true" >> $GITHUB_WORKSPACE/local.properties + # echo "kotlinWarningsAsErrors=true" >> $GITHUB_WORKSPACE/local.properties ./gradlew -Paws.kotlin.native=false apiCheck ./gradlew -Paws.kotlin.native=false test jvmTest diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1815c1cbd0..52e3b8e587 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin-version = "2.1.0" dokka-version = "1.9.10" -aws-kotlin-repo-tools-version = "0.4.22-kn" +aws-kotlin-repo-tools-version = "0.4.24-kn" # libs coroutines-version = "1.9.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3499ded5c1..9bf7bd3397 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/runtime/build.gradle.kts b/runtime/build.gradle.kts index bea1350f51..2ff2032916 100644 --- a/runtime/build.gradle.kts +++ b/runtime/build.gradle.kts @@ -5,6 +5,7 @@ import aws.sdk.kotlin.gradle.dsl.configurePublishing import aws.sdk.kotlin.gradle.kmp.* import aws.sdk.kotlin.gradle.util.typedProp +import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { @@ -112,3 +113,5 @@ subprojects { } } } + +configureIosSimulatorTasks() diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/HostResolverTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/HostResolverTest.kt new file mode 100644 index 0000000000..3bad624976 --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/HostResolverTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net + +import kotlinx.coroutines.test.runTest +import kotlin.test.* + +class HostResolverTest { + @Test + fun testResolveLocalhost() = runTest { + val addresses = HostResolver.Default.resolve("localhost") + assertTrue(addresses.isNotEmpty()) + + addresses.forEach { addr -> + assertEquals("localhost", addr.hostname) + + val localHostAddr = when (addr.address) { + is IpV4Addr -> IpV4Addr.LOCALHOST + is IpV6Addr -> IpV6Addr.LOCALHOST + } + assertEquals(addr.address, localHostAddr) + } + } + + @Test + fun testResolveIpv4Address() = runTest { + val addresses = HostResolver.Default.resolve("127.0.0.1") + assertTrue(addresses.isNotEmpty()) + + addresses.forEach { addr -> + assertTrue(addr.address is IpV4Addr) + assertContentEquals(byteArrayOf(127, 0, 0, 1), addr.address.octets) + } + } + + @Test + fun testResolveIpv6Address() = runTest { + val addresses = HostResolver.Default.resolve("::1") + assertTrue(addresses.isNotEmpty()) + + addresses.forEach { addr -> + assertTrue(addr.address is IpV6Addr) + val expectedBytes = ByteArray(16) { 0 } + expectedBytes[15] = 1 + assertContentEquals(expectedBytes, addr.address.octets) + } + } + + @Test + fun testResolveExampleDomain() = runTest { + val addresses = HostResolver.Default.resolve("example.com") + assertNotNull(addresses) + assertTrue(addresses.isNotEmpty()) + + addresses.forEach { addr -> + assertEquals("example.com", addr.hostname) + when (val ip = addr.address) { + is IpV4Addr -> assertEquals(4, ip.octets.size) + is IpV6Addr -> assertEquals(16, ip.octets.size) + } + } + } + + @Test + fun testResolveInvalidDomain() = runTest { + assertFails { + HostResolver.Default.resolve("this-domain-definitely-does-not-exist-12345.local") + } + } + + @Test + fun testNoopMethods() { + // Test no-op methods don't throw + val dummyAddr = HostAddress("test.com", IpV4Addr(ByteArray(4))) + val resolver = HostResolver.Default + resolver.reportFailure(dummyAddr) + resolver.purgeCache(null) + resolver.purgeCache(dummyAddr) + } +} diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt index 4b8fd7e7ae..9224932ef7 100644 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt @@ -4,19 +4,59 @@ */ package aws.smithy.kotlin.runtime.net -import aws.smithy.kotlin.runtime.InternalApi +import kotlinx.cinterop.* +import platform.posix.* internal actual object DefaultHostResolver : HostResolver { - actual override suspend fun resolve(hostname: String): List { - TODO("Not yet implemented") + actual override suspend fun resolve(hostname: String): List = memScoped { + val hints = alloc().apply { + ai_family = AF_UNSPEC // Allow both IPv4 and IPv6 + ai_socktype = SOCK_STREAM // TCP stream sockets + ai_flags = AI_PASSIVE // For wildcard IP address + } + + val result = allocPointerTo() + + try { + // Perform the DNS lookup + val status = getaddrinfo(hostname, null, hints.ptr, result.ptr) + check(status == 0) { "Failed to resolve host $hostname: ${gai_strerror(status)?.toKString()}" } + + return generateSequence(result.value) { it.pointed.ai_next } + .map { it.pointed.ai_addr!!.pointed.toIpAddr() } + .map { HostAddress(hostname, it) } + .toList() + } finally { + freeaddrinfo(result.value) + } + } + + @OptIn(UnsafeNumber::class) + private fun sockaddr.toIpAddr(): IpAddr { + val (size, addrPtr, constructor) = when (sa_family.toInt()) { + AF_INET -> Triple( + 4, + reinterpret().sin_addr.ptr, + { bytes: ByteArray -> IpV4Addr(bytes) }, + ) + AF_INET6 -> Triple( + 16, + reinterpret().sin6_addr.ptr, + { bytes: ByteArray -> IpV6Addr(bytes) }, + ) + else -> throw IllegalArgumentException("Unsupported sockaddr family $sa_family") + } + + val ipBytes = ByteArray(size) + memcpy(ipBytes.refTo(0), addrPtr, size.toULong()) + return constructor(ipBytes) } actual override fun reportFailure(addr: HostAddress) { - TODO("Not yet implemented") + // No-op, same as JVM implementation } - @InternalApi actual override fun purgeCache(addr: HostAddress?) { - TODO("Not yet implemented") + // No-op, same as JVM implementation } }