diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f69ca4a7..73f12087 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,6 +198,73 @@ jobs: ./gradlew :examples:simple_client:build ./gradlew :examples:quic_client:build + swift-bindings: + name: Swift Bindings + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 + with: + components: llvm-tools-preview + + - name: Install cargo-llvm-cov + run: cargo install cargo-llvm-cov + + - name: Cache Cargo dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-swift-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-swift- + + - name: Build Rust, Generate Swift Bindings, and Verify swift build + run: ./scripts/build_swift_bindings.sh --coverage --test + + - name: Export coverage to lcov + run: | + PROFDATA_VAR=$(cargo +stable llvm-cov show-env | grep LLVM_PROFDATA) || true + PROFDATA_TOOL=$(echo "$PROFDATA_VAR" | cut -d= -f2- | tr -d '"' | tr -d "'" | xargs) + if [ -z "$PROFDATA_TOOL" ] || [ ! -x "$PROFDATA_TOOL" ]; then + export PATH="$(rustc --print=target-libdir)/../bin:$PATH" + PROFDATA_TOOL=$(which llvm-profdata) + fi + if ls target/llvm-cov-target/*.profraw 2>/dev/null; then + "$PROFDATA_TOOL" merge -sparse target/llvm-cov-target/*.profraw \ + -o target/llvm-cov-target/swift-cov.profdata + llvm-cov export -format=lcov \ + --instr-profile target/llvm-cov-target/swift-cov.profdata \ + -object target/debug/libflowsdk_ffi.dylib \ + > lcov-swift.info + else + echo "No profraw files found; skipping lcov export." + fi + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: swift-coverage-data + path: | + lcov-swift.info + target/llvm-cov-target/swift-cov.profdata + target/llvm-cov-target/*.profraw + retention-days: 7 + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + files: lcov-swift.info + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + coverage: name: Code Coverage runs-on: ubuntu-latest @@ -286,6 +353,8 @@ jobs: os: ubuntu-latest - target: x86_64-apple-darwin os: macos-latest + - target: aarch64-apple-darwin + os: macos-latest - target: x86_64-pc-windows-msvc os: windows-latest diff --git a/.gitignore b/.gitignore index 07eff542..96e4e019 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,19 @@ Cargo.lock *.profraw *.profdata - -# FFI generated code +# Swift: runtime library copies and XCFramework build artifacts (populated by build_swift_bindings.sh) +swift/.build/ +swift/.swiftpm/ +swift/lib/ +swift/Generated/ +swift/xcframework-intermediates/ +swift/FlowSDK.xcframework/ + + +# FFI generated code (regenerated by scripts/build_*_bindings.sh; feature-dependent) +swift/Sources/FlowSDK/flowsdk_ffi.swift +swift/Sources/flowsdk_ffi/flowsdk_ffi.h +kotlin/package/src/main/kotlin/uniffi/flowsdk_ffi/flowsdk_ffi.kt python/package/flowsdk/flowsdk_ffi.py python/package/flowsdk/libflowsdk_ffi.dylib python/package/flowsdk/libflowsdk_ffi.so @@ -22,6 +33,7 @@ python/examples/*.so python/examples/*.dll python/**/*.pyc +python/package/flowsdk.egg-info/ # Kotlin Examples (generated files) kotlin/package/src/main/resources/libflowsdk_ffi.dylib diff --git a/examples/no_io_tokio_quic_client_example.rs b/examples/no_io_tokio_quic_client_example.rs index 0bbb828a..dcd2777d 100644 --- a/examples/no_io_tokio_quic_client_example.rs +++ b/examples/no_io_tokio_quic_client_example.rs @@ -44,20 +44,20 @@ async fn run_example() -> Result<(), Box> { event = client.next_event() => { let Some(event) = event else { break; }; match event { - MqttEvent::Connected(_) => { - if !subscribed { - let sub_cmd = SubscribeCommand::builder().add_topic("test/topic/wrapper", 1).build()?; - client.subscribe(sub_cmd).await.map_err(|e| e.to_string())?; - subscribed = true; - } + MqttEvent::Connected(_) + if !subscribed => + { + let sub_cmd = SubscribeCommand::builder().add_topic("test/topic/wrapper", 1).build()?; + client.subscribe(sub_cmd).await.map_err(|e| e.to_string())?; + subscribed = true; } - MqttEvent::Subscribed(res) => { + MqttEvent::Subscribed(res) + if !published => + { println!("Subscribed: ID={:?}", res.packet_id); - if !published { - let pub_cmd = PublishCommand::builder().topic("test/topic/wrapper").payload("Hello!".to_string()).qos(1).build()?; - client.publish(pub_cmd).await.map_err(|e| e.to_string())?; - published = true; - } + let pub_cmd = PublishCommand::builder().topic("test/topic/wrapper").payload("Hello!".to_string()).qos(1).build()?; + client.publish(pub_cmd).await.map_err(|e| e.to_string())?; + published = true; } MqttEvent::Published(res) => { println!("Published: ID={:?}", res.packet_id); diff --git a/kotlin/package/src/main/kotlin/uniffi/flowsdk_ffi/flowsdk_ffi.kt b/kotlin/package/src/main/kotlin/uniffi/flowsdk_ffi/flowsdk_ffi.kt deleted file mode 100644 index 24344645..00000000 --- a/kotlin/package/src/main/kotlin/uniffi/flowsdk_ffi/flowsdk_ffi.kt +++ /dev/null @@ -1,3800 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -@file:Suppress("NAME_SHADOWING") - -package uniffi.flowsdk_ffi - -// Common helper code. -// -// Ideally this would live in a separate .kt file where it can be unittested etc -// in isolation, and perhaps even published as a re-useable package. -// -// However, it's important that the details of how this helper code works (e.g. the -// way that different builtin types are passed across the FFI) exactly match what's -// expected by the Rust code on the other side of the interface. In practice right -// now that means coming from the exact some version of `uniffi` that was used to -// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin -// helpers directly inline like we're doing here. - -import com.sun.jna.Library -import com.sun.jna.IntegerType -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.Structure -import com.sun.jna.Callback -import com.sun.jna.ptr.* -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.nio.CharBuffer -import java.nio.charset.CodingErrorAction -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean - -// This is a helper for safely working with byte buffers returned from the Rust code. -// A rust-owned buffer is represented by its capacity, its current length, and a -// pointer to the underlying data. - -/** - * @suppress - */ -@Structure.FieldOrder("capacity", "len", "data") -open class RustBuffer : Structure() { - // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. - // When dealing with these fields, make sure to call `toULong()`. - @JvmField var capacity: Long = 0 - @JvmField var len: Long = 0 - @JvmField var data: Pointer? = null - - class ByValue: RustBuffer(), Structure.ByValue - class ByReference: RustBuffer(), Structure.ByReference - - internal fun setValue(other: RustBuffer) { - capacity = other.capacity - len = other.len - data = other.data - } - - companion object { - internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status -> - // Note: need to convert the size to a `Long` value to make this work with JVM. - UniffiLib.INSTANCE.ffi_flowsdk_ffi_rustbuffer_alloc(size.toLong(), status) - }.also { - if(it.data == null) { - throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") - } - } - - internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue { - var buf = RustBuffer.ByValue() - buf.capacity = capacity.toLong() - buf.len = len.toLong() - buf.data = data - return buf - } - - internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status -> - UniffiLib.INSTANCE.ffi_flowsdk_ffi_rustbuffer_free(buf, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } -} - -/** - * The equivalent of the `*mut RustBuffer` type. - * Required for callbacks taking in an out pointer. - * - * Size is the sum of all values in the struct. - * - * @suppress - */ -class RustBufferByReference : ByReference(16) { - /** - * Set the pointed-to `RustBuffer` to the given value. - */ - fun setValue(value: RustBuffer.ByValue) { - // NOTE: The offsets are as they are in the C-like struct. - val pointer = getPointer() - pointer.setLong(0, value.capacity) - pointer.setLong(8, value.len) - pointer.setPointer(16, value.data) - } - - /** - * Get a `RustBuffer.ByValue` from this reference. - */ - fun getValue(): RustBuffer.ByValue { - val pointer = getPointer() - val value = RustBuffer.ByValue() - value.writeField("capacity", pointer.getLong(0)) - value.writeField("len", pointer.getLong(8)) - value.writeField("data", pointer.getLong(16)) - - return value - } -} - -// This is a helper for safely passing byte references into the rust code. -// It's not actually used at the moment, because there aren't many things that you -// can take a direct pointer to in the JVM, and if we're going to copy something -// then we might as well copy it into a `RustBuffer`. But it's here for API -// completeness. - -@Structure.FieldOrder("len", "data") -internal open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue -} -/** - * The FfiConverter interface handles converter types to and from the FFI - * - * All implementing objects should be public to support external types. When a - * type is external we need to import it's FfiConverter. - * - * @suppress - */ -public interface FfiConverter { - // Convert an FFI type to a Kotlin type - fun lift(value: FfiType): KotlinType - - // Convert an Kotlin type to an FFI type - fun lower(value: KotlinType): FfiType - - // Read a Kotlin type from a `ByteBuffer` - fun read(buf: ByteBuffer): KotlinType - - // Calculate bytes to allocate when creating a `RustBuffer` - // - // This must return at least as many bytes as the write() function will - // write. It can return more bytes than needed, for example when writing - // Strings we can't know the exact bytes needed until we the UTF-8 - // encoding, so we pessimistically allocate the largest size possible (3 - // bytes per codepoint). Allocating extra bytes is not really a big deal - // because the `RustBuffer` is short-lived. - fun allocationSize(value: KotlinType): ULong - - // Write a Kotlin type to a `ByteBuffer` - fun write(value: KotlinType, buf: ByteBuffer) - - // Lower a value into a `RustBuffer` - // - // This method lowers a value into a `RustBuffer` rather than the normal - // FfiType. It's used by the callback interface code. Callback interface - // returns are always serialized into a `RustBuffer` regardless of their - // normal FFI type. - fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { - val rbuf = RustBuffer.alloc(allocationSize(value)) - try { - val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { - it.order(ByteOrder.BIG_ENDIAN) - } - write(value, bbuf) - rbuf.writeField("len", bbuf.position().toLong()) - return rbuf - } catch (e: Throwable) { - RustBuffer.free(rbuf) - throw e - } - } - - // Lift a value from a `RustBuffer`. - // - // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. - // It's currently only used by the `FfiConverterRustBuffer` class below. - fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { - val byteBuf = rbuf.asByteBuffer()!! - try { - val item = read(byteBuf) - if (byteBuf.hasRemaining()) { - throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") - } - return item - } finally { - RustBuffer.free(rbuf) - } - } -} - -/** - * FfiConverter that uses `RustBuffer` as the FfiType - * - * @suppress - */ -public interface FfiConverterRustBuffer: FfiConverter { - override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) - override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) -} -// A handful of classes and functions to support the generated data structures. -// This would be a good candidate for isolating in its own ffi-support lib. - -internal const val UNIFFI_CALL_SUCCESS = 0.toByte() -internal const val UNIFFI_CALL_ERROR = 1.toByte() -internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() - -@Structure.FieldOrder("code", "error_buf") -internal open class UniffiRustCallStatus : Structure() { - @JvmField var code: Byte = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - class ByValue: UniffiRustCallStatus(), Structure.ByValue - - fun isSuccess(): Boolean { - return code == UNIFFI_CALL_SUCCESS - } - - fun isError(): Boolean { - return code == UNIFFI_CALL_ERROR - } - - fun isPanic(): Boolean { - return code == UNIFFI_CALL_UNEXPECTED_ERROR - } - - companion object { - fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue { - val callStatus = UniffiRustCallStatus.ByValue() - callStatus.code = code - callStatus.error_buf = errorBuf - return callStatus - } - } -} - -class InternalException(message: String) : kotlin.Exception(message) - -/** - * Each top-level error class has a companion object that can lift the error from the call status's rust buffer - * - * @suppress - */ -interface UniffiRustCallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; -} - -// Helpers for calling Rust -// In practice we usually need to be synchronized to call this safely, so it doesn't -// synchronize itself - -// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err -private inline fun uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler, callback: (UniffiRustCallStatus) -> U): U { - var status = UniffiRustCallStatus() - val return_value = callback(status) - uniffiCheckCallStatus(errorHandler, status) - return return_value -} - -// Check UniffiRustCallStatus and throw an error if the call wasn't successful -private fun uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler, status: UniffiRustCallStatus) { - if (status.isSuccess()) { - return - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(FfiConverterString.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } -} - -/** - * UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR - * - * @suppress - */ -object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } -} - -// Call a rust function that returns a plain value -private inline fun uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { - return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback) -} - -internal inline fun uniffiTraitInterfaceCall( - callStatus: UniffiRustCallStatus, - makeCall: () -> T, - writeReturn: (T) -> Unit, -) { - try { - writeReturn(makeCall()) - } catch(e: kotlin.Exception) { - callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR - callStatus.error_buf = FfiConverterString.lower(e.toString()) - } -} - -internal inline fun uniffiTraitInterfaceCallWithError( - callStatus: UniffiRustCallStatus, - makeCall: () -> T, - writeReturn: (T) -> Unit, - lowerError: (E) -> RustBuffer.ByValue -) { - try { - writeReturn(makeCall()) - } catch(e: kotlin.Exception) { - if (e is E) { - callStatus.code = UNIFFI_CALL_ERROR - callStatus.error_buf = lowerError(e) - } else { - callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR - callStatus.error_buf = FfiConverterString.lower(e.toString()) - } - } -} -// Map handles to objects -// -// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. -internal class UniffiHandleMap { - private val map = ConcurrentHashMap() - private val counter = java.util.concurrent.atomic.AtomicLong(0) - - val size: Int - get() = map.size - - // Insert a new object into the handle map and get a handle for it - fun insert(obj: T): Long { - val handle = counter.getAndAdd(1) - map.put(handle, obj) - return handle - } - - // Get an object from the handle map - fun get(handle: Long): T { - return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") - } - - // Remove an entry from the handlemap and get the Kotlin object back - fun remove(handle: Long): T { - return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") - } -} - -// Contains loading, initialization code, -// and the FFI Function declarations in a com.sun.jna.Library. -@Synchronized -private fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "flowsdk_ffi" -} - -private inline fun loadIndirect( - componentName: String -): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) -} - -// Define FFI callback types -internal interface UniffiRustFutureContinuationCallback : com.sun.jna.Callback { - fun callback(`data`: Long,`pollResult`: Byte,) -} -internal interface UniffiForeignFutureFree : com.sun.jna.Callback { - fun callback(`handle`: Long,) -} -internal interface UniffiCallbackInterfaceFree : com.sun.jna.Callback { - fun callback(`handle`: Long,) -} -@Structure.FieldOrder("handle", "free") -internal open class UniffiForeignFuture( - @JvmField internal var `handle`: Long = 0.toLong(), - @JvmField internal var `free`: UniffiForeignFutureFree? = null, -) : Structure() { - class UniffiByValue( - `handle`: Long = 0.toLong(), - `free`: UniffiForeignFutureFree? = null, - ): UniffiForeignFuture(`handle`,`free`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFuture) { - `handle` = other.`handle` - `free` = other.`free` - } - -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructU8( - @JvmField internal var `returnValue`: Byte = 0.toByte(), - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Byte = 0.toByte(), - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructU8(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructU8) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteU8 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU8.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructI8( - @JvmField internal var `returnValue`: Byte = 0.toByte(), - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Byte = 0.toByte(), - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructI8(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructI8) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteI8 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI8.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructU16( - @JvmField internal var `returnValue`: Short = 0.toShort(), - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Short = 0.toShort(), - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructU16(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructU16) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteU16 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU16.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructI16( - @JvmField internal var `returnValue`: Short = 0.toShort(), - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Short = 0.toShort(), - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructI16(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructI16) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteI16 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI16.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructU32( - @JvmField internal var `returnValue`: Int = 0, - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Int = 0, - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructU32(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructU32) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteU32 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU32.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructI32( - @JvmField internal var `returnValue`: Int = 0, - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Int = 0, - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructI32(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructI32) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteI32 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI32.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructU64( - @JvmField internal var `returnValue`: Long = 0.toLong(), - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Long = 0.toLong(), - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructU64(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructU64) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteU64 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructU64.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructI64( - @JvmField internal var `returnValue`: Long = 0.toLong(), - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Long = 0.toLong(), - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructI64(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructI64) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteI64 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructI64.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructF32( - @JvmField internal var `returnValue`: Float = 0.0f, - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Float = 0.0f, - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructF32(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructF32) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteF32 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructF32.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructF64( - @JvmField internal var `returnValue`: Double = 0.0, - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Double = 0.0, - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructF64(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructF64) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteF64 : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructF64.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructPointer( - @JvmField internal var `returnValue`: Pointer = Pointer.NULL, - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: Pointer = Pointer.NULL, - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructPointer(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructPointer) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompletePointer : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructPointer.UniffiByValue,) -} -@Structure.FieldOrder("returnValue", "callStatus") -internal open class UniffiForeignFutureStructRustBuffer( - @JvmField internal var `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(), - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructRustBuffer(`returnValue`,`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructRustBuffer) { - `returnValue` = other.`returnValue` - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteRustBuffer : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructRustBuffer.UniffiByValue,) -} -@Structure.FieldOrder("callStatus") -internal open class UniffiForeignFutureStructVoid( - @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), -) : Structure() { - class UniffiByValue( - `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(), - ): UniffiForeignFutureStructVoid(`callStatus`,), Structure.ByValue - - internal fun uniffiSetValue(other: UniffiForeignFutureStructVoid) { - `callStatus` = other.`callStatus` - } - -} -internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback { - fun callback(`callbackData`: Long,`result`: UniffiForeignFutureStructVoid.UniffiByValue,) -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// A JNA Library to expose the extern-C FFI definitions. -// This is an implementation detail which will be called internally by the public API. - -internal interface UniffiLib : Library { - companion object { - internal val INSTANCE: UniffiLib by lazy { - loadIndirect(componentName = "flowsdk_ffi") - .also { lib: UniffiLib -> - uniffiCheckContractApiVersion(lib) - uniffiCheckApiChecksums(lib) - } - } - - // The Cleaner for the whole library - internal val CLEANER: UniffiCleaner by lazy { - UniffiCleaner.create() - } - } - - fun uniffi_flowsdk_ffi_fn_clone_mqttengineffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_free_mqttengineffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_constructor_mqttengineffi_new(`clientId`: RustBuffer.ByValue,`mqttVersion`: Byte,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_constructor_mqttengineffi_new_with_opts(`opts`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_auth(`ptr`: Pointer,`reasonCode`: Byte,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_connect(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_disconnect(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_get_version(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Byte - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_handle_connection_lost(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_handle_incoming(`ptr`: Pointer,`data`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_handle_tick(`ptr`: Pointer,`nowMs`: Long,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_is_connected(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Byte - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_next_tick_ms(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Long - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_publish(`ptr`: Pointer,`topic`: RustBuffer.ByValue,`payload`: RustBuffer.ByValue,`qos`: Byte,`priority`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_push_event_ffi(`ptr`: Pointer,`event`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_subscribe(`ptr`: Pointer,`topicFilter`: RustBuffer.ByValue,`qos`: Byte,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_take_events(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_take_outgoing(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_mqttengineffi_unsubscribe(`ptr`: Pointer,`topicFilter`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_clone_mqtteventlistffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_free_mqtteventlistffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_mqtteventlistffi_get(`ptr`: Pointer,`index`: Int,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_mqtteventlistffi_is_empty(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Byte - fun uniffi_flowsdk_ffi_fn_method_mqtteventlistffi_len(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_clone_quicmqttengineffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_free_quicmqttengineffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_constructor_quicmqttengineffi_new(`opts`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_connect(`ptr`: Pointer,`serverAddr`: RustBuffer.ByValue,`serverName`: RustBuffer.ByValue,`tlsOpts`: RustBuffer.ByValue,`nowMs`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_disconnect(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_handle_datagram(`ptr`: Pointer,`data`: RustBuffer.ByValue,`remoteAddr`: RustBuffer.ByValue,`nowMs`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_handle_tick(`ptr`: Pointer,`nowMs`: Long,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_is_connected(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Byte - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_publish(`ptr`: Pointer,`topic`: RustBuffer.ByValue,`payload`: RustBuffer.ByValue,`qos`: Byte,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_subscribe(`ptr`: Pointer,`topicFilter`: RustBuffer.ByValue,`qos`: Byte,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_take_events(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_take_outgoing_datagrams(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_unsubscribe(`ptr`: Pointer,`topicFilter`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_clone_tlsmqttengineffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_free_tlsmqttengineffi(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_constructor_tlsmqttengineffi_new(`opts`: RustBuffer.ByValue,`tlsOpts`: RustBuffer.ByValue,`serverName`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_connect(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_disconnect(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_handle_socket_data(`ptr`: Pointer,`data`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_handle_tick(`ptr`: Pointer,`nowMs`: Long,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_is_connected(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): Byte - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_publish(`ptr`: Pointer,`topic`: RustBuffer.ByValue,`payload`: RustBuffer.ByValue,`qos`: Byte,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_subscribe(`ptr`: Pointer,`topicFilter`: RustBuffer.ByValue,`qos`: Byte,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_take_events(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_take_socket_data(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_unsubscribe(`ptr`: Pointer,`topicFilter`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun ffi_flowsdk_ffi_rustbuffer_alloc(`size`: Long,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun ffi_flowsdk_ffi_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun ffi_flowsdk_ffi_rustbuffer_free(`buf`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun ffi_flowsdk_ffi_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Long,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun ffi_flowsdk_ffi_rust_future_poll_u8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_u8(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_u8(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_u8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Byte - fun ffi_flowsdk_ffi_rust_future_poll_i8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_i8(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_i8(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_i8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Byte - fun ffi_flowsdk_ffi_rust_future_poll_u16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_u16(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_u16(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_u16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Short - fun ffi_flowsdk_ffi_rust_future_poll_i16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_i16(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_i16(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_i16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Short - fun ffi_flowsdk_ffi_rust_future_poll_u32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_u32(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_u32(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_u32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun ffi_flowsdk_ffi_rust_future_poll_i32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_i32(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_i32(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_i32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Int - fun ffi_flowsdk_ffi_rust_future_poll_u64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_u64(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_u64(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_u64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Long - fun ffi_flowsdk_ffi_rust_future_poll_i64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_i64(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_i64(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_i64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Long - fun ffi_flowsdk_ffi_rust_future_poll_f32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_f32(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_f32(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_f32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Float - fun ffi_flowsdk_ffi_rust_future_poll_f64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_f64(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_f64(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_f64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Double - fun ffi_flowsdk_ffi_rust_future_poll_pointer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_pointer(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_pointer(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_pointer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Pointer - fun ffi_flowsdk_ffi_rust_future_poll_rust_buffer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_rust_buffer(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_rust_buffer(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_rust_buffer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): RustBuffer.ByValue - fun ffi_flowsdk_ffi_rust_future_poll_void(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_cancel_void(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_free_void(`handle`: Long, - ): Unit - fun ffi_flowsdk_ffi_rust_future_complete_void(`handle`: Long,uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_auth( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_connect( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_disconnect( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_get_version( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_handle_connection_lost( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_handle_incoming( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_handle_tick( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_is_connected( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_next_tick_ms( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_publish( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_push_event_ffi( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_subscribe( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_take_events( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_take_outgoing( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqttengineffi_unsubscribe( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqtteventlistffi_get( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqtteventlistffi_is_empty( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_mqtteventlistffi_len( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_connect( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_disconnect( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_handle_datagram( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_handle_tick( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_is_connected( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_publish( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_subscribe( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_take_events( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_take_outgoing_datagrams( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_unsubscribe( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_connect( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_disconnect( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_handle_socket_data( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_handle_tick( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_is_connected( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_publish( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_subscribe( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_take_events( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_take_socket_data( - ): Short - fun uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_unsubscribe( - ): Short - fun uniffi_flowsdk_ffi_checksum_constructor_mqttengineffi_new( - ): Short - fun uniffi_flowsdk_ffi_checksum_constructor_mqttengineffi_new_with_opts( - ): Short - fun uniffi_flowsdk_ffi_checksum_constructor_quicmqttengineffi_new( - ): Short - fun uniffi_flowsdk_ffi_checksum_constructor_tlsmqttengineffi_new( - ): Short - fun ffi_flowsdk_ffi_uniffi_contract_version( - ): Int - -} - -private fun uniffiCheckContractApiVersion(lib: UniffiLib) { - // Get the bindings contract version from our ComponentInterface - val bindings_contract_version = 26 - // Get the scaffolding contract version by calling the into the dylib - val scaffolding_contract_version = lib.ffi_flowsdk_ffi_uniffi_contract_version() - if (bindings_contract_version != scaffolding_contract_version) { - throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") - } -} - -@Suppress("UNUSED_PARAMETER") -private fun uniffiCheckApiChecksums(lib: UniffiLib) { - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_auth() != 13274.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_connect() != 11843.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_disconnect() != 35275.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_get_version() != 19826.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_handle_connection_lost() != 12358.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_handle_incoming() != 55488.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_handle_tick() != 40864.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_is_connected() != 33756.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_next_tick_ms() != 63992.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_publish() != 44572.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_push_event_ffi() != 16806.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_subscribe() != 31682.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_take_events() != 17921.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_take_outgoing() != 12337.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqttengineffi_unsubscribe() != 51097.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqtteventlistffi_get() != 5088.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqtteventlistffi_is_empty() != 43194.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_mqtteventlistffi_len() != 36035.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_connect() != 39570.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_disconnect() != 54076.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_handle_datagram() != 34322.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_handle_tick() != 39076.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_is_connected() != 53863.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_publish() != 17969.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_subscribe() != 16787.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_take_events() != 34914.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_take_outgoing_datagrams() != 32224.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_quicmqttengineffi_unsubscribe() != 48.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_connect() != 53946.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_disconnect() != 58642.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_handle_socket_data() != 27993.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_handle_tick() != 57570.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_is_connected() != 11670.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_publish() != 29858.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_subscribe() != 35959.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_take_events() != 41728.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_take_socket_data() != 6663.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_method_tlsmqttengineffi_unsubscribe() != 21790.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_constructor_mqttengineffi_new() != 63280.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_constructor_mqttengineffi_new_with_opts() != 49926.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_constructor_quicmqttengineffi_new() != 4296.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_flowsdk_ffi_checksum_constructor_tlsmqttengineffi_new() != 55234.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } -} - -// Async support - -// Public interface members begin here. - - -// Interface implemented by anything that can contain an object reference. -// -// Such types expose a `destroy()` method that must be called to cleanly -// dispose of the contained objects. Failure to call this method may result -// in memory leaks. -// -// The easiest way to ensure this method is called is to use the `.use` -// helper method to execute a block and destroy the object at the end. -interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance() - .forEach(Disposable::destroy) - } - } -} - -/** - * @suppress - */ -inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - -/** - * Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. - * - * @suppress - * */ -object NoPointer - -/** - * @suppress - */ -public object FfiConverterUByte: FfiConverter { - override fun lift(value: Byte): UByte { - return value.toUByte() - } - - override fun read(buf: ByteBuffer): UByte { - return lift(buf.get()) - } - - override fun lower(value: UByte): Byte { - return value.toByte() - } - - override fun allocationSize(value: UByte) = 1UL - - override fun write(value: UByte, buf: ByteBuffer) { - buf.put(value.toByte()) - } -} - -/** - * @suppress - */ -public object FfiConverterUShort: FfiConverter { - override fun lift(value: Short): UShort { - return value.toUShort() - } - - override fun read(buf: ByteBuffer): UShort { - return lift(buf.getShort()) - } - - override fun lower(value: UShort): Short { - return value.toShort() - } - - override fun allocationSize(value: UShort) = 2UL - - override fun write(value: UShort, buf: ByteBuffer) { - buf.putShort(value.toShort()) - } -} - -/** - * @suppress - */ -public object FfiConverterUInt: FfiConverter { - override fun lift(value: Int): UInt { - return value.toUInt() - } - - override fun read(buf: ByteBuffer): UInt { - return lift(buf.getInt()) - } - - override fun lower(value: UInt): Int { - return value.toInt() - } - - override fun allocationSize(value: UInt) = 4UL - - override fun write(value: UInt, buf: ByteBuffer) { - buf.putInt(value.toInt()) - } -} - -/** - * @suppress - */ -public object FfiConverterInt: FfiConverter { - override fun lift(value: Int): Int { - return value - } - - override fun read(buf: ByteBuffer): Int { - return buf.getInt() - } - - override fun lower(value: Int): Int { - return value - } - - override fun allocationSize(value: Int) = 4UL - - override fun write(value: Int, buf: ByteBuffer) { - buf.putInt(value) - } -} - -/** - * @suppress - */ -public object FfiConverterULong: FfiConverter { - override fun lift(value: Long): ULong { - return value.toULong() - } - - override fun read(buf: ByteBuffer): ULong { - return lift(buf.getLong()) - } - - override fun lower(value: ULong): Long { - return value.toLong() - } - - override fun allocationSize(value: ULong) = 8UL - - override fun write(value: ULong, buf: ByteBuffer) { - buf.putLong(value.toLong()) - } -} - -/** - * @suppress - */ -public object FfiConverterLong: FfiConverter { - override fun lift(value: Long): Long { - return value - } - - override fun read(buf: ByteBuffer): Long { - return buf.getLong() - } - - override fun lower(value: Long): Long { - return value - } - - override fun allocationSize(value: Long) = 8UL - - override fun write(value: Long, buf: ByteBuffer) { - buf.putLong(value) - } -} - -/** - * @suppress - */ -public object FfiConverterBoolean: FfiConverter { - override fun lift(value: Byte): Boolean { - return value.toInt() != 0 - } - - override fun read(buf: ByteBuffer): Boolean { - return lift(buf.get()) - } - - override fun lower(value: Boolean): Byte { - return if (value) 1.toByte() else 0.toByte() - } - - override fun allocationSize(value: Boolean) = 1UL - - override fun write(value: Boolean, buf: ByteBuffer) { - buf.put(lower(value)) - } -} - -/** - * @suppress - */ -public object FfiConverterString: FfiConverter { - // Note: we don't inherit from FfiConverterRustBuffer, because we use a - // special encoding when lowering/lifting. We can use `RustBuffer.len` to - // store our length and avoid writing it out to the buffer. - override fun lift(value: RustBuffer.ByValue): String { - try { - val byteArr = ByteArray(value.len.toInt()) - value.asByteBuffer()!!.get(byteArr) - return byteArr.toString(Charsets.UTF_8) - } finally { - RustBuffer.free(value) - } - } - - override fun read(buf: ByteBuffer): String { - val len = buf.getInt() - val byteArr = ByteArray(len) - buf.get(byteArr) - return byteArr.toString(Charsets.UTF_8) - } - - fun toUtf8(value: String): ByteBuffer { - // Make sure we don't have invalid UTF-16, check for lone surrogates. - return Charsets.UTF_8.newEncoder().run { - onMalformedInput(CodingErrorAction.REPORT) - encode(CharBuffer.wrap(value)) - } - } - - override fun lower(value: String): RustBuffer.ByValue { - val byteBuf = toUtf8(value) - // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us - // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. - val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) - rbuf.asByteBuffer()!!.put(byteBuf) - return rbuf - } - - // We aren't sure exactly how many bytes our string will be once it's UTF-8 - // encoded. Allocate 3 bytes per UTF-16 code unit which will always be - // enough. - override fun allocationSize(value: String): ULong { - val sizeForLength = 4UL - val sizeForString = value.length.toULong() * 3UL - return sizeForLength + sizeForString - } - - override fun write(value: String, buf: ByteBuffer) { - val byteBuf = toUtf8(value) - buf.putInt(byteBuf.limit()) - buf.put(byteBuf) - } -} - -/** - * @suppress - */ -public object FfiConverterByteArray: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): ByteArray { - val len = buf.getInt() - val byteArr = ByteArray(len) - buf.get(byteArr) - return byteArr - } - override fun allocationSize(value: ByteArray): ULong { - return 4UL + value.size.toULong() - } - override fun write(value: ByteArray, buf: ByteBuffer) { - buf.putInt(value.size) - buf.put(value) - } -} - - -// This template implements a class for working with a Rust struct via a Pointer/Arc -// to the live Rust struct on the other side of the FFI. -// -// Each instance implements core operations for working with the Rust `Arc` and the -// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an instance is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so risks -// leaking the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` -// is implemented to call the destructor when the Kotlin object becomes unreachable. -// This is done in a background thread. This is not a panacea, and client code should be aware that -// 1. the thread may starve if some there are objects that have poorly performing -// `drop` methods or do significant work in their `drop` methods. -// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, -// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// This makes a cleaner a better alternative to _not_ calling `destroy()` as -// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` -// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner -// thread may be starved, and the app will leak memory. -// -// In this case, `destroy`ing manually may be a better solution. -// -// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects -// with Rust peers are reclaimed: -// -// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: -// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: -// 3. The memory is reclaimed when the process terminates. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// - - -/** - * The cleaner interface for Object finalization code to run. - * This is the entry point to any implementation that we're using. - * - * The cleaner registers objects and returns cleanables, so now we are - * defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the - * different implmentations available at compile time. - * - * @suppress - */ -interface UniffiCleaner { - interface Cleanable { - fun clean() - } - - fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable - - companion object -} - -// The fallback Jna cleaner, which is available for both Android, and the JVM. -private class UniffiJnaCleaner : UniffiCleaner { - private val cleaner = com.sun.jna.internal.Cleaner.getCleaner() - - override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = - UniffiJnaCleanable(cleaner.register(value, cleanUpTask)) -} - -private class UniffiJnaCleanable( - private val cleanable: com.sun.jna.internal.Cleaner.Cleanable, -) : UniffiCleaner.Cleanable { - override fun clean() = cleanable.clean() -} - -// We decide at uniffi binding generation time whether we were -// using Android or not. -// There are further runtime checks to chose the correct implementation -// of the cleaner. -private fun UniffiCleaner.Companion.create(): UniffiCleaner = - try { - // For safety's sake: if the library hasn't been run in android_cleaner = true - // mode, but is being run on Android, then we still need to think about - // Android API versions. - // So we check if java.lang.ref.Cleaner is there, and use that… - java.lang.Class.forName("java.lang.ref.Cleaner") - JavaLangRefCleaner() - } catch (e: ClassNotFoundException) { - // … otherwise, fallback to the JNA cleaner. - UniffiJnaCleaner() - } - -private class JavaLangRefCleaner : UniffiCleaner { - val cleaner = java.lang.ref.Cleaner.create() - - override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = - JavaLangRefCleanable(cleaner.register(value, cleanUpTask)) -} - -private class JavaLangRefCleanable( - val cleanable: java.lang.ref.Cleaner.Cleanable -) : UniffiCleaner.Cleanable { - override fun clean() = cleanable.clean() -} -public interface MqttEngineFfiInterface { - - fun `auth`(`reasonCode`: kotlin.UByte) - - fun `connect`() - - fun `disconnect`() - - fun `getVersion`(): kotlin.UByte - - fun `handleConnectionLost`() - - fun `handleIncoming`(`data`: kotlin.ByteArray): List - - fun `handleTick`(`nowMs`: kotlin.ULong): List - - fun `isConnected`(): kotlin.Boolean - - fun `nextTickMs`(): kotlin.Long - - fun `publish`(`topic`: kotlin.String, `payload`: kotlin.ByteArray, `qos`: kotlin.UByte, `priority`: kotlin.UByte?): kotlin.Int - - fun `pushEventFfi`(`event`: MqttEventFfi) - - fun `subscribe`(`topicFilter`: kotlin.String, `qos`: kotlin.UByte): kotlin.Int - - fun `takeEvents`(): List - - fun `takeOutgoing`(): kotlin.ByteArray - - fun `unsubscribe`(`topicFilter`: kotlin.String): kotlin.Int - - companion object -} - -open class MqttEngineFfi: Disposable, AutoCloseable, MqttEngineFfiInterface { - - constructor(pointer: Pointer) { - this.pointer = pointer - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - /** - * This constructor can be used to instantiate a fake object. Only used for tests. Any - * attempt to actually use an object constructed this way will fail as there is no - * connected Rust object. - */ - @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - constructor(`clientId`: kotlin.String?, `mqttVersion`: kotlin.UByte) : - this( - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_constructor_mqttengineffi_new( - FfiConverterOptionalString.lower(`clientId`),FfiConverterUByte.lower(`mqttVersion`),_status) -} - ) - - protected val pointer: Pointer? - protected val cleanable: UniffiCleaner.Cleanable - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.uniffiClonePointer()) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - // Use a static inner class instead of a closure so as not to accidentally - // capture `this` as part of the cleanable's action. - private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { - override fun run() { - pointer?.let { ptr -> - uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_free_mqttengineffi(ptr, status) - } - } - } - } - - fun uniffiClonePointer(): Pointer { - return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_clone_mqttengineffi(pointer!!, status) - } - } - - override fun `auth`(`reasonCode`: kotlin.UByte) - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_auth( - it, FfiConverterUByte.lower(`reasonCode`),_status) -} - } - - - - override fun `connect`() - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_connect( - it, _status) -} - } - - - - override fun `disconnect`() - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_disconnect( - it, _status) -} - } - - - - override fun `getVersion`(): kotlin.UByte { - return FfiConverterUByte.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_get_version( - it, _status) -} - } - ) - } - - - override fun `handleConnectionLost`() - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_handle_connection_lost( - it, _status) -} - } - - - - override fun `handleIncoming`(`data`: kotlin.ByteArray): List { - return FfiConverterSequenceTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_handle_incoming( - it, FfiConverterByteArray.lower(`data`),_status) -} - } - ) - } - - - override fun `handleTick`(`nowMs`: kotlin.ULong): List { - return FfiConverterSequenceTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_handle_tick( - it, FfiConverterULong.lower(`nowMs`),_status) -} - } - ) - } - - - override fun `isConnected`(): kotlin.Boolean { - return FfiConverterBoolean.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_is_connected( - it, _status) -} - } - ) - } - - - override fun `nextTickMs`(): kotlin.Long { - return FfiConverterLong.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_next_tick_ms( - it, _status) -} - } - ) - } - - - override fun `publish`(`topic`: kotlin.String, `payload`: kotlin.ByteArray, `qos`: kotlin.UByte, `priority`: kotlin.UByte?): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_publish( - it, FfiConverterString.lower(`topic`),FfiConverterByteArray.lower(`payload`),FfiConverterUByte.lower(`qos`),FfiConverterOptionalUByte.lower(`priority`),_status) -} - } - ) - } - - - override fun `pushEventFfi`(`event`: MqttEventFfi) - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_push_event_ffi( - it, FfiConverterTypeMqttEventFFI.lower(`event`),_status) -} - } - - - - override fun `subscribe`(`topicFilter`: kotlin.String, `qos`: kotlin.UByte): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_subscribe( - it, FfiConverterString.lower(`topicFilter`),FfiConverterUByte.lower(`qos`),_status) -} - } - ) - } - - - override fun `takeEvents`(): List { - return FfiConverterSequenceTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_take_events( - it, _status) -} - } - ) - } - - - override fun `takeOutgoing`(): kotlin.ByteArray { - return FfiConverterByteArray.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_take_outgoing( - it, _status) -} - } - ) - } - - - override fun `unsubscribe`(`topicFilter`: kotlin.String): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqttengineffi_unsubscribe( - it, FfiConverterString.lower(`topicFilter`),_status) -} - } - ) - } - - - - - - companion object { - fun `newWithOpts`(`opts`: MqttOptionsFfi): MqttEngineFfi { - return FfiConverterTypeMqttEngineFFI.lift( - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_constructor_mqttengineffi_new_with_opts( - FfiConverterTypeMqttOptionsFFI.lower(`opts`),_status) -} - ) - } - - - - } - -} - -/** - * @suppress - */ -public object FfiConverterTypeMqttEngineFFI: FfiConverter { - - override fun lower(value: MqttEngineFfi): Pointer { - return value.uniffiClonePointer() - } - - override fun lift(value: Pointer): MqttEngineFfi { - return MqttEngineFfi(value) - } - - override fun read(buf: ByteBuffer): MqttEngineFfi { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) - } - - override fun allocationSize(value: MqttEngineFfi) = 8UL - - override fun write(value: MqttEngineFfi, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) - } -} - - -// This template implements a class for working with a Rust struct via a Pointer/Arc -// to the live Rust struct on the other side of the FFI. -// -// Each instance implements core operations for working with the Rust `Arc` and the -// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an instance is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so risks -// leaking the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` -// is implemented to call the destructor when the Kotlin object becomes unreachable. -// This is done in a background thread. This is not a panacea, and client code should be aware that -// 1. the thread may starve if some there are objects that have poorly performing -// `drop` methods or do significant work in their `drop` methods. -// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, -// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// This makes a cleaner a better alternative to _not_ calling `destroy()` as -// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` -// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner -// thread may be starved, and the app will leak memory. -// -// In this case, `destroy`ing manually may be a better solution. -// -// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects -// with Rust peers are reclaimed: -// -// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: -// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: -// 3. The memory is reclaimed when the process terminates. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// - - -public interface MqttEventListFfiInterface { - - fun `get`(`index`: kotlin.UInt): MqttEventFfi? - - fun `isEmpty`(): kotlin.Boolean - - fun `len`(): kotlin.UInt - - companion object -} - -open class MqttEventListFfi: Disposable, AutoCloseable, MqttEventListFfiInterface { - - constructor(pointer: Pointer) { - this.pointer = pointer - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - /** - * This constructor can be used to instantiate a fake object. Only used for tests. Any - * attempt to actually use an object constructed this way will fail as there is no - * connected Rust object. - */ - @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - protected val pointer: Pointer? - protected val cleanable: UniffiCleaner.Cleanable - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.uniffiClonePointer()) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - // Use a static inner class instead of a closure so as not to accidentally - // capture `this` as part of the cleanable's action. - private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { - override fun run() { - pointer?.let { ptr -> - uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_free_mqtteventlistffi(ptr, status) - } - } - } - } - - fun uniffiClonePointer(): Pointer { - return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_clone_mqtteventlistffi(pointer!!, status) - } - } - - override fun `get`(`index`: kotlin.UInt): MqttEventFfi? { - return FfiConverterOptionalTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqtteventlistffi_get( - it, FfiConverterUInt.lower(`index`),_status) -} - } - ) - } - - - override fun `isEmpty`(): kotlin.Boolean { - return FfiConverterBoolean.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqtteventlistffi_is_empty( - it, _status) -} - } - ) - } - - - override fun `len`(): kotlin.UInt { - return FfiConverterUInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_mqtteventlistffi_len( - it, _status) -} - } - ) - } - - - - - - - companion object - -} - -/** - * @suppress - */ -public object FfiConverterTypeMqttEventListFFI: FfiConverter { - - override fun lower(value: MqttEventListFfi): Pointer { - return value.uniffiClonePointer() - } - - override fun lift(value: Pointer): MqttEventListFfi { - return MqttEventListFfi(value) - } - - override fun read(buf: ByteBuffer): MqttEventListFfi { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) - } - - override fun allocationSize(value: MqttEventListFfi) = 8UL - - override fun write(value: MqttEventListFfi, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) - } -} - - -// This template implements a class for working with a Rust struct via a Pointer/Arc -// to the live Rust struct on the other side of the FFI. -// -// Each instance implements core operations for working with the Rust `Arc` and the -// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an instance is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so risks -// leaking the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` -// is implemented to call the destructor when the Kotlin object becomes unreachable. -// This is done in a background thread. This is not a panacea, and client code should be aware that -// 1. the thread may starve if some there are objects that have poorly performing -// `drop` methods or do significant work in their `drop` methods. -// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, -// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// This makes a cleaner a better alternative to _not_ calling `destroy()` as -// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` -// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner -// thread may be starved, and the app will leak memory. -// -// In this case, `destroy`ing manually may be a better solution. -// -// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects -// with Rust peers are reclaimed: -// -// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: -// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: -// 3. The memory is reclaimed when the process terminates. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// - - -public interface QuicMqttEngineFfiInterface { - - fun `connect`(`serverAddr`: kotlin.String, `serverName`: kotlin.String, `tlsOpts`: MqttTlsOptionsFfi, `nowMs`: kotlin.ULong) - - fun `disconnect`() - - fun `handleDatagram`(`data`: kotlin.ByteArray, `remoteAddr`: kotlin.String, `nowMs`: kotlin.ULong) - - fun `handleTick`(`nowMs`: kotlin.ULong): List - - fun `isConnected`(): kotlin.Boolean - - fun `publish`(`topic`: kotlin.String, `payload`: kotlin.ByteArray, `qos`: kotlin.UByte): kotlin.Int - - fun `subscribe`(`topicFilter`: kotlin.String, `qos`: kotlin.UByte): kotlin.Int - - fun `takeEvents`(): List - - fun `takeOutgoingDatagrams`(): List - - fun `unsubscribe`(`topicFilter`: kotlin.String): kotlin.Int - - companion object -} - -open class QuicMqttEngineFfi: Disposable, AutoCloseable, QuicMqttEngineFfiInterface { - - constructor(pointer: Pointer) { - this.pointer = pointer - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - /** - * This constructor can be used to instantiate a fake object. Only used for tests. Any - * attempt to actually use an object constructed this way will fail as there is no - * connected Rust object. - */ - @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - constructor(`opts`: MqttOptionsFfi) : - this( - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_constructor_quicmqttengineffi_new( - FfiConverterTypeMqttOptionsFFI.lower(`opts`),_status) -} - ) - - protected val pointer: Pointer? - protected val cleanable: UniffiCleaner.Cleanable - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.uniffiClonePointer()) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - // Use a static inner class instead of a closure so as not to accidentally - // capture `this` as part of the cleanable's action. - private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { - override fun run() { - pointer?.let { ptr -> - uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_free_quicmqttengineffi(ptr, status) - } - } - } - } - - fun uniffiClonePointer(): Pointer { - return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_clone_quicmqttengineffi(pointer!!, status) - } - } - - override fun `connect`(`serverAddr`: kotlin.String, `serverName`: kotlin.String, `tlsOpts`: MqttTlsOptionsFfi, `nowMs`: kotlin.ULong) - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_connect( - it, FfiConverterString.lower(`serverAddr`),FfiConverterString.lower(`serverName`),FfiConverterTypeMqttTlsOptionsFFI.lower(`tlsOpts`),FfiConverterULong.lower(`nowMs`),_status) -} - } - - - - override fun `disconnect`() - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_disconnect( - it, _status) -} - } - - - - override fun `handleDatagram`(`data`: kotlin.ByteArray, `remoteAddr`: kotlin.String, `nowMs`: kotlin.ULong) - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_handle_datagram( - it, FfiConverterByteArray.lower(`data`),FfiConverterString.lower(`remoteAddr`),FfiConverterULong.lower(`nowMs`),_status) -} - } - - - - override fun `handleTick`(`nowMs`: kotlin.ULong): List { - return FfiConverterSequenceTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_handle_tick( - it, FfiConverterULong.lower(`nowMs`),_status) -} - } - ) - } - - - override fun `isConnected`(): kotlin.Boolean { - return FfiConverterBoolean.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_is_connected( - it, _status) -} - } - ) - } - - - override fun `publish`(`topic`: kotlin.String, `payload`: kotlin.ByteArray, `qos`: kotlin.UByte): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_publish( - it, FfiConverterString.lower(`topic`),FfiConverterByteArray.lower(`payload`),FfiConverterUByte.lower(`qos`),_status) -} - } - ) - } - - - override fun `subscribe`(`topicFilter`: kotlin.String, `qos`: kotlin.UByte): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_subscribe( - it, FfiConverterString.lower(`topicFilter`),FfiConverterUByte.lower(`qos`),_status) -} - } - ) - } - - - override fun `takeEvents`(): List { - return FfiConverterSequenceTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_take_events( - it, _status) -} - } - ) - } - - - override fun `takeOutgoingDatagrams`(): List { - return FfiConverterSequenceTypeMqttDatagramFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_take_outgoing_datagrams( - it, _status) -} - } - ) - } - - - override fun `unsubscribe`(`topicFilter`: kotlin.String): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_quicmqttengineffi_unsubscribe( - it, FfiConverterString.lower(`topicFilter`),_status) -} - } - ) - } - - - - - - - companion object - -} - -/** - * @suppress - */ -public object FfiConverterTypeQuicMqttEngineFFI: FfiConverter { - - override fun lower(value: QuicMqttEngineFfi): Pointer { - return value.uniffiClonePointer() - } - - override fun lift(value: Pointer): QuicMqttEngineFfi { - return QuicMqttEngineFfi(value) - } - - override fun read(buf: ByteBuffer): QuicMqttEngineFfi { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) - } - - override fun allocationSize(value: QuicMqttEngineFfi) = 8UL - - override fun write(value: QuicMqttEngineFfi, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) - } -} - - -// This template implements a class for working with a Rust struct via a Pointer/Arc -// to the live Rust struct on the other side of the FFI. -// -// Each instance implements core operations for working with the Rust `Arc` and the -// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an instance is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so risks -// leaking the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` -// is implemented to call the destructor when the Kotlin object becomes unreachable. -// This is done in a background thread. This is not a panacea, and client code should be aware that -// 1. the thread may starve if some there are objects that have poorly performing -// `drop` methods or do significant work in their `drop` methods. -// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, -// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// This makes a cleaner a better alternative to _not_ calling `destroy()` as -// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` -// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner -// thread may be starved, and the app will leak memory. -// -// In this case, `destroy`ing manually may be a better solution. -// -// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects -// with Rust peers are reclaimed: -// -// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: -// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: -// 3. The memory is reclaimed when the process terminates. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// - - -public interface TlsMqttEngineFfiInterface { - - fun `connect`() - - fun `disconnect`() - - fun `handleSocketData`(`data`: kotlin.ByteArray) - - fun `handleTick`(`nowMs`: kotlin.ULong): List - - fun `isConnected`(): kotlin.Boolean - - fun `publish`(`topic`: kotlin.String, `payload`: kotlin.ByteArray, `qos`: kotlin.UByte): kotlin.Int - - fun `subscribe`(`topicFilter`: kotlin.String, `qos`: kotlin.UByte): kotlin.Int - - fun `takeEvents`(): List - - fun `takeSocketData`(): kotlin.ByteArray - - fun `unsubscribe`(`topicFilter`: kotlin.String): kotlin.Int - - companion object -} - -open class TlsMqttEngineFfi: Disposable, AutoCloseable, TlsMqttEngineFfiInterface { - - constructor(pointer: Pointer) { - this.pointer = pointer - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - - /** - * This constructor can be used to instantiate a fake object. Only used for tests. Any - * attempt to actually use an object constructed this way will fail as there is no - * connected Rust object. - */ - @Suppress("UNUSED_PARAMETER") - constructor(noPointer: NoPointer) { - this.pointer = null - this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) - } - constructor(`opts`: MqttOptionsFfi, `tlsOpts`: MqttTlsOptionsFfi, `serverName`: kotlin.String) : - this( - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_constructor_tlsmqttengineffi_new( - FfiConverterTypeMqttOptionsFFI.lower(`opts`),FfiConverterTypeMqttTlsOptionsFFI.lower(`tlsOpts`),FfiConverterString.lower(`serverName`),_status) -} - ) - - protected val pointer: Pointer? - protected val cleanable: UniffiCleaner.Cleanable - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.uniffiClonePointer()) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - cleanable.clean() - } - } - } - - // Use a static inner class instead of a closure so as not to accidentally - // capture `this` as part of the cleanable's action. - private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { - override fun run() { - pointer?.let { ptr -> - uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_free_tlsmqttengineffi(ptr, status) - } - } - } - } - - fun uniffiClonePointer(): Pointer { - return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_clone_tlsmqttengineffi(pointer!!, status) - } - } - - override fun `connect`() - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_connect( - it, _status) -} - } - - - - override fun `disconnect`() - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_disconnect( - it, _status) -} - } - - - - override fun `handleSocketData`(`data`: kotlin.ByteArray) - = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_handle_socket_data( - it, FfiConverterByteArray.lower(`data`),_status) -} - } - - - - override fun `handleTick`(`nowMs`: kotlin.ULong): List { - return FfiConverterSequenceTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_handle_tick( - it, FfiConverterULong.lower(`nowMs`),_status) -} - } - ) - } - - - override fun `isConnected`(): kotlin.Boolean { - return FfiConverterBoolean.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_is_connected( - it, _status) -} - } - ) - } - - - override fun `publish`(`topic`: kotlin.String, `payload`: kotlin.ByteArray, `qos`: kotlin.UByte): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_publish( - it, FfiConverterString.lower(`topic`),FfiConverterByteArray.lower(`payload`),FfiConverterUByte.lower(`qos`),_status) -} - } - ) - } - - - override fun `subscribe`(`topicFilter`: kotlin.String, `qos`: kotlin.UByte): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_subscribe( - it, FfiConverterString.lower(`topicFilter`),FfiConverterUByte.lower(`qos`),_status) -} - } - ) - } - - - override fun `takeEvents`(): List { - return FfiConverterSequenceTypeMqttEventFFI.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_take_events( - it, _status) -} - } - ) - } - - - override fun `takeSocketData`(): kotlin.ByteArray { - return FfiConverterByteArray.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_take_socket_data( - it, _status) -} - } - ) - } - - - override fun `unsubscribe`(`topicFilter`: kotlin.String): kotlin.Int { - return FfiConverterInt.lift( - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_flowsdk_ffi_fn_method_tlsmqttengineffi_unsubscribe( - it, FfiConverterString.lower(`topicFilter`),_status) -} - } - ) - } - - - - - - - companion object - -} - -/** - * @suppress - */ -public object FfiConverterTypeTlsMqttEngineFFI: FfiConverter { - - override fun lower(value: TlsMqttEngineFfi): Pointer { - return value.uniffiClonePointer() - } - - override fun lift(value: Pointer): TlsMqttEngineFfi { - return TlsMqttEngineFfi(value) - } - - override fun read(buf: ByteBuffer): TlsMqttEngineFfi { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) - } - - override fun allocationSize(value: TlsMqttEngineFfi) = 8UL - - override fun write(value: TlsMqttEngineFfi, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) - } -} - - - -data class ConnectionResultFfi ( - var `reasonCode`: kotlin.UByte, - var `sessionPresent`: kotlin.Boolean -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeConnectionResultFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): ConnectionResultFfi { - return ConnectionResultFfi( - FfiConverterUByte.read(buf), - FfiConverterBoolean.read(buf), - ) - } - - override fun allocationSize(value: ConnectionResultFfi) = ( - FfiConverterUByte.allocationSize(value.`reasonCode`) + - FfiConverterBoolean.allocationSize(value.`sessionPresent`) - ) - - override fun write(value: ConnectionResultFfi, buf: ByteBuffer) { - FfiConverterUByte.write(value.`reasonCode`, buf) - FfiConverterBoolean.write(value.`sessionPresent`, buf) - } -} - - - -data class MqttDatagramFfi ( - var `addr`: kotlin.String, - var `data`: kotlin.ByteArray -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeMqttDatagramFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): MqttDatagramFfi { - return MqttDatagramFfi( - FfiConverterString.read(buf), - FfiConverterByteArray.read(buf), - ) - } - - override fun allocationSize(value: MqttDatagramFfi) = ( - FfiConverterString.allocationSize(value.`addr`) + - FfiConverterByteArray.allocationSize(value.`data`) - ) - - override fun write(value: MqttDatagramFfi, buf: ByteBuffer) { - FfiConverterString.write(value.`addr`, buf) - FfiConverterByteArray.write(value.`data`, buf) - } -} - - - -data class MqttMessageFfi ( - var `topic`: kotlin.String, - var `payload`: kotlin.ByteArray, - var `qos`: kotlin.UByte, - var `retain`: kotlin.Boolean -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeMqttMessageFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): MqttMessageFfi { - return MqttMessageFfi( - FfiConverterString.read(buf), - FfiConverterByteArray.read(buf), - FfiConverterUByte.read(buf), - FfiConverterBoolean.read(buf), - ) - } - - override fun allocationSize(value: MqttMessageFfi) = ( - FfiConverterString.allocationSize(value.`topic`) + - FfiConverterByteArray.allocationSize(value.`payload`) + - FfiConverterUByte.allocationSize(value.`qos`) + - FfiConverterBoolean.allocationSize(value.`retain`) - ) - - override fun write(value: MqttMessageFfi, buf: ByteBuffer) { - FfiConverterString.write(value.`topic`, buf) - FfiConverterByteArray.write(value.`payload`, buf) - FfiConverterUByte.write(value.`qos`, buf) - FfiConverterBoolean.write(value.`retain`, buf) - } -} - - - -data class MqttOptionsFfi ( - var `clientId`: kotlin.String, - var `mqttVersion`: kotlin.UByte, - var `cleanStart`: kotlin.Boolean, - var `keepAlive`: kotlin.UShort, - var `username`: kotlin.String?, - var `password`: kotlin.String?, - var `reconnectBaseDelayMs`: kotlin.ULong, - var `reconnectMaxDelayMs`: kotlin.ULong, - var `maxReconnectAttempts`: kotlin.UInt -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeMqttOptionsFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): MqttOptionsFfi { - return MqttOptionsFfi( - FfiConverterString.read(buf), - FfiConverterUByte.read(buf), - FfiConverterBoolean.read(buf), - FfiConverterUShort.read(buf), - FfiConverterOptionalString.read(buf), - FfiConverterOptionalString.read(buf), - FfiConverterULong.read(buf), - FfiConverterULong.read(buf), - FfiConverterUInt.read(buf), - ) - } - - override fun allocationSize(value: MqttOptionsFfi) = ( - FfiConverterString.allocationSize(value.`clientId`) + - FfiConverterUByte.allocationSize(value.`mqttVersion`) + - FfiConverterBoolean.allocationSize(value.`cleanStart`) + - FfiConverterUShort.allocationSize(value.`keepAlive`) + - FfiConverterOptionalString.allocationSize(value.`username`) + - FfiConverterOptionalString.allocationSize(value.`password`) + - FfiConverterULong.allocationSize(value.`reconnectBaseDelayMs`) + - FfiConverterULong.allocationSize(value.`reconnectMaxDelayMs`) + - FfiConverterUInt.allocationSize(value.`maxReconnectAttempts`) - ) - - override fun write(value: MqttOptionsFfi, buf: ByteBuffer) { - FfiConverterString.write(value.`clientId`, buf) - FfiConverterUByte.write(value.`mqttVersion`, buf) - FfiConverterBoolean.write(value.`cleanStart`, buf) - FfiConverterUShort.write(value.`keepAlive`, buf) - FfiConverterOptionalString.write(value.`username`, buf) - FfiConverterOptionalString.write(value.`password`, buf) - FfiConverterULong.write(value.`reconnectBaseDelayMs`, buf) - FfiConverterULong.write(value.`reconnectMaxDelayMs`, buf) - FfiConverterUInt.write(value.`maxReconnectAttempts`, buf) - } -} - - - -data class MqttTlsOptionsFfi ( - var `caCertFile`: kotlin.String?, - var `clientCertFile`: kotlin.String?, - var `clientKeyFile`: kotlin.String?, - var `insecureSkipVerify`: kotlin.Boolean, - var `alpnProtocols`: List, - var `enableKeyLog`: kotlin.Boolean -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeMqttTlsOptionsFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): MqttTlsOptionsFfi { - return MqttTlsOptionsFfi( - FfiConverterOptionalString.read(buf), - FfiConverterOptionalString.read(buf), - FfiConverterOptionalString.read(buf), - FfiConverterBoolean.read(buf), - FfiConverterSequenceString.read(buf), - FfiConverterBoolean.read(buf), - ) - } - - override fun allocationSize(value: MqttTlsOptionsFfi) = ( - FfiConverterOptionalString.allocationSize(value.`caCertFile`) + - FfiConverterOptionalString.allocationSize(value.`clientCertFile`) + - FfiConverterOptionalString.allocationSize(value.`clientKeyFile`) + - FfiConverterBoolean.allocationSize(value.`insecureSkipVerify`) + - FfiConverterSequenceString.allocationSize(value.`alpnProtocols`) + - FfiConverterBoolean.allocationSize(value.`enableKeyLog`) - ) - - override fun write(value: MqttTlsOptionsFfi, buf: ByteBuffer) { - FfiConverterOptionalString.write(value.`caCertFile`, buf) - FfiConverterOptionalString.write(value.`clientCertFile`, buf) - FfiConverterOptionalString.write(value.`clientKeyFile`, buf) - FfiConverterBoolean.write(value.`insecureSkipVerify`, buf) - FfiConverterSequenceString.write(value.`alpnProtocols`, buf) - FfiConverterBoolean.write(value.`enableKeyLog`, buf) - } -} - - - -data class PublishResultFfi ( - var `packetId`: kotlin.UShort?, - var `reasonCode`: kotlin.UByte?, - var `qos`: kotlin.UByte -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypePublishResultFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): PublishResultFfi { - return PublishResultFfi( - FfiConverterOptionalUShort.read(buf), - FfiConverterOptionalUByte.read(buf), - FfiConverterUByte.read(buf), - ) - } - - override fun allocationSize(value: PublishResultFfi) = ( - FfiConverterOptionalUShort.allocationSize(value.`packetId`) + - FfiConverterOptionalUByte.allocationSize(value.`reasonCode`) + - FfiConverterUByte.allocationSize(value.`qos`) - ) - - override fun write(value: PublishResultFfi, buf: ByteBuffer) { - FfiConverterOptionalUShort.write(value.`packetId`, buf) - FfiConverterOptionalUByte.write(value.`reasonCode`, buf) - FfiConverterUByte.write(value.`qos`, buf) - } -} - - - -data class SubscribeResultFfi ( - var `packetId`: kotlin.UShort, - var `reasonCodes`: kotlin.ByteArray -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeSubscribeResultFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): SubscribeResultFfi { - return SubscribeResultFfi( - FfiConverterUShort.read(buf), - FfiConverterByteArray.read(buf), - ) - } - - override fun allocationSize(value: SubscribeResultFfi) = ( - FfiConverterUShort.allocationSize(value.`packetId`) + - FfiConverterByteArray.allocationSize(value.`reasonCodes`) - ) - - override fun write(value: SubscribeResultFfi, buf: ByteBuffer) { - FfiConverterUShort.write(value.`packetId`, buf) - FfiConverterByteArray.write(value.`reasonCodes`, buf) - } -} - - - -data class UnsubscribeResultFfi ( - var `packetId`: kotlin.UShort, - var `reasonCodes`: kotlin.ByteArray -) { - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeUnsubscribeResultFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): UnsubscribeResultFfi { - return UnsubscribeResultFfi( - FfiConverterUShort.read(buf), - FfiConverterByteArray.read(buf), - ) - } - - override fun allocationSize(value: UnsubscribeResultFfi) = ( - FfiConverterUShort.allocationSize(value.`packetId`) + - FfiConverterByteArray.allocationSize(value.`reasonCodes`) - ) - - override fun write(value: UnsubscribeResultFfi, buf: ByteBuffer) { - FfiConverterUShort.write(value.`packetId`, buf) - FfiConverterByteArray.write(value.`reasonCodes`, buf) - } -} - - - -sealed class MqttEventFfi { - - data class Connected( - val v1: ConnectionResultFfi) : MqttEventFfi() { - companion object - } - - data class Disconnected( - val `reasonCode`: kotlin.UByte?) : MqttEventFfi() { - companion object - } - - data class MessageReceived( - val v1: MqttMessageFfi) : MqttEventFfi() { - companion object - } - - data class Published( - val v1: PublishResultFfi) : MqttEventFfi() { - companion object - } - - data class Subscribed( - val v1: SubscribeResultFfi) : MqttEventFfi() { - companion object - } - - data class Unsubscribed( - val v1: UnsubscribeResultFfi) : MqttEventFfi() { - companion object - } - - data class PingResponse( - val `success`: kotlin.Boolean) : MqttEventFfi() { - companion object - } - - data class Error( - val `message`: kotlin.String) : MqttEventFfi() { - companion object - } - - object ReconnectNeeded : MqttEventFfi() - - - data class ReconnectScheduled( - val `attempt`: kotlin.UInt, - val `delayMs`: kotlin.ULong) : MqttEventFfi() { - companion object - } - - - - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeMqttEventFFI : FfiConverterRustBuffer{ - override fun read(buf: ByteBuffer): MqttEventFfi { - return when(buf.getInt()) { - 1 -> MqttEventFfi.Connected( - FfiConverterTypeConnectionResultFFI.read(buf), - ) - 2 -> MqttEventFfi.Disconnected( - FfiConverterOptionalUByte.read(buf), - ) - 3 -> MqttEventFfi.MessageReceived( - FfiConverterTypeMqttMessageFFI.read(buf), - ) - 4 -> MqttEventFfi.Published( - FfiConverterTypePublishResultFFI.read(buf), - ) - 5 -> MqttEventFfi.Subscribed( - FfiConverterTypeSubscribeResultFFI.read(buf), - ) - 6 -> MqttEventFfi.Unsubscribed( - FfiConverterTypeUnsubscribeResultFFI.read(buf), - ) - 7 -> MqttEventFfi.PingResponse( - FfiConverterBoolean.read(buf), - ) - 8 -> MqttEventFfi.Error( - FfiConverterString.read(buf), - ) - 9 -> MqttEventFfi.ReconnectNeeded - 10 -> MqttEventFfi.ReconnectScheduled( - FfiConverterUInt.read(buf), - FfiConverterULong.read(buf), - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - - override fun allocationSize(value: MqttEventFfi) = when(value) { - is MqttEventFfi.Connected -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterTypeConnectionResultFFI.allocationSize(value.v1) - ) - } - is MqttEventFfi.Disconnected -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterOptionalUByte.allocationSize(value.`reasonCode`) - ) - } - is MqttEventFfi.MessageReceived -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterTypeMqttMessageFFI.allocationSize(value.v1) - ) - } - is MqttEventFfi.Published -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterTypePublishResultFFI.allocationSize(value.v1) - ) - } - is MqttEventFfi.Subscribed -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterTypeSubscribeResultFFI.allocationSize(value.v1) - ) - } - is MqttEventFfi.Unsubscribed -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterTypeUnsubscribeResultFFI.allocationSize(value.v1) - ) - } - is MqttEventFfi.PingResponse -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterBoolean.allocationSize(value.`success`) - ) - } - is MqttEventFfi.Error -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterString.allocationSize(value.`message`) - ) - } - is MqttEventFfi.ReconnectNeeded -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - ) - } - is MqttEventFfi.ReconnectScheduled -> { - // Add the size for the Int that specifies the variant plus the size needed for all fields - ( - 4UL - + FfiConverterUInt.allocationSize(value.`attempt`) - + FfiConverterULong.allocationSize(value.`delayMs`) - ) - } - } - - override fun write(value: MqttEventFfi, buf: ByteBuffer) { - when(value) { - is MqttEventFfi.Connected -> { - buf.putInt(1) - FfiConverterTypeConnectionResultFFI.write(value.v1, buf) - Unit - } - is MqttEventFfi.Disconnected -> { - buf.putInt(2) - FfiConverterOptionalUByte.write(value.`reasonCode`, buf) - Unit - } - is MqttEventFfi.MessageReceived -> { - buf.putInt(3) - FfiConverterTypeMqttMessageFFI.write(value.v1, buf) - Unit - } - is MqttEventFfi.Published -> { - buf.putInt(4) - FfiConverterTypePublishResultFFI.write(value.v1, buf) - Unit - } - is MqttEventFfi.Subscribed -> { - buf.putInt(5) - FfiConverterTypeSubscribeResultFFI.write(value.v1, buf) - Unit - } - is MqttEventFfi.Unsubscribed -> { - buf.putInt(6) - FfiConverterTypeUnsubscribeResultFFI.write(value.v1, buf) - Unit - } - is MqttEventFfi.PingResponse -> { - buf.putInt(7) - FfiConverterBoolean.write(value.`success`, buf) - Unit - } - is MqttEventFfi.Error -> { - buf.putInt(8) - FfiConverterString.write(value.`message`, buf) - Unit - } - is MqttEventFfi.ReconnectNeeded -> { - buf.putInt(9) - Unit - } - is MqttEventFfi.ReconnectScheduled -> { - buf.putInt(10) - FfiConverterUInt.write(value.`attempt`, buf) - FfiConverterULong.write(value.`delayMs`, buf) - Unit - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } -} - - - - - - -/** - * @suppress - */ -public object FfiConverterOptionalUByte: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): kotlin.UByte? { - if (buf.get().toInt() == 0) { - return null - } - return FfiConverterUByte.read(buf) - } - - override fun allocationSize(value: kotlin.UByte?): ULong { - if (value == null) { - return 1UL - } else { - return 1UL + FfiConverterUByte.allocationSize(value) - } - } - - override fun write(value: kotlin.UByte?, buf: ByteBuffer) { - if (value == null) { - buf.put(0) - } else { - buf.put(1) - FfiConverterUByte.write(value, buf) - } - } -} - - - - -/** - * @suppress - */ -public object FfiConverterOptionalUShort: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): kotlin.UShort? { - if (buf.get().toInt() == 0) { - return null - } - return FfiConverterUShort.read(buf) - } - - override fun allocationSize(value: kotlin.UShort?): ULong { - if (value == null) { - return 1UL - } else { - return 1UL + FfiConverterUShort.allocationSize(value) - } - } - - override fun write(value: kotlin.UShort?, buf: ByteBuffer) { - if (value == null) { - buf.put(0) - } else { - buf.put(1) - FfiConverterUShort.write(value, buf) - } - } -} - - - - -/** - * @suppress - */ -public object FfiConverterOptionalString: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): kotlin.String? { - if (buf.get().toInt() == 0) { - return null - } - return FfiConverterString.read(buf) - } - - override fun allocationSize(value: kotlin.String?): ULong { - if (value == null) { - return 1UL - } else { - return 1UL + FfiConverterString.allocationSize(value) - } - } - - override fun write(value: kotlin.String?, buf: ByteBuffer) { - if (value == null) { - buf.put(0) - } else { - buf.put(1) - FfiConverterString.write(value, buf) - } - } -} - - - - -/** - * @suppress - */ -public object FfiConverterOptionalTypeMqttEventFFI: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): MqttEventFfi? { - if (buf.get().toInt() == 0) { - return null - } - return FfiConverterTypeMqttEventFFI.read(buf) - } - - override fun allocationSize(value: MqttEventFfi?): ULong { - if (value == null) { - return 1UL - } else { - return 1UL + FfiConverterTypeMqttEventFFI.allocationSize(value) - } - } - - override fun write(value: MqttEventFfi?, buf: ByteBuffer) { - if (value == null) { - buf.put(0) - } else { - buf.put(1) - FfiConverterTypeMqttEventFFI.write(value, buf) - } - } -} - - - - -/** - * @suppress - */ -public object FfiConverterSequenceString: FfiConverterRustBuffer> { - override fun read(buf: ByteBuffer): List { - val len = buf.getInt() - return List(len) { - FfiConverterString.read(buf) - } - } - - override fun allocationSize(value: List): ULong { - val sizeForLength = 4UL - val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() - return sizeForLength + sizeForItems - } - - override fun write(value: List, buf: ByteBuffer) { - buf.putInt(value.size) - value.iterator().forEach { - FfiConverterString.write(it, buf) - } - } -} - - - - -/** - * @suppress - */ -public object FfiConverterSequenceTypeMqttDatagramFFI: FfiConverterRustBuffer> { - override fun read(buf: ByteBuffer): List { - val len = buf.getInt() - return List(len) { - FfiConverterTypeMqttDatagramFFI.read(buf) - } - } - - override fun allocationSize(value: List): ULong { - val sizeForLength = 4UL - val sizeForItems = value.map { FfiConverterTypeMqttDatagramFFI.allocationSize(it) }.sum() - return sizeForLength + sizeForItems - } - - override fun write(value: List, buf: ByteBuffer) { - buf.putInt(value.size) - value.iterator().forEach { - FfiConverterTypeMqttDatagramFFI.write(it, buf) - } - } -} - - - - -/** - * @suppress - */ -public object FfiConverterSequenceTypeMqttEventFFI: FfiConverterRustBuffer> { - override fun read(buf: ByteBuffer): List { - val len = buf.getInt() - return List(len) { - FfiConverterTypeMqttEventFFI.read(buf) - } - } - - override fun allocationSize(value: List): ULong { - val sizeForLength = 4UL - val sizeForItems = value.map { FfiConverterTypeMqttEventFFI.allocationSize(it) }.sum() - return sizeForLength + sizeForItems - } - - override fun write(value: List, buf: ByteBuffer) { - buf.putInt(value.size) - value.iterator().forEach { - FfiConverterTypeMqttEventFFI.write(it, buf) - } - } -} - diff --git a/scripts/build_swift_bindings.sh b/scripts/build_swift_bindings.sh new file mode 100755 index 00000000..6994e318 --- /dev/null +++ b/scripts/build_swift_bindings.sh @@ -0,0 +1,127 @@ +#!/bin/bash +set -e + +# Default to debug build +PROFILE="debug" +CARGO_PROFILE="dev" +TARGET_DIR="target/debug" +COVERAGE=false + +if [[ "$1" == "--release" ]]; then + PROFILE="release" + CARGO_PROFILE="release" + TARGET_DIR="target/release" + shift +fi + +if [[ "$1" == "--coverage" ]]; then + COVERAGE=true + shift +fi + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +# Temporary generation dir; final files are distributed below +SWIFT_GEN_DIR="swift/Generated" +# SPM target paths (must stay in sync with Package.swift) +SWIFT_C_TARGET="swift/Sources/flowsdk_ffi" +SWIFT_TARGET="swift/Sources/FlowSDK" +SWIFT_LIB_DIR="swift/lib" + +echo "Building flowsdk_ffi ($PROFILE)..." +if [[ "$COVERAGE" == true ]]; then + mkdir -p target/llvm-cov-target + export RUSTFLAGS="-C instrument-coverage" + export LLVM_PROFILE_FILE="$PWD/target/llvm-cov-target/swift-%p-%m.profraw" +fi +cargo build -p flowsdk_ffi --profile "$CARGO_PROFILE" --features quic + +echo "Generating Swift bindings..." +mkdir -p "$SWIFT_GEN_DIR" +cargo run -p flowsdk_ffi --features=uniffi/cli,quic --bin uniffi-bindgen generate \ + --library "$TARGET_DIR/libflowsdk_ffi.dylib" \ + --language swift \ + --out-dir "$SWIFT_GEN_DIR" + +echo "Distributing generated files to SPM target directories..." +# C module target: only the header (SPM auto-generates the module map) +mkdir -p "$SWIFT_C_TARGET" +cp "$SWIFT_GEN_DIR/flowsdk_ffiFFI.h" "$SWIFT_C_TARGET/flowsdk_ffi.h" +# Swift target: the generated Swift bindings +mkdir -p "$SWIFT_TARGET" +cp "$SWIFT_GEN_DIR/flowsdk_ffi.swift" "$SWIFT_TARGET/" +# Normalize the SPM module name so consumers import `flowsdk_ffi` instead of `flowsdk_ffiFFI`. +perl -0pi -e 's/canImport\(flowsdk_ffiFFI\)/canImport(flowsdk_ffi)/g; s/import flowsdk_ffiFFI/import flowsdk_ffi/g' "$SWIFT_TARGET/flowsdk_ffi.swift" +# Clean up temp dir +rm -rf "$SWIFT_GEN_DIR" + +echo "Copying library for Swift package..." +mkdir -p "$SWIFT_LIB_DIR" +cp "$TARGET_DIR/libflowsdk_ffi.dylib" "$SWIFT_LIB_DIR/" + +if [[ "$1" == "--xcframework" ]]; then + echo "Building multi-arch XCFramework..." + + # Ensure required targets are installed + rustup target add aarch64-apple-darwin x86_64-apple-darwin aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios 2>/dev/null || true + + # Build macOS slices + echo " Building aarch64-apple-darwin..." + cargo build -p flowsdk_ffi --profile "$CARGO_PROFILE" --features quic --target aarch64-apple-darwin + echo " Building x86_64-apple-darwin..." + cargo build -p flowsdk_ffi --profile "$CARGO_PROFILE" --features quic --target x86_64-apple-darwin + + # Build iOS device + echo " Building aarch64-apple-ios..." + cargo build -p flowsdk_ffi --profile "$CARGO_PROFILE" --features quic --target aarch64-apple-ios + + # Build iOS simulator (universal: arm64 sim + x86_64 sim) + echo " Building aarch64-apple-ios-sim..." + cargo build -p flowsdk_ffi --profile "$CARGO_PROFILE" --features quic --target aarch64-apple-ios-sim + echo " Building x86_64-apple-ios (simulator)..." + cargo build -p flowsdk_ffi --profile "$CARGO_PROFILE" --features quic --target x86_64-apple-ios + + MACOS_ARM="target/aarch64-apple-darwin/$PROFILE/libflowsdk_ffi.a" + MACOS_X86="target/x86_64-apple-darwin/$PROFILE/libflowsdk_ffi.a" + IOS_ARM="target/aarch64-apple-ios/$PROFILE/libflowsdk_ffi.a" + IOS_SIM_ARM="target/aarch64-apple-ios-sim/$PROFILE/libflowsdk_ffi.a" + IOS_SIM_X86="target/x86_64-apple-ios/$PROFILE/libflowsdk_ffi.a" + + mkdir -p swift/xcframework-intermediates + + # Universal macOS .a + echo " Lipo-ing macOS slices..." + lipo -create "$MACOS_ARM" "$MACOS_X86" \ + -output swift/xcframework-intermediates/libflowsdk_ffi-macos.a + + # Universal iOS simulator .a + echo " Lipo-ing iOS simulator slices..." + lipo -create "$IOS_SIM_ARM" "$IOS_SIM_X86" \ + -output swift/xcframework-intermediates/libflowsdk_ffi-ios-sim.a + + echo " Creating XCFramework..." + rm -rf swift/FlowSDK.xcframework + xcodebuild -create-xcframework \ + -library swift/xcframework-intermediates/libflowsdk_ffi-macos.a \ + -headers "$SWIFT_C_TARGET" \ + -library "$IOS_ARM" \ + -headers "$SWIFT_C_TARGET" \ + -library swift/xcframework-intermediates/libflowsdk_ffi-ios-sim.a \ + -headers "$SWIFT_C_TARGET" \ + -output swift/FlowSDK.xcframework + + echo " XCFramework created at swift/FlowSDK.xcframework" +fi + +if [[ "$1" == "--test" ]]; then + echo "Running Swift build verification..." + LIBRARY_PATH="$PWD/$SWIFT_LIB_DIR" swift build --package-path swift +fi + +if [[ "$COVERAGE" == true ]]; then + echo "Running TcpClientExample to collect coverage data..." + LIBRARY_PATH="$PWD/$SWIFT_LIB_DIR" timeout 5s swift run --package-path swift TcpClientExample broker.emqx.io 1883 || true +fi + +echo "Done!" diff --git a/src/mqtt_client/engine.rs b/src/mqtt_client/engine.rs index 9843534d..387e4f7c 100644 --- a/src/mqtt_client/engine.rs +++ b/src/mqtt_client/engine.rs @@ -998,13 +998,13 @@ impl QuicMqttEngine { quinn_proto::Event::Stream(_stream_id) => { // Stream event (readable/writable etc) } - quinn_proto::Event::Connected => { + quinn_proto::Event::Connected // QUIC Handshake done. Open a bidirectional stream for MQTT. - if self.mqtt_stream.is_none() { - if let Some(stream_id) = conn.streams().open(quinn_proto::Dir::Bi) { - self.mqtt_stream = Some(stream_id); - self.mqtt_engine.connect(); - } + if self.mqtt_stream.is_none() => + { + if let Some(stream_id) = conn.streams().open(quinn_proto::Dir::Bi) { + self.mqtt_stream = Some(stream_id); + self.mqtt_engine.connect(); } } quinn_proto::Event::ConnectionLost { .. } => { diff --git a/swift/Examples/QuicClientExample/main.swift b/swift/Examples/QuicClientExample/main.swift new file mode 100644 index 00000000..8fc83f7f --- /dev/null +++ b/swift/Examples/QuicClientExample/main.swift @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MPL-2.0 +// FlowSDK QUIC MQTT client example (macOS / Linux) +// +// Usage: +// LIBRARY_PATH=swift/lib swift run QuicClientExample [host] [port] +// +// For Wireshark key logging: +// SSLKEYLOGFILE=~/tmp/sslkeylog.txt LIBRARY_PATH=swift/lib swift run QuicClientExample +// +// Default broker: broker.emqx.io:14567 + +import Foundation +import FlowSDK + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#endif + +// MARK: - Configuration + +private let brokerHost = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "broker.emqx.io" +private let brokerPort = CommandLine.arguments.count > 2 ? Int(CommandLine.arguments[2])! : 14567 +private let runDurationMs: UInt64 = 10_000 +private let tickIntervalMs: UInt64 = 10 +private let recvBufSize = 65536 + +// MARK: - POSIX UDP helpers + +private func resolveHostAddr(_ host: String, port: Int) -> sockaddr_in { + var hints = addrinfo() + hints.ai_family = AF_INET + hints.ai_socktype = SOCK_DGRAM + var res: UnsafeMutablePointer? + guard getaddrinfo(host, String(port), &hints, &res) == 0, let ai = res else { + fatalError("getaddrinfo failed for \(host):\(port)") + } + defer { freeaddrinfo(res) } + var addr = sockaddr_in() + withUnsafeMutableBytes(of: &addr) { + $0.copyMemory(from: UnsafeRawBufferPointer(start: ai.pointee.ai_addr, + count: Int(ai.pointee.ai_addrlen))) + } + return addr +} + +private func makeNonBlockingUdpSocket() -> Int32 { + let fd = socket(AF_INET, SOCK_DGRAM, 0) + guard fd >= 0 else { fatalError("socket() failed") } + let flags = fcntl(fd, F_GETFL) + _ = fcntl(fd, F_SETFL, flags | O_NONBLOCK) + return fd +} + +private func sendDatagram(_ fd: Int32, data: Data, to addr: inout sockaddr_in) { + guard !data.isEmpty else { return } + data.withUnsafeBytes { ptr in + withUnsafeBytes(of: &addr) { addrPtr in + _ = sendto(fd, ptr.baseAddress!, data.count, 0, + addrPtr.baseAddress!.assumingMemoryBound(to: sockaddr.self), + socklen_t(MemoryLayout.size)) + } + } +} + +private func recvDatagram(_ fd: Int32, buf: inout [UInt8]) -> Data? { + var src = sockaddr_in() + var srcLen = socklen_t(MemoryLayout.size) + let n = withUnsafeMutableBytes(of: &src) { srcPtr in + recvfrom(fd, &buf, buf.count, 0, + srcPtr.baseAddress!.assumingMemoryBound(to: sockaddr.self), + &srcLen) + } + guard n > 0 else { return nil } + return Data(buf[0.. Bool { + var pfd = pollfd(fd: fd, events: Int16(POLLIN), revents: 0) + return poll(&pfd, 1, Int32(timeoutMs)) > 0 +} + +// MARK: - Engine helpers + +func nowMs(since startMs: UInt64) -> UInt64 { + return UInt64(Date().timeIntervalSince1970 * 1000) - startMs +} + +/// Addr string expected by the FFI engine: "1.2.3.4:14567" +func addrString(addr: sockaddr_in, port: Int) -> String { + var a = addr.sin_addr + var buf = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) + inet_ntop(AF_INET, &a, &buf, socklen_t(INET_ADDRSTRLEN)) + return "\(String(cString: buf)):\(port)" +} + +func sendOutgoing(_ engine: QuicMqttEngineFfi, fd: Int32, to addr: inout sockaddr_in) { + for dgram in engine.takeOutgoingDatagrams() { + sendDatagram(fd, data: dgram.data, to: &addr) + } +} + +// MARK: - Entry point + +print("Initializing FlowSDK QUIC Swift Example...") + +let opts = MqttOptionsFfi( + clientId: "swift_quic_\(Int(Date().timeIntervalSince1970) % 100_000)", + mqttVersion: 5, + cleanStart: true, + keepAlive: 30, // Must match QUIC idle timeout (30 s) + username: nil, + password: nil, + reconnectBaseDelayMs: 1_000, + reconnectMaxDelayMs: 10_000, + maxReconnectAttempts: 3 +) + +let engine = QuicMqttEngineFfi(opts: opts) +print("QUIC Engine created.") + +// Enable TLS key logging when SSLKEYLOGFILE is set (for Wireshark) +let enableKeyLog = ProcessInfo.processInfo.environment["SSLKEYLOGFILE"] != nil +let tlsOpts = MqttTlsOptionsFfi( + caCertFile: nil, + clientCertFile: nil, + clientKeyFile: nil, + insecureSkipVerify: true, // Demo broker only — do not use in production + alpnProtocols: [], + enableKeyLog: enableKeyLog +) + +// Open non-blocking UDP socket and resolve broker +var brokerAddr = resolveHostAddr(brokerHost, port: brokerPort) +let fd = makeNonBlockingUdpSocket() +let serverAddrStr = addrString(addr: brokerAddr, port: brokerPort) + +// Track relative time from engine creation (required by tick API) +let engineStartMs = UInt64(Date().timeIntervalSince1970 * 1000) + +print("Connecting to QUIC broker at \(serverAddrStr) (host: \(brokerHost))...") +engine.connect(serverAddr: serverAddrStr, serverName: brokerHost, + tlsOpts: tlsOpts, nowMs: nowMs(since: engineStartMs)) +// Tick immediately to generate initial QUIC handshake packets +_ = engine.handleTick(nowMs: nowMs(since: engineStartMs)) +sendOutgoing(engine, fd: fd, to: &brokerAddr) + +// Main event loop +var recvBuf = [UInt8](repeating: 0, count: recvBufSize) +var subscribed = false +var published = false + +while nowMs(since: engineStartMs) < runDurationMs { + // Drain all received datagrams + if waitReadable(fd, timeoutMs: Int(tickIntervalMs)) { + while let data = recvDatagram(fd, buf: &recvBuf) { + engine.handleDatagram(data: data, remoteAddr: serverAddrStr, + nowMs: nowMs(since: engineStartMs)) + } + } + + // Tick the engine (drives QUIC timers + MQTT keepalive) + let events = engine.handleTick(nowMs: nowMs(since: engineStartMs)) + + for event in events { + switch event { + case .connected(let r): + print("Connected! sessionPresent=\(r.sessionPresent)") + if !subscribed { + let pid = engine.subscribe(topicFilter: "test/swift/quic", qos: 1) + print("Subscribed to 'test/swift/quic' (PID \(pid))") + subscribed = true + sendOutgoing(engine, fd: fd, to: &brokerAddr) + } + case .subscribed(let r): + print("Subscribe ack PID \(r.packetId)") + if !published { + let payload = Data("Hello from Swift QUIC!".utf8) + let pid = engine.publish(topic: "test/swift/quic", payload: payload, qos: 1) + print("Published to 'test/swift/quic' (PID \(pid))") + published = true + // Tick immediately so QUIC frames are generated before the next sendOutgoing + _ = engine.handleTick(nowMs: nowMs(since: engineStartMs)) + sendOutgoing(engine, fd: fd, to: &brokerAddr) + } + case .messageReceived(let m): + let msg = String(data: m.payload, encoding: .utf8) ?? "" + print("✅ Message on '\(m.topic)': \(msg)") + case .published(let r): + print("✅ Publish ack PID \(r.packetId.map(String.init) ?? "none")") + case .disconnected(let reasonCode): + print("⚠️ Disconnected. reasonCode=\(String(describing: reasonCode))") + case .error(let message): + print("❌ Error: \(message)") + case .reconnectNeeded: + print("ℹ Reconnect needed") + case .reconnectScheduled(let attempt, let delayMs): + print("ℹ Reconnect attempt \(attempt) in \(delayMs)ms") + case .pingResponse(let success): + print("ℹ Ping response: success=\(success)") + case .unsubscribed(_): + break + } + } + + // Forward engine-generated outgoing datagrams + sendOutgoing(engine, fd: fd, to: &brokerAddr) +} + +// Graceful disconnect +print("Run time elapsed, disconnecting...") +engine.disconnect() +sendOutgoing(engine, fd: fd, to: &brokerAddr) +close(fd) +print("Done.") diff --git a/swift/Examples/TcpClientExample/main.swift b/swift/Examples/TcpClientExample/main.swift new file mode 100644 index 00000000..0e5556c4 --- /dev/null +++ b/swift/Examples/TcpClientExample/main.swift @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MPL-2.0 +// FlowSDK TCP MQTT client example (macOS / Linux) +// +// Usage: +// LIBRARY_PATH=swift/lib swift run TcpClientExample [host] [port] +// +// Default broker: broker.emqx.io:1883 + +import Foundation +import FlowSDK + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#endif + +// MARK: - Configuration + +private let brokerHost = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "broker.emqx.io" +private let brokerPort = CommandLine.arguments.count > 2 ? Int(CommandLine.arguments[2])! : 1883 +private let runDurationMs: UInt64 = 15_000 +private let tickIntervalMs: UInt64 = 10 +private let recvBufSize = 65536 + +// MARK: - POSIX TCP helpers + +private func makeNonBlockingTcpSocket(host: String, port: Int) -> Int32 { + var hints = addrinfo() + hints.ai_family = AF_UNSPEC + hints.ai_socktype = SOCK_STREAM + var res: UnsafeMutablePointer? + guard getaddrinfo(host, String(port), &hints, &res) == 0, let ai = res else { + fatalError("getaddrinfo failed for \(host):\(port)") + } + defer { freeaddrinfo(res) } + + let fd = socket(ai.pointee.ai_family, ai.pointee.ai_socktype, ai.pointee.ai_protocol) + guard fd >= 0 else { fatalError("socket() failed") } + + // Non-blocking + let flags = fcntl(fd, F_GETFL) + _ = fcntl(fd, F_SETFL, flags | O_NONBLOCK) + + let rc = Darwin.connect(fd, ai.pointee.ai_addr, ai.pointee.ai_addrlen) + if rc < 0 && errno != EINPROGRESS { + fatalError("connect() failed: \(String(cString: strerror(errno)))") + } + return fd +} + +private func waitWritable(_ fd: Int32, timeoutMs: Int) -> Bool { + var pfd = pollfd(fd: fd, events: Int16(POLLOUT), revents: 0) + return poll(&pfd, 1, Int32(timeoutMs)) > 0 +} + +private func waitReadable(_ fd: Int32, timeoutMs: Int) -> Bool { + var pfd = pollfd(fd: fd, events: Int16(POLLIN), revents: 0) + return poll(&pfd, 1, Int32(timeoutMs)) > 0 +} + +private func sendAll(_ fd: Int32, data: Data) { + guard !data.isEmpty else { return } + data.withUnsafeBytes { ptr in + var sent = 0 + while sent < data.count { + let n = send(fd, ptr.baseAddress!.advanced(by: sent), data.count - sent, 0) + if n > 0 { + sent += n + } else if n < 0 { + if errno == EINTR { + continue // signal interrupted — retry immediately + } else if errno == EAGAIN || errno == EWOULDBLOCK { + _ = waitWritable(fd, timeoutMs: 1000) // wait for buffer space + continue + } else { + return // unrecoverable error + } + } + } + } +} + +// MARK: - Entry point + +func nowMs(since startMs: UInt64) -> UInt64 { + return UInt64(Date().timeIntervalSince1970 * 1000) - startMs +} + +func handleEvent(_ event: MqttEventFfi, engine: MqttEngineFfi, fd: Int32, + subscribed: inout Bool, published: inout Bool, startMs: UInt64) { + switch event { + case .connected(let r): + print("Connected! sessionPresent=\(r.sessionPresent)") + if !subscribed { + let pid = engine.subscribe(topicFilter: "test/swift/tcp", qos: 1) + print("Subscribed to 'test/swift/tcp' (PID \(pid))") + subscribed = true + sendAll(fd, data: engine.takeOutgoing()) + } + case .subscribed(let r): + print("Subscribe ack PID \(r.packetId)") + if !published { + let payload = Data("Hello from Swift TCP!".utf8) + let pid = engine.publish(topic: "test/swift/tcp", payload: payload, qos: 1, priority: nil) + print("Published to 'test/swift/tcp' (PID \(pid))") + published = true + sendAll(fd, data: engine.takeOutgoing()) + } + case .messageReceived(let m): + let msg = String(data: m.payload, encoding: .utf8) ?? "" + print("✅ Message on '\(m.topic)': \(msg)") + case .published(let r): + print("✅ Publish ack PID \(r.packetId.map(String.init) ?? "none")") + case .disconnected(let reasonCode): + print("⚠️ Disconnected. reasonCode=\(String(describing: reasonCode))") + case .error(let message): + print("❌ Error: \(message)") + case .reconnectNeeded: + print("ℹ Reconnect needed") + case .reconnectScheduled(let attempt, let delayMs): + print("ℹ Reconnect attempt \(attempt) in \(delayMs)ms") + case .pingResponse(let success): + print("ℹ Ping response: success=\(success)") + case .unsubscribed(_): + break + } +} + +print("Initializing FlowSDK TCP Swift Example...") + +let opts = MqttOptionsFfi( + clientId: "swift_tcp_\(Int(Date().timeIntervalSince1970) % 100_000)", + mqttVersion: 5, + cleanStart: true, + keepAlive: 60, + username: nil, + password: nil, + reconnectBaseDelayMs: 1_000, + reconnectMaxDelayMs: 10_000, + maxReconnectAttempts: 3 +) + +let engine = MqttEngineFfi.newWithOpts(opts: opts) +print("Engine created.") + +// Track relative time from engine creation (required by tick API) +let engineStartMs = UInt64(Date().timeIntervalSince1970 * 1000) + +// Trigger MQTT CONNECT packet generation +engine.connect() + +// Open TCP socket +print("Connecting to TCP broker at \(brokerHost):\(brokerPort)...") +let fd = makeNonBlockingTcpSocket(host: brokerHost, port: brokerPort) + +// Complete async TCP connect +if !waitWritable(fd, timeoutMs: 5000) { + fatalError("TCP connect timed out") +} +var err: Int32 = 0 +var errLen = socklen_t(MemoryLayout.size) +getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errLen) +guard err == 0 else { fatalError("TCP connect error: \(err)") } +print("TCP connected.") + +// Flush initial CONNECT packet +sendAll(fd, data: engine.takeOutgoing()) + +// Main event loop +var recvBuf = [UInt8](repeating: 0, count: recvBufSize) +var subscribed = false +var published = false + +while nowMs(since: engineStartMs) < runDurationMs { + // Wait up to one tick interval for readable data + if waitReadable(fd, timeoutMs: Int(tickIntervalMs)) { + let n = recv(fd, &recvBuf, recvBufSize, 0) + if n > 0 { + let inData = Data(recvBuf[0..