Skip to content

Commit

Permalink
Add iOS SocketTransport implementation (#542)
Browse files Browse the repository at this point in the history
This is rather crude, but it seems to work. Protocol and Transport are blocking APIs; in Android (and Java generally) that's okay, but Apple strongly pushes you towards async networking in iOS. So strongly in fact that all high-level APIs that are not yet deprecated are async-only, including Network.framework. This framework is also the only non-deprecated game in town when it comes to TLS.

In order to bridge the gap between Network.framework and our blocking APIs, this PR makes extensive use of dispatch semaphores - essentially, we block the calling thread until a completion handler signals the semaphore.

In the next major version of Thrifty, we should see about making the core APIs suspend, with blocking shims for migration. In that version, we can drop this charade and just use ktor or something. Until that glorious day, we get NwSocket.
  • Loading branch information
benjamin-bader authored Oct 17, 2023
1 parent 436e7f2 commit 188faba
Show file tree
Hide file tree
Showing 9 changed files with 749 additions and 8 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/pre-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
jvm-version: [17]
os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest, macos-13]
env:
JDK_VERSION: ${{ matrix.jvm-version }}
GRADLE_OPTS: -Dorg.gradle.daemon=false
Expand All @@ -34,6 +34,7 @@ jobs:
path: |
~/.gradle/caches/
~/.gradle/wrapper/
~/.konan/
.build-cache/
key: cache-gradle-${{ matrix.os }}-${{ matrix.jvm-version }}-${{ hashFiles('settings.gradle') }}-${{ hashFiles('**/build.gradle') }}
restore-keys: |
Expand All @@ -56,6 +57,16 @@ jobs:
shell: bash
run: ./gradlew check codeCoverageReport --parallel --no-daemon

- name: Boot simulator
if: matrix.os == 'macos-13'
shell: bash
run: xcrun simctl boot 'iPhone 14 Pro Max' || true

- name: Run simulator tests
if: matrix.os == 'macos-13'
shell: bash
run: ./gradlew -PiosDevice="iPhone 14 Pro Max" iosTest

- name: Upload coverage stats
if: success() && matrix.os == 'ubuntu-latest' && matrix.jvm-version == '8'
uses: codecov/codecov-action@v3
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m

# Build cache is helpful
org.gradle.caching=true

kotlin.mpp.enableCInteropCommonization=true
29 changes: 29 additions & 0 deletions thrifty-runtime/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ kotlin {
baseName = "Thrifty"
}
}

compilations.main.cinterops {
KT62102Workaround {}
}
}

iosX64 {
Expand All @@ -55,6 +59,10 @@ kotlin {
baseName = "Thrifty"
}
}

compilations.main.cinterops {
KT62102Workaround {}
}
}

sourceSets {
Expand Down Expand Up @@ -98,6 +106,12 @@ kotlin {

iosTest {
dependsOn commonTest

dependencies {
implementation libs.kotlin.test.common
implementation libs.kotest.assertions.common
implementation libs.kotest.assertions.core
}
}

iosArm64Main {
Expand All @@ -122,6 +136,21 @@ jvmTest {
useJUnitPlatform()
}

tasks.register("iosTest") {
def device = project.findProperty("iosDevice")?.toString() ?: "iPhone 15 Pro Max"
dependsOn 'linkDebugTestIosX64'
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Runs tests for target 'ios' on an iOS simulator"

doLast {
def binary = kotlin.targets.iosX64.binaries.getTest('DEBUG').outputFile
println("muh binary: ${binary.absolutePath}")
exec {
commandLine 'xcrun', 'simctl', 'spawn', device, binary.absolutePath
}
}
}

// What have I gotten myself in to
configurations {
jvmApiElements {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Thrifty
*
* Copyright (c) Microsoft Corporation
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
* FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
*
* See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/
package com.microsoft.thrifty.transport

expect class SocketTransport internal constructor(
builder: Builder
) : Transport {
class Builder(host: String, port: Int) {
/**
* The number of milliseconds to wait for a connection to be established.
*/
fun connectTimeout(connectTimeout: Int): Builder

/**
* The number of milliseconds a read operation should wait for completion.
*/
fun readTimeout(readTimeout: Int): Builder

/**
* Enable TLS for this connection.
*/
fun enableTls(enableTls: Boolean): Builder

fun build(): SocketTransport
}

@Throws(okio.IOException::class)
fun connect()
}
Loading

0 comments on commit 188faba

Please sign in to comment.