diff --git a/.brazil.json b/.brazil.json index 7de96869c6..ad60e171a6 100644 --- a/.brazil.json +++ b/.brazil.json @@ -26,7 +26,7 @@ "software.amazon.smithy:smithy-waiters:1.*": "Maven-software-amazon-smithy_smithy-waiters-1.x", "software.amazon.smithy:smithy-rules-engine:1.*": "Maven-software-amazon-smithy_smithy-rules-engine-1.x", "software.amazon.smithy:smithy-smoke-test-traits:1.*": "Maven-software-amazon-smithy_smithy-smoke-test-traits-1.x", - "org.jsoup:jsoup:1.19.*": "Maven-jsoup-1.19.x" + "org.jsoup:jsoup:1.*": "Maven-jsoup-1.x" }, "packageHandlingRules": { "versioning": { diff --git a/.changes/7e4f8c08-941c-4ea5-91b3-4c284746c453.json b/.changes/7e4f8c08-941c-4ea5-91b3-4c284746c453.json new file mode 100644 index 0000000000..2b126708dd --- /dev/null +++ b/.changes/7e4f8c08-941c-4ea5-91b3-4c284746c453.json @@ -0,0 +1,5 @@ +{ + "id": "7e4f8c08-941c-4ea5-91b3-4c284746c453", + "type": "documentation", + "description": "Improve documentation for [`StandardRetryPolicy`](https://docs.aws.amazon.com/smithy-kotlin/api/latest/runtime-core/aws.smithy.kotlin.runtime.retries.policy/-standard-retry-policy/)" +} \ No newline at end of file diff --git a/.github/workflows/artifact-size-metrics.yml b/.github/workflows/artifact-size-metrics.yml index fce601e0f5..8c22752455 100644 --- a/.github/workflows/artifact-size-metrics.yml +++ b/.github/workflows/artifact-size-metrics.yml @@ -1,6 +1,6 @@ name: Artifact Size Metrics on: - pull_request_target: + pull_request: types: [ opened, synchronize, reopened, labeled, unlabeled ] branches: - main @@ -32,7 +32,7 @@ jobs: role-to-assume: ${{ secrets.CI_AWS_ROLE_ARN }} aws-region: us-west-2 - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main - name: Generate Artifact Size Metrics run: ./gradlew artifactSizeMetrics - name: Save Artifact Size Metrics @@ -40,39 +40,37 @@ jobs: - name: Put Artifact Size Metrics in CloudWatch run: ./gradlew putArtifactSizeMetricsInCloudWatch -Prelease=${{ github.event.release.tag_name }} size-check: - if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - name: Checkout Sources uses: actions/checkout@v4 with: path: smithy-kotlin + - name: Setup build uses: ./smithy-kotlin/.github/actions/setup-build - - name: Configure JDK - uses: actions/setup-java@v3 - with: - distribution: 'corretto' - java-version: 17 - cache: 'gradle' + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.CI_AWS_ROLE_ARN }} aws-region: us-west-2 + - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main with: working-directory: smithy-kotlin + - name: Generate Artifact Size Metrics run: ./gradlew -Paws.kotlin.native=false artifactSizeMetrics working-directory: smithy-kotlin + - name: Analyze Artifact Size Metrics run: ./gradlew analyzeArtifactSizeMetrics working-directory: smithy-kotlin - name: Show Results - uses: awslabs/aws-kotlin-repo-tools/.github/actions/artifact-size-metrics/show-results@main + uses: aws/aws-kotlin-repo-tools/.github/actions/artifact-size-metrics/show-results@main - name: Evaluate if: ${{ !contains(github.event.pull_request.labels.*.name, 'acknowledge-artifact-size-increase') }} diff --git a/.github/workflows/changelog-verification.yml b/.github/workflows/changelog-verification.yml index 995c03843c..bc9b81bded 100644 --- a/.github/workflows/changelog-verification.yml +++ b/.github/workflows/changelog-verification.yml @@ -22,4 +22,4 @@ jobs: aws-region: us-west-2 - name: Verify changelog - uses: awslabs/aws-kotlin-repo-tools/.github/actions/changelog-verification@main \ No newline at end of file + uses: aws/aws-kotlin-repo-tools/.github/actions/changelog-verification@main diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5343a8bf01..da0bb593d6 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -46,7 +46,7 @@ jobs: java-version: 17 cache: 'gradle' - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main with: working-directory: 'smithy-kotlin' @@ -176,7 +176,7 @@ jobs: cache: 'gradle' - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main with: working-directory: 'smithy-kotlin' @@ -215,7 +215,7 @@ jobs: cache: 'gradle' - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main with: working-directory: 'smithy-kotlin' @@ -237,6 +237,15 @@ jobs: - name: Setup build uses: ./smithy-kotlin/.github/actions/setup-build + - name: Checkout tools + uses: actions/checkout@v4 + with: + path: 'aws-kotlin-repo-tools' + repository: 'aws/aws-kotlin-repo-tools' + ref: '0.2.3' + sparse-checkout: | + .github + - name: Checkout aws-sdk-kotlin uses: ./aws-kotlin-repo-tools/.github/actions/checkout-head with: @@ -253,12 +262,7 @@ jobs: uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main with: working-directory: ./aws-sdk-kotlin - - name: Configure JDK - uses: actions/setup-java@v3 - with: - distribution: 'corretto' - java-version: 17 - cache: 'gradle' + - name: Build and Test aws-sdk-kotlin downstream working-directory: ./smithy-kotlin run: | @@ -277,4 +281,4 @@ jobs: sed -i "s/smithy-kotlin-codegen-version = .*$/smithy-kotlin-codegen-version = \"$SMITHY_KOTLIN_CODEGEN_VERSION\"/" gradle/libs.versions.toml ./gradlew --parallel -Paws.kotlin.native=false publishToMavenLocal ./gradlew -Paws.kotlin.native=false test jvmTest - ./gradlew -Paws.kotlin.native=false testAllProtocols \ No newline at end of file + ./gradlew -Paws.kotlin.native=false testAllProtocols diff --git a/.github/workflows/jreleaser.yml b/.github/workflows/jreleaser.yml new file mode 100644 index 0000000000..391e452d5d --- /dev/null +++ b/.github/workflows/jreleaser.yml @@ -0,0 +1,13 @@ +# TODO: Delete this workflow when we migrate off JReleaser + +name: JReleaser check +on: + pull_request: + +jobs: + jreleaser-check: + permissions: {} + runs-on: ubuntu-latest + steps: + - name: JReleaser check + uses: awslabs/aws-kotlin-repo-tools/.github/actions/jreleaser@main diff --git a/.github/workflows/kat-transform.yml b/.github/workflows/kat-transform.yml index 9ed7e3ecca..6f2dfae691 100644 --- a/.github/workflows/kat-transform.yml +++ b/.github/workflows/kat-transform.yml @@ -1,7 +1,7 @@ name: Kat Transform on: - pull_request_target: + pull_request: types: [ opened, synchronize, reopened, labeled, unlabeled ] branches: - main @@ -39,12 +39,12 @@ jobs: aws-region: us-west-2 - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main with: working-directory: ./smithy-kotlin - name: Setup kat - uses: awslabs/aws-kotlin-repo-tools/.github/actions/setup-kat@main + uses: aws/aws-kotlin-repo-tools/.github/actions/setup-kat@main - name: Build working-directory: ./smithy-kotlin @@ -71,4 +71,4 @@ jobs: exit 1 fi - echo "Transformation succeeded!" \ No newline at end of file + echo "Transformation succeeded!" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 809b2bc451..c8c67eb7f7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main - name: Lint ${{ env.PACKAGE_NAME }} run: | ./gradlew ktlint diff --git a/.github/workflows/merge-main.yml b/.github/workflows/merge-main.yml index ae4af7a055..b6316a2f38 100644 --- a/.github/workflows/merge-main.yml +++ b/.github/workflows/merge-main.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Merge main - uses: awslabs/aws-kotlin-repo-tools/.github/actions/merge-main@main + uses: aws/aws-kotlin-repo-tools/.github/actions/merge-main@main with: ci-user-pat: ${{ secrets.CI_USER_PAT }} exempt-branches: # Add any if required diff --git a/.github/workflows/release-readiness.yml b/.github/workflows/release-readiness.yml index e88c6931fa..26a880217f 100644 --- a/.github/workflows/release-readiness.yml +++ b/.github/workflows/release-readiness.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v4 - name: Configure Gradle - uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + uses: aws/aws-kotlin-repo-tools/.github/actions/configure-gradle@main - name: Build smithy-kotlin run: ./gradlew test jvmTest diff --git a/CHANGELOG.md b/CHANGELOG.md index 06450764f6..b10da529c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [1.5.8] - 09/04/2025 + +## [1.5.7] - 09/04/2025 + +### Features +* [#820](https://github.com/awslabs/smithy-kotlin/issues/820) Add additional TLS configuration options to HTTP engines. + +### Miscellaneous +* Upgrade to Gradle 9.0.0 + +## [1.5.6] - 08/21/2025 + +### Fixes +* [#1285](https://github.com/awslabs/smithy-kotlin/issues/1285) ⚠️ **IMPORTANT**: Correctly return number of bytes read from chunked streams + +## [1.5.5] - 08/13/2025 + +## [1.5.4] - 08/07/2025 + ## [1.5.3] - 07/28/2025 ### Features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 316ecb449a..abd6e50a04 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -168,7 +168,7 @@ operating system, run `./gradlew build`. #### CI downstream -This repo is a core dependency of [**aws-sdk-kotlin**](https://github.com/awslabs/aws-sdk-kotlin) and many changes made +This repo is a core dependency of [**aws-sdk-kotlin**](https://github.com/aws/aws-sdk-kotlin) and many changes made here could impact that repo as well. To ensure that **aws-sdk-kotlin** continues to function correctly, we run a downstream CI check that builds both code bases in a shared workspace. To run this check locally, check out both repositories into subdirectories of the same parent, build/publish **smithy-kotlin** followed by @@ -178,7 +178,7 @@ repositories into subdirectories of the same parent, build/publish **smithy-kotl mkdir path/to/workspace # create a new directory to hold both repositories cd path/to/workspace git clone -b branch-name https://github.com/smithy-lang/smithy-kotlin.git # replace branch-name as appropriate -git clone -b branch-name https://github.com/awslabs/aws-sdk-kotlin.git # replace branch-name as appropriate +git clone -b branch-name https://github.com/aws/aws-sdk-kotlin.git # replace branch-name as appropriate cd smithy-kotlin ./gradlew build publishToMavenLocal cd ../aws-sdk-kotlin diff --git a/build.gradle.kts b/build.gradle.kts index 7e5c097678..2a900f2cda 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ import aws.sdk.kotlin.gradle.dsl.configureJReleaser import aws.sdk.kotlin.gradle.dsl.configureLinting import aws.sdk.kotlin.gradle.util.typedProp +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { // NOTE: buildscript classpath for the root project is the parent classloader for the subprojects, we @@ -19,7 +20,19 @@ buildscript { https://github.com/Kotlin/dokka/issues/3472#issuecomment-1929712374 https://github.com/Kotlin/dokka/issues/3194#issuecomment-1929382630 */ - classpath(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.2")) + classpath(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.15.3")) + } + + configurations.classpath { + resolutionStrategy { + /* + Version bumping the SDK to 1.5.x in repo tools broke our buildscript classpath: + java.lang.NoSuchMethodError: 'void kotlinx.coroutines.CancellableContinuation.resume(java.lang.Object, kotlin.jvm.functions.Function3) + + FIXME: Figure out what broke our buildscipt classpath, this is a temporary fix + */ + force("com.squareup.okhttp3:okhttp-coroutines:5.0.0-alpha.14") + } } } @@ -46,9 +59,9 @@ val testJavaVersion = typedProp("test.java.version")?.let { } allprojects { - if (rootProject.typedProp("kotlinWarningsAsErrors") == true) { - tasks.withType { - compilerOptions.allWarningsAsErrors = true + tasks.withType { + compilerOptions { + allWarningsAsErrors = true } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 306b0211ac..c96c9652de 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + plugins { `kotlin-dsl` } diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index 62991c1ef5..fa90930379 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + rootProject.name = "buildSrc" dependencyResolutionManagement { diff --git a/buildSrc/src/main/kotlin/dokka-convention.gradle.kts b/buildSrc/src/main/kotlin/dokka-convention.gradle.kts index 0260162ba1..7169bb7e87 100644 --- a/buildSrc/src/main/kotlin/dokka-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-convention.gradle.kts @@ -1,4 +1,7 @@ -import kotlin.text.set +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ plugins { id("org.jetbrains.dokka") diff --git a/codegen/protocol-tests/build.gradle.kts b/codegen/protocol-tests/build.gradle.kts index 7adafe207e..546fed72c2 100644 --- a/codegen/protocol-tests/build.gradle.kts +++ b/codegen/protocol-tests/build.gradle.kts @@ -95,51 +95,35 @@ tasks.generateSmithyProjections { } } -abstract class ProtocolTestTask @Inject constructor(private val project: Project) : DefaultTask() { - /** - * The projection - */ - @get:Input - abstract val projectionName: Property - - /** - * The projection root directory - */ - @get:Input - abstract val projectionRootDirectory: Property - - @TaskAction - fun runTests() { - val projectionRootDir = project.file(projectionRootDirectory.get()) - println("[$projectionName] buildDir: $projectionRootDir") - if (!projectionRootDir.exists()) { - throw GradleException("$projectionRootDir does not exist") - } - val wrapper = if (System.getProperty("os.name").lowercase().contains("windows")) "gradlew.bat" else "gradlew" - val gradlew = project.rootProject.file(wrapper).absolutePath - - // NOTE - this still requires us to publish to maven local. - project.exec { - workingDir = projectionRootDir - executable = gradlew - args = listOf("test") - } - } -} - smithyBuild.projections.forEach { val protocolName = it.name + val dirProvider = smithyBuild + .smithyKotlinProjectionPath(protocolName) + .map { file(it.toString()) } - tasks.register("testProtocol-$protocolName") { - dependsOn(tasks.generateSmithyProjections) + tasks.register("testProtocol-$protocolName") { group = "Verification" - projectionName.set(it.name) - projectionRootDirectory.set(smithyBuild.smithyKotlinProjectionPath(it.name).map { it.toString() }) + dependsOn(tasks.generateSmithyProjections) + + doFirst { + val dir = dirProvider.get() + require(dir.exists()) { "$dir does not exist" } + + val wrapper = if (System.getProperty("os.name").lowercase().contains("windows")) { + "gradlew.bat" + } else { + "gradlew" + } + val gradlew = rootProject.layout.projectDirectory.file(wrapper).asFile.absolutePath + + workingDir = dir + executable = gradlew + args = listOf("test") + } } } tasks.register("testAllProtocols") { group = "Verification" - val allTests = tasks.withType() - dependsOn(allTests) + dependsOn(tasks.matching { it.name.startsWith("testProtocol-") }) } diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/customization/RegionSupportTest.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/customization/RegionSupportTest.kt index 2df3e519f0..c15e3f11ee 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/customization/RegionSupportTest.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/customization/RegionSupportTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.aws.customization import org.junit.jupiter.api.Test diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQueryTest.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQueryTest.kt index 8a575c7cd4..244d265375 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQueryTest.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/AwsQueryTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.kotlin.codegen.test.* diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1Test.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1Test.kt index e1a4b22c01..4013d91887 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1Test.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestJson1Test.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.kotlin.codegen.test.* diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXmlTest.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXmlTest.kt index 4f59359bee..b13d5723fb 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXmlTest.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/RestXmlTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.aws.protocols import software.amazon.smithy.kotlin.codegen.test.* diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/checksums/HttpChecksumRequiredIntegration.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/checksums/HttpChecksumRequiredIntegration.kt index 61432a7b12..239609778f 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/checksums/HttpChecksumRequiredIntegration.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/checksums/HttpChecksumRequiredIntegration.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.rendering.checksums import software.amazon.smithy.aws.traits.HttpChecksumTrait diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt index 192494e9e2..df257c04a0 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsIntegration.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.rendering.smoketests import software.amazon.smithy.kotlin.codegen.KotlinSettings diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt index 9c90f6c72c..7dc993143e 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGenerator.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.rendering.smoketests import software.amazon.smithy.codegen.core.Symbol @@ -262,7 +267,11 @@ class SmokeTestsRunnerGenerator( statusOverride: String? = null, directive: String? = "", ) { - val expectation = if (errorExpected) "error expected from service" else "no error expected from service" + val expectation = if (errorExpected) { + "error expected from service" + } else { + "no error expected from service" + } val status = statusOverride ?: "\$status" val testResult = "$status $service $testCase - $expectation $directive" writer.write("printer.appendLine(#S)", testResult) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt index 73b9a041f9..3cc3b050e1 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/util/Node.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.rendering.util import software.amazon.smithy.kotlin.codegen.utils.dq diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/waiters/KotlinJmespathExpressionVisitor.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/waiters/KotlinJmespathExpressionVisitor.kt index a7b28180f2..5c486d571f 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/waiters/KotlinJmespathExpressionVisitor.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/waiters/KotlinJmespathExpressionVisitor.kt @@ -164,6 +164,7 @@ class KotlinJmespathExpressionVisitor( val codegen = buildString { val nullables = buildList { + // FIXME In some cases we are unnecessarily handling nulls here, leading to compiler warnings. SDK-KT-786 if (left.shape?.isNullable == true || left.nullable) add("${left.identifier} == null") if (right.shape?.isNullable == true || right.nullable) add("${right.identifier} == null") } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt index 27b1ced806..31d5484126 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/utils/Model.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.utils import software.amazon.smithy.model.Model diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterTest.kt index 78d76dcbbc..ac08447298 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.rendering.endpoints import software.amazon.smithy.kotlin.codegen.test.* diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/OperationContextParamsTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/OperationContextParamsTest.kt index 6d5d713a49..1a3a76fa81 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/OperationContextParamsTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/OperationContextParamsTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.rendering.endpoints import software.amazon.smithy.kotlin.codegen.test.* diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt index 07f680d68a..85eda5c3d9 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/smoketests/SmokeTestsRunnerGeneratorTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package software.amazon.smithy.kotlin.codegen.rendering.smoketests import software.amazon.smithy.kotlin.codegen.test.* diff --git a/dokka-smithy/build.gradle.kts b/dokka-smithy/build.gradle.kts index bb4b1ff1ec..d46562ac72 100644 --- a/dokka-smithy/build.gradle.kts +++ b/dokka-smithy/build.gradle.kts @@ -35,7 +35,6 @@ tasks.withType { compilerOptions { jvmTarget.set(JvmTarget.JVM_1_8) freeCompilerArgs.add("-Xjdk-release=1.8") - allWarningsAsErrors.set(false) // FIXME Dokka bundles stdlib into the classpath, causing an unfixable warning } } diff --git a/gradle.properties b/gradle.properties index 5760061e6b..c90e8aaef3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,11 +14,11 @@ kotlinx.atomicfu.enableNativeIrTransformation=false org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=1G # SDK -sdkVersion=1.5.4-SNAPSHOT +sdkVersion=1.5.9-SNAPSHOT # codegen -codegenVersion=0.35.4-SNAPSHOT +codegenVersion=0.35.9-SNAPSHOT # FIXME Remove after Dokka 2.0 Gradle plugin is stable org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled -org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true \ No newline at end of file +org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bdd68f6896..f7bcf7886e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin-version = "2.2.0" dokka-version = "2.0.0" -aws-kotlin-repo-tools-version = "0.4.30-kn" +aws-kotlin-repo-tools-version = "0.4.51" # libs coroutines-version = "1.10.2" @@ -10,10 +10,10 @@ atomicfu-version = "0.29.0" okhttp-version = "5.1.0" okhttp4-version = "4.12.0" okio-version = "3.16.0" -otel-version = "1.45.0" -slf4j-version = "2.0.16" +otel-version = "1.52.0" +slf4j-version = "2.0.17" slf4j-v1x-version = "1.7.36" -crt-kotlin-version = "0.10.0" +crt-kotlin-version = "0.10.3" micrometer-version = "1.15.2" binary-compatibility-validator-version = "0.18.1" kotlin-multiplatform-bignum-version = "0.3.10" @@ -27,8 +27,8 @@ junit-version = "5.13.4" kotest-version = "5.9.1" kotlin-compile-testing-version = "0.8.0" kotlinx-benchmark-version = "0.4.14" -kotlinx-serialization-version = "1.7.3" -docker-java-version = "3.4.0" +kotlinx-serialization-version = "1.9.0" +docker-java-version = "3.5.3" ktor-version = "3.2.3" kaml-version = "0.55.0" jsoup-version = "1.21.1" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bf3beeb7dd..4ba2524eb9 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.14.2-bin.zip +distributionUrl=https://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGenerator.kt b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGenerator.kt index f80ee4fe7e..a78bd0143a 100644 --- a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGenerator.kt +++ b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGenerator.kt @@ -1,11 +1,11 @@ /* -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningConfig.Companion.invoke import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpMethod import aws.smithy.kotlin.runtime.http.request.HttpRequest diff --git a/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGeneratorTest.kt b/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGeneratorTest.kt index 75b17fbab9..50d38aeb5c 100644 --- a/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGeneratorTest.kt +++ b/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGeneratorTest.kt @@ -1,7 +1,8 @@ /* -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials diff --git a/runtime/auth/http-auth/api/http-auth.api b/runtime/auth/http-auth/api/http-auth.api index d47a5a9c79..bbdec31e71 100644 --- a/runtime/auth/http-auth/api/http-auth.api +++ b/runtime/auth/http-auth/api/http-auth.api @@ -62,6 +62,8 @@ public abstract interface class aws/smithy/kotlin/runtime/http/auth/CloseableBea public final class aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProvider : aws/smithy/kotlin/runtime/http/auth/BearerTokenProvider { public fun (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/PlatformProvider;)V public synthetic fun (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/PlatformProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/PlatformProvider;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/PlatformProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProvider.kt b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProvider.kt index ffe37b60b0..4863f0c6e7 100644 --- a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProvider.kt +++ b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProvider.kt @@ -4,25 +4,37 @@ */ package aws.smithy.kotlin.runtime.http.auth +import aws.smithy.kotlin.runtime.businessmetrics.SmithyBusinessMetric +import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric import aws.smithy.kotlin.runtime.collections.Attributes -import aws.smithy.kotlin.runtime.collections.emptyAttributes +import aws.smithy.kotlin.runtime.collections.mutableAttributes import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.util.PlatformProvider /** - * A [BearerTokenProvider] that extracts the bearer token from the target environment variable. + * A [BearerTokenProvider] that extracts the bearer token from JVM system properties or environment variables. */ public class EnvironmentBearerTokenProvider( - private val key: String, + private val sysPropKey: String, + private val envKey: String, private val platform: PlatformProvider = PlatformProvider.System, ) : BearerTokenProvider { + @Deprecated("This constructor does not support a parameter for a system property key and will be removed in version 1.6.x") + public constructor( + envKey: String, + platform: PlatformProvider = PlatformProvider.System, + ) : this("", envKey, platform) + override suspend fun resolve(attributes: Attributes): BearerToken { - val bearerToken = platform.getenv(key) - ?: error("$key environment variable is not set") + val bearerToken = sysPropKey.takeUnless(String::isBlank)?.let(platform::getProperty) + ?: platform.getenv(envKey) + if (bearerToken.isNullOrBlank()) throw IllegalStateException("""Missing values for system property "$sysPropKey" and environment variable "$envKey"""") return object : BearerToken { override val token: String = bearerToken - override val attributes: Attributes = emptyAttributes() + override val attributes: Attributes = mutableAttributes().apply { + emitBusinessMetric(SmithyBusinessMetric.BEARER_SERVICE_ENV_VARS) + } override val expiration: Instant? = null } } diff --git a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProviderTest.kt b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProviderTest.kt index e98e8083d5..d1413c72cf 100644 --- a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProviderTest.kt +++ b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/EnvironmentBearerTokenProviderTest.kt @@ -1,6 +1,10 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.http.auth -import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.util.TestPlatformProvider import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -9,27 +13,65 @@ import kotlin.test.assertFailsWith class EnvironmentBearerTokenProviderTest { @Test - fun testResolveWithValidToken() = runTest { + fun testResolveFromEnvVar() = runTest { + val provider = EnvironmentBearerTokenProvider( + "TEST_SYS_PROPS_TOKEN", + "TEST_ENV_TOKEN", + TestPlatformProvider( + env = mapOf("TEST_ENV_TOKEN" to "test-env-bearer-token"), + ), + ) + + val token = provider.resolve() + + assertEquals("test-env-bearer-token", token.token) + } + + @Test + fun testResolveFromSysProps() = runTest { val provider = EnvironmentBearerTokenProvider( - "TEST_TOKEN", - TestPlatformProvider(mutableMapOf("TEST_TOKEN" to "test-bearer-token")), + "TEST_SYS_PROPS_TOKEN", + "TEST_ENV_TOKEN", + TestPlatformProvider( + props = mapOf("TEST_SYS_PROPS_TOKEN" to "test-sys-props-bearer-token"), + ), ) val token = provider.resolve() - assertEquals("test-bearer-token", token.token) + assertEquals("test-sys-props-bearer-token", token.token) + } + + @Test + fun testResolutionOrder() = runTest { + val provider = EnvironmentBearerTokenProvider( + "TEST_SYS_PROPS_TOKEN", + "TEST_ENV_TOKEN", + TestPlatformProvider( + props = mapOf("TEST_SYS_PROPS_TOKEN" to "test-sys-props-bearer-token"), + env = mapOf("TEST_ENV_TOKEN" to "test-env-bearer-token"), + ), + ) + + val token = provider.resolve() + + assertEquals("test-sys-props-bearer-token", token.token) } @Test fun testResolveWithMissingToken() = runTest { val provider = EnvironmentBearerTokenProvider( "MISSING_TEST_TOKEN", - TestPlatformProvider(mutableMapOf()), + "MISSING_TEST_TOKEN", + TestPlatformProvider( + env = mapOf("TEST_TOKEN" to "test-env-bearer-token"), + props = mapOf("TEST_TOKEN" to "test-sys-props-bearer-token"), + ), ) val exception = assertFailsWith { - provider.resolve(emptyAttributes()) + provider.resolve() } - assertEquals("MISSING_TEST_TOKEN environment variable is not set", exception.message) + assertEquals("Missing values for system property \"MISSING_TEST_TOKEN\" and environment variable \"MISSING_TEST_TOKEN\"", exception.message) } } diff --git a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/ReprioritizeAuthOptionsTest.kt b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/ReprioritizeAuthOptionsTest.kt index db479c10f8..865746553a 100644 --- a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/ReprioritizeAuthOptionsTest.kt +++ b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/ReprioritizeAuthOptionsTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + import aws.smithy.kotlin.runtime.auth.AuthOption import aws.smithy.kotlin.runtime.auth.AuthSchemeId import aws.smithy.kotlin.runtime.http.auth.reprioritizeAuthOptions diff --git a/runtime/build.gradle.kts b/runtime/build.gradle.kts index 949c95973e..158856a208 100644 --- a/runtime/build.gradle.kts +++ b/runtime/build.gradle.kts @@ -3,10 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ import aws.sdk.kotlin.gradle.dsl.configurePublishing -import aws.sdk.kotlin.gradle.kmp.* -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.withType +import aws.sdk.kotlin.gradle.kmp.configureKmpTargets +import aws.sdk.kotlin.gradle.kmp.kotlin +import aws.sdk.kotlin.gradle.kmp.needsKmpConfigured import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest plugins { @@ -58,13 +59,19 @@ subprojects { implementation(libraries.kotest.assertions.core.jvm) } } + + all { + languageSettings.optIn("kotlin.RequiresOptIn") + } } - } - kotlin.sourceSets.all { - // Allow subprojects to use internal APIs - // See https://kotlinlang.org/docs/reference/opt-in-requirements.html#opting-in-to-using-api - listOf("kotlin.RequiresOptIn", "kotlinx.cinterop.ExperimentalForeignApi").forEach { languageSettings.optIn(it) } + targets.withType().all { + listOf("${name}Main", "${name}Test").forEach { + this@kotlin.sourceSets.findByName(it)?.apply { + languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") + } + } + } } tasks.withType { @@ -118,6 +125,20 @@ subprojects { tasks.withType { enabled = false } + + tasks.withType { + if (this is Test) { + useJUnitPlatform() + } + + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = true + showStackTraces = true + showExceptions = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } + } } // configureIosSimulatorTasks() diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api b/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api index 1b0bb2d984..2fe8c67077 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api @@ -15,21 +15,41 @@ public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine$Compa public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl { public static final field Companion Laws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Companion; public synthetic fun (Laws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getCertificateFile ()Ljava/lang/String; + public final fun getCertificatePem ()Ljava/lang/String; + public final fun getCertificatesDirectory ()Ljava/lang/String; public final fun getClientBootstrap ()Laws/sdk/kotlin/crt/io/ClientBootstrap; public final fun getInitialWindowSizeBytes ()I public final fun getMaxConnections-pVg5ArA ()I + public final fun getTlsCipherPreference ()Laws/sdk/kotlin/crt/io/TlsCipherPreference; + public final fun getVerifyPeer ()Z + public final fun setCertificateFile (Ljava/lang/String;)V + public final fun setCertificatePem (Ljava/lang/String;)V + public final fun setCertificatesDirectory (Ljava/lang/String;)V public final fun setClientBootstrap (Laws/sdk/kotlin/crt/io/ClientBootstrap;)V + public final fun setTlsCipherPreference (Laws/sdk/kotlin/crt/io/TlsCipherPreference;)V + public final fun setVerifyPeer (Z)V public fun toBuilderApplicator ()Lkotlin/jvm/functions/Function1; } public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Builder : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl$BuilderImpl { public fun ()V + public final fun getCertificateFile ()Ljava/lang/String; + public final fun getCertificatePem ()Ljava/lang/String; + public final fun getCertificatesDirectory ()Ljava/lang/String; public final fun getClientBootstrap ()Laws/sdk/kotlin/crt/io/ClientBootstrap; public final fun getInitialWindowSizeBytes ()I public final fun getMaxConnections-pVg5ArA ()I + public final fun getTlsCipherPreference ()Laws/sdk/kotlin/crt/io/TlsCipherPreference; + public final fun getVerifyPeer ()Z + public final fun setCertificateFile (Ljava/lang/String;)V + public final fun setCertificatePem (Ljava/lang/String;)V + public final fun setCertificatesDirectory (Ljava/lang/String;)V public final fun setClientBootstrap (Laws/sdk/kotlin/crt/io/ClientBootstrap;)V public final fun setInitialWindowSizeBytes (I)V public final fun setMaxConnections-WZ4Q5Ns (I)V + public final fun setTlsCipherPreference (Laws/sdk/kotlin/crt/io/TlsCipherPreference;)V + public final fun setVerifyPeer (Z)V } public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Companion { diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt index 4b4fe5504c..6009d41576 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt @@ -32,9 +32,13 @@ internal class ConnectionManager( private val crtTlsContext: TlsContext = TlsContextOptionsBuilder() .apply { - verifyPeer = true alpn = config.tlsContext.alpn.joinToString(separator = ";") { it.protocolId } minTlsVersion = toCrtTlsVersion(config.tlsContext.minVersion) + caRoot = config.certificatePem + caFile = config.certificateFile + caDir = config.certificatesDirectory + tlsCipherPreference = config.tlsCipherPreference + verifyPeer = config.verifyPeer } .build() .let(::CrtTlsContext) diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt index fd29e0bfaf..77f65bf283 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/jvmAndNative/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt @@ -6,6 +6,7 @@ package aws.smithy.kotlin.runtime.http.engine.crt import aws.sdk.kotlin.crt.io.ClientBootstrap +import aws.sdk.kotlin.crt.io.TlsCipherPreference import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfig import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfigImpl @@ -44,6 +45,36 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli */ public var clientBootstrap: ClientBootstrap? = builder.clientBootstrap + /** + * Certificate authority content in PEM format + * Mutually exclusive with [certificateFile] and [certificatesDirectory]. + */ + public var certificatePem: String? = builder.certificatePem + + /** + * Path to the certificate file in PEM format. + * Mutually exclusive with [certificatePem]. Can be used independently or together with [certificatesDirectory]. + */ + public var certificateFile: String? = builder.certificateFile + + /** + * Path to the certificates directory containing PEM files. + * Mutually exclusive with [certificatePem]. Can be used independently or together with [certificateFile]. + */ + public var certificatesDirectory: String? = builder.certificatesDirectory + + /** + * TLS cipher suite preference for connections. + * Controls which cipher suites are available during TLS negotiation. + */ + public var tlsCipherPreference: TlsCipherPreference = builder.tlsCipherPreference + + /** + * Whether to verify the peer's certificate during TLS handshake. + * When false, accepts any certificate. + */ + public var verifyPeer: Boolean = builder.verifyPeer + override fun toBuilderApplicator(): HttpClientEngineConfig.Builder.() -> Unit = { super.toBuilderApplicator()() @@ -51,6 +82,11 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli maxConnections = this@CrtHttpEngineConfig.maxConnections initialWindowSizeBytes = this@CrtHttpEngineConfig.initialWindowSizeBytes clientBootstrap = this@CrtHttpEngineConfig.clientBootstrap + certificatePem = this@CrtHttpEngineConfig.certificatePem + certificateFile = this@CrtHttpEngineConfig.certificateFile + certificatesDirectory = this@CrtHttpEngineConfig.certificatesDirectory + tlsCipherPreference = this@CrtHttpEngineConfig.tlsCipherPreference + verifyPeer = this@CrtHttpEngineConfig.verifyPeer } } @@ -73,5 +109,35 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli * Set the [ClientBootstrap] to use for the engine. By default it is a shared instance. */ public var clientBootstrap: ClientBootstrap? = null + + /** + * Certificate Authority content in PEM format. + * Mutually exclusive with caFile and caDir. + */ + public var certificatePem: String? = null + + /** + * Path to the certificate file in PEM format. + * Mutually exclusive with [certificatePem]. Can be used independently or together with [certificatesDirectory]. + */ + public var certificateFile: String? = null + + /** + * Path to the certificates directory containing PEM files. + * Mutually exclusive with [certificatePem]. Can be used independently or together with [certificateFile]. + */ + public var certificatesDirectory: String? = null + + /** + * TLS cipher suite preference for connections. + * Controls which cipher suites are available during TLS negotiation. + */ + public var tlsCipherPreference: TlsCipherPreference = TlsCipherPreference.SYSTEM_DEFAULT + + /** + * Whether to verify the peer's certificate during TLS handshake. + * When false, accepts any certificate (insecure, for testing only). + */ + public var verifyPeer: Boolean = true } } diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/api/http-client-engine-okhttp.api b/runtime/protocol/http-client-engines/http-client-engine-okhttp/api/http-client-engine-okhttp.api index 77e6120478..b2e85fc114 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/api/http-client-engine-okhttp.api +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/api/http-client-engine-okhttp.api @@ -66,19 +66,36 @@ public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine$Com public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl { public static final field Companion Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Companion; public synthetic fun (Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getCertificatePinner ()Lokhttp3/CertificatePinner; + public final fun getCipherSuites ()Ljava/util/List; public final fun getConnectionIdlePollingInterval-FghU774 ()Lkotlin/time/Duration; + public final fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier; + public final fun getKeyManager ()Ljavax/net/ssl/KeyManager; public final fun getMaxConcurrencyPerHost-pVg5ArA ()I + public final fun getTrustManager ()Ljavax/net/ssl/X509TrustManager; + public final fun setKeyManager (Ljavax/net/ssl/KeyManager;)V + public final fun setTrustManager (Ljavax/net/ssl/X509TrustManager;)V public fun toBuilderApplicator ()Lkotlin/jvm/functions/Function1; } public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Builder : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl$BuilderImpl { public fun ()V + public final fun getCertificatePinner ()Lokhttp3/CertificatePinner; + public final fun getCipherSuites ()Ljava/util/List; public final fun getConnectionIdlePollingInterval-FghU774 ()Lkotlin/time/Duration; + public final fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier; + public final fun getKeyManager ()Ljavax/net/ssl/KeyManager; public final fun getMaxConcurrencyPerHost-0hXNFcg ()Lkotlin/UInt; public fun getTelemetryProvider ()Laws/smithy/kotlin/runtime/telemetry/TelemetryProvider; + public final fun getTrustManager ()Ljavax/net/ssl/X509TrustManager; + public final fun setCertificatePinner (Lokhttp3/CertificatePinner;)V + public final fun setCipherSuites (Ljava/util/List;)V public final fun setConnectionIdlePollingInterval-BwNAW2A (Lkotlin/time/Duration;)V + public final fun setHostnameVerifier (Ljavax/net/ssl/HostnameVerifier;)V + public final fun setKeyManager (Ljavax/net/ssl/KeyManager;)V public final fun setMaxConcurrencyPerHost-ExVfyTY (Lkotlin/UInt;)V public fun setTelemetryProvider (Laws/smithy/kotlin/runtime/telemetry/TelemetryProvider;)V + public final fun setTrustManager (Ljavax/net/ssl/X509TrustManager;)V } public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Companion { diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt index 297e015eed..ffe6ca7a98 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt @@ -24,6 +24,9 @@ import kotlinx.coroutines.job import okhttp3.* import okhttp3.coroutines.executeAsync import java.util.concurrent.TimeUnit +import javax.net.ssl.KeyManager +import javax.net.ssl.SSLContext +import javax.net.ssl.X509TrustManager import kotlin.time.toJavaDuration import aws.smithy.kotlin.runtime.net.TlsVersion as SdkTlsVersion import okhttp3.TlsVersion as OkHttpTlsVersion @@ -103,7 +106,15 @@ public fun OkHttpEngineConfig.buildClient( followRedirects(false) followSslRedirects(false) - connectionSpecs(listOf(minTlsConnectionSpec(config.tlsContext), ConnectionSpec.CLEARTEXT)) + connectionSpecs(listOf(tlsConnectionSpec(config.tlsContext, config.cipherSuites), ConnectionSpec.CLEARTEXT)) + + config.trustManager?.let { + val sslContext = createSslContext(it, config.keyManager) + sslSocketFactory(sslContext.socketFactory, trustManager!!) + } + + config.certificatePinner?.let(::certificatePinner) + config.hostnameVerifier?.let(::hostnameVerifier) // Transient connection errors are handled by retry strategy (exceptions are wrapped and marked retryable // appropriately internally). We don't want inner retry logic that inflates the number of retries. @@ -159,7 +170,7 @@ public fun OkHttpEngineConfig.buildClient( }.build() } -private fun minTlsConnectionSpec(tlsContext: TlsContext): ConnectionSpec { +private fun tlsConnectionSpec(tlsContext: TlsContext, cipherSuites: List?): ConnectionSpec { val minVersion = tlsContext.minVersion ?: TlsVersion.TLS_1_2 val okHttpTlsVersions = SdkTlsVersion .values() @@ -170,6 +181,9 @@ private fun minTlsConnectionSpec(tlsContext: TlsContext): ConnectionSpec { return ConnectionSpec .Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(*okHttpTlsVersions) + .apply { + cipherSuites?.toTypedArray()?.let(::cipherSuites) + } .build() } @@ -179,3 +193,16 @@ private fun toOkHttpTlsVersion(sdkTlsVersion: SdkTlsVersion): OkHttpTlsVersion = SdkTlsVersion.TLS_1_2 -> OkHttpTlsVersion.TLS_1_2 SdkTlsVersion.TLS_1_3 -> OkHttpTlsVersion.TLS_1_3 } + +/** + * Creates an SSL context with custom trust and key managers + */ +private fun createSslContext(trustManager: X509TrustManager, keyManager: KeyManager?): SSLContext { + val keyManagers = keyManager?.let { arrayOf(it) } + val trustManagers = arrayOf(trustManager) + + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(keyManagers, trustManagers, null) + + return sslContext +} diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig.kt index 9c852f4219..0145612640 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig.kt @@ -9,6 +9,10 @@ import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfig import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfigImpl import aws.smithy.kotlin.runtime.telemetry.Global import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider +import okhttp3.CertificatePinner +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.KeyManager +import javax.net.ssl.X509TrustManager import kotlin.time.Duration /** @@ -50,12 +54,50 @@ public class OkHttpEngineConfig private constructor(builder: Builder) : HttpClie */ public val maxConcurrencyPerHost: UInt = builder.maxConcurrencyPerHost ?: builder.maxConcurrency + /** + * Trust manager used to validate server certificates during TLS handshake. + * Determines whether to trust the certificate chain presented by a remote server. + * When provided, this trust manager will be used instead of the default system trust store. + */ + public var trustManager: X509TrustManager? = builder.trustManager + + /** + * Key manager that supplies client certificates for mutual TLS (mTLS) authentication. + * When provided, the client will present certificates from this key manager when the server + * requests client authentication. Used for scenarios requiring client certificate authentication. + */ + public var keyManager: KeyManager? = builder.keyManager + + /** + * List of cipher suites to enable for TLS connections. If null, uses OkHttp defaults. + * When specified, only the listed cipher suites will be enabled. + */ + public val cipherSuites: List? = builder.cipherSuites + + /** + * Certificate pinner that validates server certificates against known public key pins. + * Used to prevent man-in-the-middle attacks by ensuring the server presents expected certificates. + */ + public val certificatePinner: CertificatePinner? = builder.certificatePinner + + /** + * Custom hostname verifier for validating server hostnames during TLS handshake. + * By default, OkHttp verifies that the certificate's hostname matches the request hostname. + * Use this to implement custom hostname verification logic. + */ + public val hostnameVerifier: HostnameVerifier? = builder.hostnameVerifier + override fun toBuilderApplicator(): HttpClientEngineConfig.Builder.() -> Unit = { super.toBuilderApplicator()() if (this is Builder) { connectionIdlePollingInterval = this@OkHttpEngineConfig.connectionIdlePollingInterval maxConcurrencyPerHost = this@OkHttpEngineConfig.maxConcurrencyPerHost + trustManager = this@OkHttpEngineConfig.trustManager + keyManager = this@OkHttpEngineConfig.keyManager + cipherSuites = this@OkHttpEngineConfig.cipherSuites + certificatePinner = this@OkHttpEngineConfig.certificatePinner + hostnameVerifier = this@OkHttpEngineConfig.hostnameVerifier } } @@ -84,6 +126,39 @@ public class OkHttpEngineConfig private constructor(builder: Builder) : HttpClie */ public var maxConcurrencyPerHost: UInt? = null + /** + * Trust manager used to validate server certificates during TLS handshake. + * Determines whether to trust the certificate chain presented by a remote server. + * When provided, this trust manager will be used instead of the default system trust store. + */ + public var trustManager: X509TrustManager? = null + + /** + * Key manager that supplies client certificates for mutual TLS (mTLS) authentication. + * When provided, the client will present certificates from this key manager when the server + * requests client authentication. Used for scenarios requiring client certificate authentication. + */ + public var keyManager: KeyManager? = null + + /** + * List of cipher suites to enable for TLS connections. If null, uses OkHttp defaults. + * When specified, only the listed cipher suites will be enabled. + */ + public var cipherSuites: List? = null + + /** + * Certificate pinner that validates server certificates against known public key pins. + * Used to prevent man-in-the-middle attacks by ensuring the server presents expected certificates. + */ + public var certificatePinner: CertificatePinner? = null + + /** + * Custom hostname verifier for validating server hostnames during TLS handshake. + * By default, OkHttp verifies that the certificate's hostname matches the request hostname. + * Use this to implement custom hostname verification logic. + */ + public var hostnameVerifier: HostnameVerifier? = null + override var telemetryProvider: TelemetryProvider = TelemetryProvider.Global } } diff --git a/runtime/protocol/http-client-engines/test-suite/build.gradle.kts b/runtime/protocol/http-client-engines/test-suite/build.gradle.kts index 3f0d75176b..e9c3e6555d 100644 --- a/runtime/protocol/http-client-engines/test-suite/build.gradle.kts +++ b/runtime/protocol/http-client-engines/test-suite/build.gradle.kts @@ -26,6 +26,7 @@ kotlin { dependencies { implementation(libs.ktor.server.jetty.jakarta) implementation(libs.ktor.network.tls.certificates) + implementation(libs.okhttp) implementation(project(":runtime:protocol:http-client-engines:http-client-engine-default")) implementation(project(":runtime:protocol:http-client-engines:http-client-engine-crt")) diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt index de47ed47db..a1862bef98 100644 --- a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt @@ -6,50 +6,63 @@ package aws.smithy.kotlin.runtime.http.test import aws.smithy.kotlin.runtime.content.decodeToString import aws.smithy.kotlin.runtime.http.* +import aws.smithy.kotlin.runtime.http.engine.TlsContext +import aws.smithy.kotlin.runtime.http.engine.crt.CrtHttpEngineConfig import aws.smithy.kotlin.runtime.http.engine.okhttp.OkHttpEngineConfig import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.http.test.util.* -import aws.smithy.kotlin.runtime.http.test.util.testServers import aws.smithy.kotlin.runtime.net.TlsVersion +import aws.smithy.kotlin.runtime.net.toUrlString import kotlinx.coroutines.delay -import java.nio.file.Paths -import kotlin.test.* +import javax.net.ssl.HostnameVerifier +import kotlin.io.path.createTempDirectory +import kotlin.io.path.createTempFile +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds class ConnectionTest : AbstractEngineTest() { - private fun testMinTlsVersion(version: TlsVersion, serverType: ServerType) { + private fun testTlsConfigs( + testName: String, + serverType: ServerType, + tlsContext: TlsContext = TlsContext {}, + okHttpConfigBlock: OkHttpEngineConfig.Builder.() -> Unit = {}, + crtConfigBlock: CrtHttpEngineConfig.Builder.() -> Unit = {}, + ) { val url = testServers.getValue(serverType) - val sslConfigPath = System.getProperty("SSL_CONFIG_PATH") - val sslConfig = SslConfig.load(Paths.get(sslConfigPath)) - // Set SSL certs via system properties which HTTP clients should pick up - sslConfig.useAsSystemProperties { + testSslConfig.useAsSystemProperties { testEngines(skipEngines = setOf("CrtHttpEngine")) { engineConfig { - tlsContext { - minVersion = version + this.tlsContext = tlsContext + + if (this is OkHttpEngineConfig.Builder) { + okHttpConfigBlock() + } + + if (this is CrtHttpEngineConfig.Builder) { + crtConfigBlock() } } test { _, client -> - val bodyText = "Testing $version" - val req = HttpRequest { testSetup(url) method = HttpMethod.POST url { path.decoded = "/tlsVerification" } - body = HttpBody.fromBytes(bodyText.encodeToByteArray()) + body = HttpBody.fromBytes(testName.encodeToByteArray()) } val call = client.call(req) assertEquals(HttpStatusCode.OK, call.response.status) val body = call.response.body.toByteStream()?.decodeToString() - assertEquals("Received body: $bodyText", body) + assertEquals("Received body: $testName", body) call.complete() } } @@ -60,29 +73,181 @@ class ConnectionTest : AbstractEngineTest() { // and server need to agree. /* @Test - fun testMinTls1_0() = testMinTlsVersion(TlsVersion.TLS_1_0, ServerType.TLS_1_0) + fun testMinTls1_0() = testTlsConfigs("testMinTls1_0", TlsVersion.TLS_1_0, ServerType.TLS_1_0) @Test - fun testMinTls1_1() = testMinTlsVersion(TlsVersion.TLS_1_1, ServerType.TLS_1_1) + fun testMinTls1_1() = testTlsConfigs("testMinTls1_1", TlsVersion.TLS_1_1, ServerType.TLS_1_1) */ @Test fun testMinTls1_2_vs_Tls_1_1() { - val e = assertFailsWith { testMinTlsVersion(TlsVersion.TLS_1_2, ServerType.TLS_1_1) } + val e = assertFailsWith { + testTlsConfigs("testMinTls1_2", ServerType.TLS_1_1, TlsContext { minVersion = TlsVersion.TLS_1_2 }) + } assertEquals(HttpErrorCode.TLS_NEGOTIATION_ERROR, e.errorCode) } @Test - fun testMinTls1_2() = testMinTlsVersion(TlsVersion.TLS_1_2, ServerType.TLS_1_2) + fun testMinTls1_2() = testTlsConfigs("testMinTls1_2", ServerType.TLS_1_2, TlsContext { minVersion = TlsVersion.TLS_1_2 }) @Test fun testMinTls1_3_vs_Tls_1_2() { - val e = assertFailsWith { testMinTlsVersion(TlsVersion.TLS_1_3, ServerType.TLS_1_2) } + val e = assertFailsWith { + testTlsConfigs("testMinTls1_3_vs_Tls_1_2", ServerType.TLS_1_2, TlsContext { minVersion = TlsVersion.TLS_1_3 }) + } + assertEquals(HttpErrorCode.TLS_NEGOTIATION_ERROR, e.errorCode) + } + + @Test + fun testMinTls1_3() = testTlsConfigs("testMinTls1_3", ServerType.TLS_1_3, TlsContext { minVersion = TlsVersion.TLS_1_3 }) + + @Test + fun testTrustManagerWithTls1_2() { + testTlsConfigs( + "testTrustManagerWithTls1_2", + ServerType.TLS_1_2, + TlsContext { minVersion = TlsVersion.TLS_1_2 }, + okHttpConfigBlock = { + trustManager = createTestTrustManager(testCert) + }, + ) + } + + @Test + fun testTrustManagerWithTls1_3() { + testTlsConfigs( + "testTrustManagerWithTls1_3", + ServerType.TLS_1_3, + TlsContext { minVersion = TlsVersion.TLS_1_3 }, + okHttpConfigBlock = { + trustManager = createTestTrustManager(testCert) + }, + ) + } + + // TODO: Add mutual TLS (mTLS) tests once mTls implementation is available + // This would test keyManagerProvider with servers that require client certificates + + @Test + fun testCipherSuitesWithTls1_2() { + testTlsConfigs( + "testCipherSuitesWithTls1_2", + ServerType.TLS_1_2, + TlsContext { minVersion = TlsVersion.TLS_1_2 }, + okHttpConfigBlock = { + cipherSuites = listOf("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") + }, + ) + } + + @Test + fun testCipherSuitesWithTls1_3() { + // test cipher suites not compatible with Tls1_3 + val e = assertFailsWith { + testTlsConfigs( + "testCipherSuitesWithTls1_3", + ServerType.TLS_1_3, + TlsContext { minVersion = TlsVersion.TLS_1_3 }, + okHttpConfigBlock = { + cipherSuites = listOf("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") + }, + ) + } assertEquals(HttpErrorCode.TLS_NEGOTIATION_ERROR, e.errorCode) + + // test cipher suites compatible with Tls1_3 + testTlsConfigs( + "testCipherSuitesWithTls1_3", + ServerType.TLS_1_3, + TlsContext { minVersion = TlsVersion.TLS_1_3 }, + okHttpConfigBlock = { + cipherSuites = listOf("TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256") + }, + ) + } + + @Test + fun testHostnameVerifier() { + testTlsConfigs( + "testHostnameVerifier", + ServerType.TLS_1_2, + okHttpConfigBlock = { + hostnameVerifier = HostnameVerifier { hostname, _ -> + hostname == testServers.getValue(ServerType.TLS_1_2).host.toUrlString() + } + }, + ) + } + + @Test + fun testCertificatePinner() { + testTlsConfigs( + "testCertificatePinner", + ServerType.TLS_1_2, + okHttpConfigBlock = { + certificatePinner = createTestCertificatePinner(testCert, ServerType.TLS_1_2) + }, + ) } @Test - fun testMinTls1_3() = testMinTlsVersion(TlsVersion.TLS_1_3, ServerType.TLS_1_3) + fun testCertificatePem() { + testTlsConfigs( + "testCertificatePem", + ServerType.TLS_1_2, + crtConfigBlock = { + certificatePem = createTestPemCert(testCert) + }, + ) + } + + @Test + fun testCertificateFile() { + val tempFile = createTempFile("ca-cert", ".pem").toFile() + try { + tempFile.writeText(createTestPemCert(testCert)) + testTlsConfigs( + "testCertificateFile", + ServerType.TLS_1_2, + crtConfigBlock = { + certificateFile = tempFile.absolutePath + }, + ) + } finally { + tempFile.delete() + } + } + + @Test + fun testCertificateDirectory() { + val tempDir = createTempDirectory("ca-certs").toFile() + try { + val certFile = tempDir.resolve("ca-cert.pem") + certFile.writeText(createTestPemCert(testCert)) + + testTlsConfigs( + "testCertificateDirectory", + ServerType.TLS_1_2, + crtConfigBlock = { + certificatesDirectory = tempDir.absolutePath + }, + ) + } finally { + tempDir.deleteRecursively() + } + } + + @Test + fun testVerifyPeerFalse() { + testTlsConfigs( + "testVerifyPeers", + ServerType.TLS_1_2, + crtConfigBlock = { + certificatePem = createInvalidTestPemCert() + verifyPeer = false + }, + ) + } // See https://github.com/awslabs/aws-sdk-kotlin/issues/1214 @Test diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/util/ConnectionTestUtils.kt b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/util/ConnectionTestUtils.kt new file mode 100644 index 0000000000..c99c7e254a --- /dev/null +++ b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/util/ConnectionTestUtils.kt @@ -0,0 +1,82 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.http.test.util + +import aws.smithy.kotlin.runtime.net.toUrlString +import okhttp3.CertificatePinner +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import java.math.BigInteger.valueOf +import java.nio.file.Paths +import java.security.KeyPairGenerator +import java.security.cert.X509Certificate +import java.util.* +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager + +internal val testSslConfig by lazy { + val sslConfigPath = System.getProperty("SSL_CONFIG_PATH") + SslConfig.load(Paths.get(sslConfigPath)) +} + +internal val testCert by lazy { + testSslConfig.keyStore.getCertificate(testSslConfig.certificateAlias) as X509Certificate +} + +fun createTestTrustManager(testCert: X509Certificate): X509TrustManager { + val keyStore = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType()) + keyStore.load(null, null) + keyStore.setCertificateEntry("test-cert", testCert) + + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(keyStore) + return trustManagerFactory.trustManagers[0] as X509TrustManager +} + +fun createTestCertificatePinner(testCert: X509Certificate, serverType: ServerType): CertificatePinner { + val sha256Hash = java.security.MessageDigest.getInstance("SHA-256").digest(testCert.publicKey.encoded) + val pin = "sha256/" + Base64.getEncoder().encodeToString(sha256Hash) + val hostname = testServers.getValue(serverType).host.toUrlString() + + return CertificatePinner.Builder() + .add(hostname, pin) + .build() +} + +fun createTestPemCert(testCert: X509Certificate): String = + """ + -----BEGIN CERTIFICATE----- + ${Base64.getEncoder().encodeToString(testCert.encoded)} + -----END CERTIFICATE----- + """.trimIndent() + +fun createInvalidTestPemCert(): String { + // Generate an invalid certificate PEM + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(2048) + val keyPair = keyPairGenerator.generateKeyPair() + + val subject = X500Name("CN=invalid.example.com") + val serial = valueOf(System.currentTimeMillis()) + val notBefore = Date() + val notAfter = Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L) // 1 year + + val certBuilder = JcaX509v3CertificateBuilder(subject, serial, notBefore, notAfter, subject, keyPair.public) + + val signer = JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private) + val cert = certBuilder.build(signer) + + return """ + -----BEGIN CERTIFICATE----- + ${Base64.getEncoder().encodeToString(cert.encoded).chunked(64).joinToString("\n ")} + -----END CERTIFICATE----- + """.trimIndent() +} diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/config/TimeoutConfig.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/config/TimeoutConfig.kt index 25bc7db84d..a23ef63ac1 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/config/TimeoutConfig.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/config/TimeoutConfig.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.http.config import kotlin.time.Duration diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/ChecksumInterceptorUtils.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/ChecksumInterceptorUtils.kt index b510ab14e5..b4f0e491f0 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/ChecksumInterceptorUtils.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/ChecksumInterceptorUtils.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.http.interceptors import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt index c8d6012d6d..6c055edfa2 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/SmokeTestsInterceptor.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.http.interceptors import aws.smithy.kotlin.runtime.InternalApi diff --git a/runtime/protocol/http-test/jvmAndNative/src/aws/smithy/kotlin/runtime/httptest/TestWithLocalServer.kt b/runtime/protocol/http-test/jvmAndNative/src/aws/smithy/kotlin/runtime/httptest/TestWithLocalServer.kt index 370c8f9b7c..ed76448c10 100644 --- a/runtime/protocol/http-test/jvmAndNative/src/aws/smithy/kotlin/runtime/httptest/TestWithLocalServer.kt +++ b/runtime/protocol/http-test/jvmAndNative/src/aws/smithy/kotlin/runtime/httptest/TestWithLocalServer.kt @@ -5,13 +5,10 @@ package aws.smithy.kotlin.runtime.httptest -import io.ktor.client.request.get -import io.ktor.network.selector.SelectorManager -import io.ktor.network.sockets.InetSocketAddress -import io.ktor.network.sockets.aSocket -import io.ktor.server.application.ApplicationStarted -import io.ktor.server.engine.EmbeddedServer -import io.ktor.utils.io.core.use +import io.ktor.network.selector.* +import io.ktor.network.sockets.* +import io.ktor.server.application.* +import io.ktor.server.engine.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index 1ba5b08c40..597cdafe81 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -110,6 +110,7 @@ public final class aws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetri public static final field ACCOUNT_ID_MODE_DISABLED Laws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetric; public static final field ACCOUNT_ID_MODE_PREFERRED Laws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetric; public static final field ACCOUNT_ID_MODE_REQUIRED Laws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetric; + public static final field BEARER_SERVICE_ENV_VARS Laws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetric; public static final field FLEXIBLE_CHECKSUMS_REQ_CRC32 Laws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetric; public static final field FLEXIBLE_CHECKSUMS_REQ_CRC32C Laws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetric; public static final field FLEXIBLE_CHECKSUMS_REQ_SHA1 Laws/smithy/kotlin/runtime/businessmetrics/SmithyBusinessMetric; diff --git a/runtime/runtime-core/build.gradle.kts b/runtime/runtime-core/build.gradle.kts index cd9e4e8b3f..2b6cc8ece4 100644 --- a/runtime/runtime-core/build.gradle.kts +++ b/runtime/runtime-core/build.gradle.kts @@ -3,7 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +import aws.sdk.kotlin.gradle.kmp.NATIVE_ENABLED +import aws.sdk.kotlin.gradle.util.typedProp import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.konan.target.HostManager +import java.nio.file.Files +import java.nio.file.Paths plugins { alias(libs.plugins.kotlinx.serialization) @@ -29,6 +34,7 @@ kotlin { nativeMain { dependencies { api(libs.crt.kotlin) + implementation(libs.kotlin.multiplatform.bignum) } } @@ -46,25 +52,81 @@ kotlin { } } - nativeMain { - dependencies { - implementation(libs.kotlin.multiplatform.bignum) - } - } - all { languageSettings.optIn("aws.smithy.kotlin.runtime.InternalApi") } } - targets.withType { - compilations["main"].cinterops { - val interopDir = "$projectDir/native/src/nativeInterop/cinterop" - create("environ") { - includeDirs(interopDir) - packageName("aws.smithy.platform.posix") - headers(listOf("$interopDir/environ.h")) + if (NATIVE_ENABLED && !HostManager.hostIsMingw) { + targets.withType { + compilations["main"].cinterops { + val interopDir = "$projectDir/posix/src/posixInterop/cinterop" + create("environ") { + includeDirs(interopDir) + packageName("aws.smithy.platform.posix") + headers(listOf("$interopDir/environ.h")) + } + } + } + } + + if (NATIVE_ENABLED && HostManager.hostIsMingw) { + mingwX64 { + val mingwHome = findMingwHome() + val defPath = layout.buildDirectory.file("cinterop/winver.def") + + // Dynamically construct def file because of dynamic mingw paths + val defFileTask by tasks.registering { + outputs.file(defPath) + + val mingwLibs = Paths.get(mingwHome, "lib").toString().replace("\\", "\\\\") // Windows path shenanigans + + doLast { + Files.writeString( + defPath.get().asFile.toPath(), + """ + package = aws.smithy.kotlin.native.winver + headers = windows.h + compilerOpts = \ + -DUNICODE \ + -DWINVER=0x0601 \ + -D_WIN32_WINNT=0x0601 \ + -DWINAPI_FAMILY=3 \ + -DOEMRESOURCE \ + -Wno-incompatible-pointer-types \ + -Wno-deprecated-declarations + libraryPaths = $mingwLibs + staticLibraries = libversion.a + """.trimIndent(), + ) + } } + compilations["main"].cinterops { + create("winver") { + val mingwIncludes = Paths.get(mingwHome, "include").toString() + includeDirs(mingwIncludes) + definitionFile.set(defPath) + + // Ensure that the def file is written first + tasks[interopProcessingTaskName].dependsOn(defFileTask) + } + } + + // TODO clean up + val compilerArgs = listOf( + "-Xverbose-phases=linker", // Enable verbose linking phase from the compiler + "-linker-option", + "-v", + ) + compilerOptions.freeCompilerArgs.addAll(compilerArgs) } } } + +private fun findMingwHome(): String = + System.getenv("MINGW_PREFIX")?.takeUnless { it.isBlank() } + ?: typedProp("mingw.prefix") + ?: throw IllegalStateException( + "Cannot determine MinGW prefix location. Please verify MinGW is installed correctly " + + "and that either the `MINGW_PREFIX` environment variable or the `mingw.prefix` Gradle property is set.", + ) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/businessmetrics/BusinessMetricsUtils.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/businessmetrics/BusinessMetricsUtils.kt index 13a88aea29..3b3d4e9072 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/businessmetrics/BusinessMetricsUtils.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/businessmetrics/BusinessMetricsUtils.kt @@ -102,6 +102,7 @@ public enum class SmithyBusinessMetric(public override val identifier: String) : FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED("a"), FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED("b"), FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED("c"), + BEARER_SERVICE_ENV_VARS("3"), ; override fun toString(): String = identifier diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSet.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSet.kt index 63924013c9..2c51ddb0c9 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSet.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSet.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.collections internal class CaseInsensitiveMutableStringSet( diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveString.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveString.kt index e182ca813d..e5252f13f6 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveString.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveString.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.collections internal class CaseInsensitiveString(val original: String) { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/retries/policy/StandardRetryPolicy.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/retries/policy/StandardRetryPolicy.kt index 70107f097b..272fa7ed51 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/retries/policy/StandardRetryPolicy.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/retries/policy/StandardRetryPolicy.kt @@ -8,12 +8,21 @@ package aws.smithy.kotlin.runtime.retries.policy import aws.smithy.kotlin.runtime.ClientException import aws.smithy.kotlin.runtime.SdkBaseException import aws.smithy.kotlin.runtime.ServiceException -import aws.smithy.kotlin.runtime.ServiceException.* +import aws.smithy.kotlin.runtime.ServiceException.ErrorType import kotlin.reflect.KClass import kotlin.reflect.safeCast /** - * A standard retry policy which attempts to derive information from the Smithy exception hierarchy. + * A basic retry policy for Smithy clients that defines which error conditions are retryable and how. This policy will + * evaluate the following exceptions as retryable: + * + * * Any [ServiceException] with an `sdkErrorMetadata.errorType` of: + * * [ErrorType.Server] (such as internal service errors) + * * [ErrorType.Client] (such as an invalid request, a resource not found, access denied, etc.) + * * Any [SdkBaseException] where `sdkErrorMetadata.isRetryable` is `true` (such as a client-side timeout, + * networking/socket error, etc.) + * * Any [SdkBaseException] where `sdkErrorMetadata.isThrottling` is `true` (such as making too many requests in a short + * amount of time) */ public open class StandardRetryPolicy : RetryPolicy { public companion object { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt index 9cab81bdbc..8044dc620c 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctions.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.smoketests public expect fun exitProcess(status: Int): Nothing diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSetTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSetTest.kt index 258ef4c7f3..f987fbda82 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSetTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMutableStringSetTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.collections import kotlin.test.* @@ -11,7 +16,12 @@ private val intersecting = subset + disjoint class CaseInsensitiveMutableStringSetTest { private fun assertSize(size: Int, set: CaseInsensitiveMutableStringSet) { assertEquals(size, set.size) - val emptyAsserter: (Boolean) -> Unit = if (size == 0) ::assertTrue else ::assertFalse + val emptyAsserter: (Boolean) -> Unit = + if (size == 0) { + ::assertTrue + } else { + ::assertFalse + } emptyAsserter(set.isEmpty()) } diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveStringTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveStringTest.kt index f3a0c7b600..f76af0d117 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveStringTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveStringTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.collections import kotlin.test.Test diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/JmesPathTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/JmesPathTest.kt index e867f1e8a1..3cf5ce89ae 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/JmesPathTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/JmesPathTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.util import kotlin.test.Test diff --git a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt index b11912df71..0f28cc03fb 100644 --- a/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt +++ b/runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsJVM.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.smoketests import kotlin.system.exitProcess diff --git a/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/io/SdkByteChannelStressTest.kt b/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/io/SdkByteChannelStressTest.kt index 8b1fcc3932..120fd5c5f3 100644 --- a/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/io/SdkByteChannelStressTest.kt +++ b/runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/io/SdkByteChannelStressTest.kt @@ -95,7 +95,7 @@ class SdkByteChannelStressTest { } @Test - fun testReadAllFromFailedChannel() = runBlocking { + fun testReadAllFromFailedChannel(): Unit = runBlocking { val ch = SdkByteChannel(true) ch.cancel(TestException()) assertFailsWith { diff --git a/runtime/runtime-core/mingw/src/aws/smithy/kotlin/runtime/net/DefaultHostResolverMingw.kt b/runtime/runtime-core/mingw/src/aws/smithy/kotlin/runtime/net/DefaultHostResolverMingw.kt new file mode 100644 index 0000000000..75459c8912 --- /dev/null +++ b/runtime/runtime-core/mingw/src/aws/smithy/kotlin/runtime/net/DefaultHostResolverMingw.kt @@ -0,0 +1,101 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.smithy.kotlin.runtime.net + +import kotlinx.cinterop.UnsafeNumber +import kotlinx.cinterop.alloc +import kotlinx.cinterop.allocPointerTo +import kotlinx.cinterop.invoke +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.pointed +import kotlinx.cinterop.ptr +import kotlinx.cinterop.refTo +import kotlinx.cinterop.reinterpret +import kotlinx.cinterop.toKString +import kotlinx.cinterop.value +import platform.posix.AF_INET +import platform.posix.AF_INET6 +import platform.posix.AF_UNSPEC +import platform.posix.SOCK_STREAM +import platform.posix.WSADATA +import platform.posix.memcpy +import platform.posix.sockaddr +import platform.posix.sockaddr_in +import platform.windows.AI_PASSIVE +import platform.windows.WSACleanup +import platform.windows.WSAStartup +import platform.windows.addrinfo +import platform.windows.freeaddrinfo +import platform.windows.gai_strerror +import platform.windows.getaddrinfo +import platform.windows.sockaddr_in6 + +internal actual object DefaultHostResolver : HostResolver { + actual override suspend fun resolve(hostname: String): List = memScoped { + // Version format specified in https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup + val wsaMajorVersion = 1u + val wsaMinorVersion = 1u + val wsaVersion = (wsaMajorVersion shl 8 or wsaMinorVersion).toUShort() + + val wsaInfo = alloc() + val wsaResult = WSAStartup(wsaVersion, wsaInfo.ptr) + check(wsaResult == 0) { "Failed to initialize Windows Sockets (error code $wsaResult)" } + + try { + 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?.invoke(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) + } + } finally { + WSACleanup() + } + } + + @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) { + // No-op, same as JVM implementation + } + + actual override fun purgeCache(addr: HostAddress?) { + // No-op, same as JVM implementation + } +} diff --git a/runtime/runtime-core/mingw/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderMingw.kt b/runtime/runtime-core/mingw/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderMingw.kt new file mode 100644 index 0000000000..0addf5c419 --- /dev/null +++ b/runtime/runtime-core/mingw/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderMingw.kt @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.smithy.kotlin.runtime.util + +import aws.smithy.kotlin.native.winver.* +import kotlinx.cinterop.* +import platform.posix.environ +import platform.posix.memcpy + +public actual object SystemDefaultProvider : SystemDefaultProviderBase() { + actual override val filePathSeparator: String = "\\" + actual override fun osInfo(): OperatingSystem = OperatingSystem(OsFamily.Windows, osVersionFromKernel()) + + actual override fun getAllEnvVars(): Map = memScoped { + generateSequence(0) { it + 1 } + .map { idx -> environ.get(idx)?.toKString() } + .takeWhile { it != null } + .associate { env -> + val parts = env?.split("=", limit = 2) + check(parts?.size == 2) { "Environment entry \"$env\" is malformed" } + parts[0] to parts[1] + } + } +} + +// The functions below are adapted from C++ SDK: +// https://github.com/aws/aws-sdk-cpp/blob/0e6085bf0dd9a1cb1f27d101c4cf2db6ade6f307/src/aws-cpp-sdk-core/source/platform/windows/OSVersionInfo.cpp#L49-L106 + +private val wordHexFormat = HexFormat { + upperCase = false + number { + removeLeadingZeros = true + minLength = 4 + } +} + +private data class LangCodePage( + val language: UShort, + val codePage: UShort, +) + +private fun osVersionFromKernel(): String? = memScoped { + withFileVersionInfo("Kernel32.dll") { versionInfoPtr -> + getLangCodePage(versionInfoPtr)?.let { langCodePage -> + getProductVersion(versionInfoPtr, langCodePage) + } + } +} + +private inline fun withFileVersionInfo(fileName: String, block: (CPointer>) -> R?): R? { + val blobSize = GetFileVersionInfoSizeW(fileName, null) + val blob = ByteArray(blobSize.convert()) + blob.usePinned { pinned -> + val result = GetFileVersionInfoW(fileName, 0u, blobSize, pinned.addressOf(0)) + return if (result == 0) { + null + } else { + block(pinned.addressOf(0)) + } + } +} + +private fun MemScope.getLangCodePage(versionInfoPtr: CPointer>): LangCodePage? { + // Get _any_ language pack and codepage since they should all have the same version + val langAndCodePagePtr = alloc() + val codePageSize = alloc() + val result = VerQueryValueW( + versionInfoPtr, + """\VarFileInfo\Translation""", + langAndCodePagePtr.ptr, + codePageSize.ptr, + ) + + return if (result == 0) { + null + } else { + val langAndCodePage = langAndCodePagePtr.value!!.reinterpret().pointed.value + val language = (langAndCodePage and 0x0000ffffu).toUShort() // low WORD + val codePage = (langAndCodePage and 0xffff0000u shr 16).toUShort() // high WORD + LangCodePage(language, codePage) + } +} + +private fun MemScope.getProductVersion(versionInfoPtr: CPointer>, langCodePage: LangCodePage): String? { + val versionId = buildString { + // Something like: \StringFileInfo\04090fb0\ProductVersion + append("""\StringFileInfo\""") + append(langCodePage.language.toHexString(wordHexFormat)) + append(langCodePage.codePage.toHexString(wordHexFormat)) + append("""\ProductVersion""") + } + + // Get the block corresponding to versionId + val block = alloc() + val blockSize = alloc() + val result = VerQueryValueW(versionInfoPtr, versionId, block.ptr, blockSize.ptr) + + return if (result == 0) { + null + } else { + // Copy the bytes into a Kotlin byte array + val blockBytes = ByteArray(blockSize.value.convert()) + blockBytes.usePinned { pinned -> + memcpy(pinned.addressOf(0), block.value!!.reinterpret(), blockSize.value.convert()) + } + blockBytes.decodeToString() + } +} diff --git a/runtime/runtime-core/mingw/test/aws/smithy/kotlin/runtime/util/SystemPlatformProviderMingwTest.kt b/runtime/runtime-core/mingw/test/aws/smithy/kotlin/runtime/util/SystemPlatformProviderMingwTest.kt new file mode 100644 index 0000000000..22e95ef214 --- /dev/null +++ b/runtime/runtime-core/mingw/test/aws/smithy/kotlin/runtime/util/SystemPlatformProviderMingwTest.kt @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util + +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class SystemPlatformProviderMingwTest { + @Test + fun testOsInfo() = runTest { + val osInfo = PlatformProvider.System.osInfo() + println(osInfo) + assertEquals(OsFamily.Windows, osInfo.family) + assertNotNull(osInfo.version) + } +} diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipCompressor.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipCompressor.kt index 954cfb3bd5..2d4c657af3 100644 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipCompressor.kt +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipCompressor.kt @@ -16,6 +16,7 @@ private const val MEM_LEVEL = 8 // Default memory level /** * Streaming-style gzip compressor, implemented using zlib bindings */ +@OptIn(ExperimentalForeignApi::class) internal class GzipCompressor : Closeable { companion object { internal const val BUFFER_SIZE = 16384 diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipTestUtilsNative.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipTestUtilsNative.kt index 6015984fb3..185019607e 100644 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipTestUtilsNative.kt +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/compression/GzipTestUtilsNative.kt @@ -5,6 +5,7 @@ package aws.smithy.kotlin.runtime.compression import aws.smithy.kotlin.runtime.InternalApi +import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.addressOf import kotlinx.cinterop.alloc import kotlinx.cinterop.memScoped @@ -18,6 +19,7 @@ import platform.zlib.* /** * Decompresses a byte array compressed using the gzip format */ +@OptIn(ExperimentalForeignApi::class) @InternalApi public actual fun decompressGzipBytes(bytes: ByteArray): ByteArray { if (bytes.isEmpty()) { diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt index b11912df71..0f28cc03fb 100644 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/smoketests/SmokeTestsFunctionsNative.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.smithy.kotlin.runtime.smoketests import kotlin.system.exitProcess diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/util/PlatformNative.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/util/PlatformNative.kt deleted file mode 100644 index 220e66fde5..0000000000 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/util/PlatformNative.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.util - -import aws.smithy.kotlin.runtime.io.IOException -import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers -import aws.smithy.platform.posix.get_environ_ptr -import kotlinx.cinterop.* -import kotlinx.coroutines.withContext -import platform.posix.* - -internal actual object SystemDefaultProvider : PlatformProvider { - actual override fun getAllEnvVars(): Map = memScoped { - val environ = get_environ_ptr() - generateSequence(0) { it + 1 } - .map { idx -> environ?.get(idx)?.toKString() } - .takeWhile { it != null } - .associate { env -> - val parts = env?.split("=", limit = 2) - check(parts?.size == 2) { "Environment entry \"$env\" is malformed" } - parts[0] to parts[1] - } - } - - actual override fun getenv(key: String): String? = platform.posix.getenv(key)?.toKString() - - actual override val filePathSeparator: String - get() = when (osInfo().family) { - OsFamily.Windows -> "\\" - else -> "/" - } - - actual override suspend fun readFileOrNull(path: String): ByteArray? = withContext(SdkDispatchers.IO) { - try { - val file = fopen(path, "rb") ?: return@withContext null - - try { - // Get file size - fseek(file, 0L, SEEK_END) - val size = ftell(file) - fseek(file, 0L, SEEK_SET) - - // Read file content - val buffer = ByteArray(size.toInt()).pin() - val rc = fread(buffer.addressOf(0), 1uL, size.toULong(), file) - if (rc == size.toULong()) buffer.get() else null - } finally { - fclose(file) - } - } catch (_: Exception) { - null - } - } - - actual override suspend fun writeFile(path: String, data: ByteArray) = withContext(SdkDispatchers.IO) { - val file = fopen(path, "wb") ?: throw IOException("Cannot open file for writing: $path") - try { - val wc = fwrite(data.refTo(0), 1uL, data.size.toULong(), file) - if (wc != data.size.toULong()) { - throw IOException("Failed to write all bytes to file $path, expected ${data.size.toLong()}, wrote $wc") - } - } finally { - fclose(file) - } - } - - actual override fun fileExists(path: String): Boolean = access(path, F_OK) == 0 - - actual override fun osInfo(): OperatingSystem = memScoped { - val utsname = alloc() - uname(utsname.ptr) - - val sysName = utsname.sysname.toKString().lowercase() - val version = utsname.release.toKString() - val machine = utsname.machine.toKString().lowercase() // Helps differentiate Apple platforms - - val family = when { - sysName.contains("darwin") -> { - when { - machine.startsWith("iphone") -> OsFamily.Ios - // TODO Validate that iPadOS/tvOS/watchOS resolves correctly on each of these devices - machine.startsWith("ipad") -> OsFamily.IpadOs - machine.startsWith("tv") -> OsFamily.TvOs - machine.startsWith("watch") -> OsFamily.WatchOs - else -> OsFamily.MacOs - } - } - sysName.contains("linux") -> OsFamily.Linux - sysName.contains("windows") -> OsFamily.Windows - else -> OsFamily.Unknown - } - - return OperatingSystem(family, version) - } - - actual override val isJvm: Boolean = false - actual override val isAndroid: Boolean = false - actual override val isBrowser: Boolean = false - actual override val isNode: Boolean = false - actual override val isNative: Boolean = true - - // Kotlin/Native doesn't have system properties - actual override fun getAllProperties(): Map = emptyMap() - actual override fun getProperty(key: String): String? = null -} diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderBase.kt b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderBase.kt new file mode 100644 index 0000000000..fde52492e8 --- /dev/null +++ b/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderBase.kt @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util + +import aws.smithy.kotlin.runtime.io.IOException +import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers +import kotlinx.cinterop.* +import kotlinx.coroutines.withContext +import platform.posix.* + +@OptIn(ExperimentalForeignApi::class) +public abstract class SystemDefaultProviderBase : PlatformProvider { + override fun getenv(key: String): String? = platform.posix.getenv(key)?.toKString() + + override suspend fun readFileOrNull(path: String): ByteArray? = withContext(SdkDispatchers.IO) { + try { + val size = memScoped { + val statResult = alloc() + if (stat(path, statResult.ptr) != 0) return@withContext null + statResult.st_size.convert() + } + + val file = fopen(path, "rb") ?: return@withContext null + + try { + // Read file content + val buffer = ByteArray(size).pin() + val rc = fread(buffer.addressOf(0), 1uL, size.toULong(), file) + if (rc == size.toULong()) buffer.get() else null + } finally { + fclose(file) + } + } catch (_: Exception) { + null + } + } + + override suspend fun writeFile(path: String, data: ByteArray): Unit = withContext(SdkDispatchers.IO) { + val file = fopen(path, "wb") ?: throw IOException("Cannot open file for writing: $path") + try { + val wc = fwrite(data.refTo(0), 1uL, data.size.toULong(), file) + if (wc != data.size.toULong()) { + throw IOException("Failed to write all bytes to file $path, expected ${data.size.toLong()}, wrote $wc") + } + } finally { + fclose(file) + } + } + + override fun fileExists(path: String): Boolean = access(path, F_OK) == 0 + + override val isJvm: Boolean = false + override val isAndroid: Boolean = false + override val isBrowser: Boolean = false + override val isNode: Boolean = false + override val isNative: Boolean = true + + // Kotlin/Native doesn't have system properties + override fun getAllProperties(): Map = emptyMap() + override fun getProperty(key: String): String? = null +} diff --git a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt b/runtime/runtime-core/posix/src/aws/smithy/kotlin/runtime/net/DefaultHostResolverPosix.kt similarity index 98% rename from runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt rename to runtime/runtime-core/posix/src/aws/smithy/kotlin/runtime/net/DefaultHostResolverPosix.kt index 9224932ef7..b871ea5095 100644 --- a/runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/net/HostResolverNative.kt +++ b/runtime/runtime-core/posix/src/aws/smithy/kotlin/runtime/net/DefaultHostResolverPosix.kt @@ -7,6 +7,7 @@ package aws.smithy.kotlin.runtime.net import kotlinx.cinterop.* import platform.posix.* +@OptIn(ExperimentalForeignApi::class) internal actual object DefaultHostResolver : HostResolver { actual override suspend fun resolve(hostname: String): List = memScoped { val hints = alloc().apply { @@ -31,7 +32,6 @@ internal actual object DefaultHostResolver : HostResolver { } } - @OptIn(UnsafeNumber::class) private fun sockaddr.toIpAddr(): IpAddr { val (size, addrPtr, constructor) = when (sa_family.toInt()) { AF_INET -> Triple( diff --git a/runtime/runtime-core/posix/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderPosix.kt b/runtime/runtime-core/posix/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderPosix.kt new file mode 100644 index 0000000000..9c1010c6b0 --- /dev/null +++ b/runtime/runtime-core/posix/src/aws/smithy/kotlin/runtime/util/SystemDefaultProviderPosix.kt @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util + +import aws.smithy.platform.posix.get_environ_ptr +import kotlinx.cinterop.* +import platform.posix.uname +import platform.posix.utsname + +@OptIn(ExperimentalForeignApi::class) +public actual object SystemDefaultProvider : SystemDefaultProviderBase() { + actual override val filePathSeparator: String = "/" + + actual override fun osInfo(): OperatingSystem = memScoped { + val utsname = alloc() + uname(utsname.ptr) + + val sysName = utsname.sysname.toKString().lowercase() + val version = utsname.release.toKString() + val machine = utsname.machine.toKString().lowercase() // Helps differentiate Apple platforms + + val family = when { + sysName.contains("darwin") -> { + when { + machine.startsWith("iphone") -> OsFamily.Ios + // TODO Validate that iPadOS/tvOS/watchOS resolves correctly on each of these devices + machine.startsWith("ipad") -> OsFamily.IpadOs + machine.startsWith("tv") -> OsFamily.TvOs + machine.startsWith("watch") -> OsFamily.WatchOs + else -> OsFamily.MacOs + } + } + sysName.contains("linux") -> OsFamily.Linux + else -> OsFamily.Unknown + } + + return OperatingSystem(family, version) + } + + actual override fun getAllEnvVars(): Map = memScoped { + val environ = get_environ_ptr() + generateSequence(0) { it + 1 } + .map { idx -> environ?.get(idx)?.toKString() } + .takeWhile { it != null } + .associate { env -> + val parts = env?.split("=", limit = 2) + check(parts?.size == 2) { "Environment entry \"$env\" is malformed" } + parts[0] to parts[1] + } + } +} diff --git a/runtime/runtime-core/native/src/nativeInterop/cinterop/environ.h b/runtime/runtime-core/posix/src/posixInterop/cinterop/environ.h similarity index 96% rename from runtime/runtime-core/native/src/nativeInterop/cinterop/environ.h rename to runtime/runtime-core/posix/src/posixInterop/cinterop/environ.h index 6262167426..aa6348ea0d 100644 --- a/runtime/runtime-core/native/src/nativeInterop/cinterop/environ.h +++ b/runtime/runtime-core/posix/src/posixInterop/cinterop/environ.h @@ -9,4 +9,4 @@ char** get_environ_ptr() { return environ; } -#endif +#endif \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 19d2caa85d..a39f42c908 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,6 +23,13 @@ dependencyResolutionManagement { mavenLocal() mavenCentral() google() + maven { + name = "kotlinRepoTools" + url = java.net.URI("https://d2gys1nrxnjnyg.cloudfront.net/releases") + content { + includeGroupByRegex("""aws\.sdk\.kotlin.*""") + } + } } } diff --git a/tests/codegen/nullability-tests/build.gradle.kts b/tests/codegen/nullability-tests/build.gradle.kts index 89e335cb34..c438a8d175 100644 --- a/tests/codegen/nullability-tests/build.gradle.kts +++ b/tests/codegen/nullability-tests/build.gradle.kts @@ -45,8 +45,6 @@ kotlin.sourceSets.getByName("main") { tasks.withType { dependsOn(tasks.generateSmithyProjections) - // FIXME - generated code has warnings unfortunately, see https://github.com/awslabs/aws-sdk-kotlin/issues/1169 - compilerOptions.allWarningsAsErrors = false } tasks.test { diff --git a/tests/codegen/serde-tests/build.gradle.kts b/tests/codegen/serde-tests/build.gradle.kts index cbde9d4059..fac6eb51ca 100644 --- a/tests/codegen/serde-tests/build.gradle.kts +++ b/tests/codegen/serde-tests/build.gradle.kts @@ -96,8 +96,6 @@ kotlin.sourceSets.getByName("main") { tasks.withType { dependsOn(stageGeneratedSources) - // generated code has warnings unfortunately, see https://github.com/awslabs/aws-sdk-kotlin/issues/1169 - compilerOptions.allWarningsAsErrors = false } tasks.clean.configure { diff --git a/tests/codegen/waiter-tests/build.gradle.kts b/tests/codegen/waiter-tests/build.gradle.kts index 992a4d471e..4171d35fee 100644 --- a/tests/codegen/waiter-tests/build.gradle.kts +++ b/tests/codegen/waiter-tests/build.gradle.kts @@ -38,8 +38,9 @@ kotlin.sourceSets.getByName("main") { tasks.withType { dependsOn(tasks.generateSmithyProjections) + + // FIXME Re-enable warnings as errors SDK-KT-785 compilerOptions { - // generated code has warnings unfortunately allWarningsAsErrors = false } }