commit e743afc0154003897dd1e318e543f1a80ccf9dc4
parent 6a196b1ff23563d2b58662ed091f85208a995307
Author: triesap <tyson@radroots.org>
Date: Mon, 3 Aug 2026 13:47:55 +0000
kotlin: align generated sdk contract
- Generate Kotlin bindings from the same versioned private SDK FFI authority.
- Commit deterministic Kotlin source and exact source-and-output provenance.
- Enforce the mobile DTO inventory while excluding host-owned application policy.
- Verify repeat generation, schema freshness, xtask tests, and architecture gates.
Diffstat:
4 files changed, 1817 insertions(+), 18 deletions(-)
diff --git a/docs/platform/kotlin.md b/docs/platform/kotlin.md
@@ -0,0 +1,15 @@
+# Kotlin SDK FFI bindings
+
+Kotlin uses the same private `radroots_sdk_ffi` contract as Swift. Generate the
+Kotlin/JNA source and `source.lock` from the SDK root with:
+
+```sh
+cargo extbuild run -- cargo xtask generate bindings kotlin
+```
+
+The repository currently owns no Android or Kotlin build. Accordingly, the
+release gate validates deterministic generation, exact source/output hashes,
+and the versioned DTO schema inventory only. A future Android host must add its
+own JNA/runtime packaging and compile lane; it must not create a parallel
+product engine or move keychain, background, scheduling, or presentation
+responsibilities into this binding crate.
diff --git a/generated/kotlin/source.lock b/generated/kotlin/source.lock
@@ -0,0 +1,12 @@
+{
+ "schema_version": 1,
+ "source_package": "radroots_sdk_ffi",
+ "source_version": "0.1.0-alpha",
+ "source_sha256": "733c27226e29c3bea820e869e93f3916f1e1dbe9fc1cce30c15d31063ad5a033",
+ "generator": "uniffi",
+ "generator_version": "0.29.5",
+ "language": "kotlin",
+ "outputs": {
+ "uniffi/radroots_sdk/radroots_sdk.kt": "63a91b7c7790ee786d1c94cfad30cbe6e3ac5c737108ec61c74696fc068ef7da"
+ }
+}
diff --git a/generated/kotlin/uniffi/radroots_sdk/radroots_sdk.kt b/generated/kotlin/uniffi/radroots_sdk/radroots_sdk.kt
@@ -0,0 +1,1744 @@
+// 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.radroots_sdk
+
+// 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
+import kotlin.coroutines.resume
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+// 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_radroots_sdk_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_radroots_sdk_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<KotlinType, FfiType> {
+ // 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<KotlinType>: FfiConverter<KotlinType, RustBuffer.ByValue> {
+ 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<E> {
+ 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 <U, E: kotlin.Exception> uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler<E>, 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<E: kotlin.Exception> uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler<E>, 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<InternalException> {
+ 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 <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U {
+ return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback)
+}
+
+internal inline fun<T> 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<T, reified E: Throwable> 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<T: Any> {
+ private val map = ConcurrentHashMap<Long, T>()
+ 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 "radroots_sdk_ffi"
+}
+
+private inline fun <reified Lib : Library> loadIndirect(
+ componentName: String
+): Lib {
+ return Native.load<Lib>(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,)
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// For large crates we prevent `MethodTooLargeException` (see #2340)
+// N.B. the name of the extension is very misleading, since it is
+// rather `InterfaceTooLargeException`, caused by too many methods
+// in the interface for large crates.
+//
+// By splitting the otherwise huge interface into two parts
+// * UniffiLib
+// * IntegrityCheckingUniffiLib (this)
+// we allow for ~2x as many methods in the UniffiLib interface.
+//
+// The `ffi_uniffi_contract_version` method and all checksum methods are put
+// into `IntegrityCheckingUniffiLib` and these methods are called only once,
+// when the library is loaded.
+internal interface IntegrityCheckingUniffiLib : Library {
+ // Integrity check functions only
+ fun uniffi_radroots_sdk_ffi_checksum_method_mobileclient_capabilities(
+): Short
+fun uniffi_radroots_sdk_ffi_checksum_method_mobileclient_close(
+): Short
+fun uniffi_radroots_sdk_ffi_checksum_method_mobileclient_is_closed(
+): Short
+fun uniffi_radroots_sdk_ffi_checksum_constructor_mobileclient_memory(
+): Short
+fun ffi_radroots_sdk_ffi_uniffi_contract_version(
+): Int
+
+}
+
+// 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 {
+ val componentName = "radroots_sdk"
+ // For large crates we prevent `MethodTooLargeException` (see #2340)
+ // N.B. the name of the extension is very misleading, since it is
+ // rather `InterfaceTooLargeException`, caused by too many methods
+ // in the interface for large crates.
+ //
+ // By splitting the otherwise huge interface into two parts
+ // * UniffiLib (this)
+ // * IntegrityCheckingUniffiLib
+ // And all checksum methods are put into `IntegrityCheckingUniffiLib`
+ // we allow for ~2x as many methods in the UniffiLib interface.
+ //
+ // Thus we first load the library with `loadIndirect` as `IntegrityCheckingUniffiLib`
+ // so that we can (optionally!) call `uniffiCheckApiChecksums`...
+ loadIndirect<IntegrityCheckingUniffiLib>(componentName)
+ .also { lib: IntegrityCheckingUniffiLib ->
+ uniffiCheckContractApiVersion(lib)
+ uniffiCheckApiChecksums(lib)
+ }
+ // ... and then we load the library as `UniffiLib`
+ // N.B. we cannot use `loadIndirect` once and then try to cast it to `UniffiLib`
+ // => results in `java.lang.ClassCastException: com.sun.proxy.$Proxy cannot be cast to ...`
+ // error. So we must call `loadIndirect` twice. For crates large enough
+ // to trigger this issue, the performance impact is negligible, running on
+ // a macOS M1 machine the `loadIndirect` call takes ~50ms.
+ val lib = loadIndirect<UniffiLib>(componentName)
+ // No need to check the contract version and checksums, since
+ // we already did that with `IntegrityCheckingUniffiLib` above.
+ // Loading of library with integrity check done.
+ lib
+ }
+
+ // The Cleaner for the whole library
+ internal val CLEANER: UniffiCleaner by lazy {
+ UniffiCleaner.create()
+ }
+ }
+
+ // FFI functions
+ fun uniffi_radroots_sdk_ffi_fn_clone_mobileclient(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus,
+): Pointer
+fun uniffi_radroots_sdk_ffi_fn_free_mobileclient(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus,
+): Unit
+fun uniffi_radroots_sdk_ffi_fn_constructor_mobileclient_memory(uniffi_out_err: UniffiRustCallStatus,
+): Pointer
+fun uniffi_radroots_sdk_ffi_fn_method_mobileclient_capabilities(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus,
+): RustBuffer.ByValue
+fun uniffi_radroots_sdk_ffi_fn_method_mobileclient_close(`ptr`: Pointer,
+): Long
+fun uniffi_radroots_sdk_ffi_fn_method_mobileclient_is_closed(`ptr`: Pointer,uniffi_out_err: UniffiRustCallStatus,
+): Byte
+fun ffi_radroots_sdk_ffi_rustbuffer_alloc(`size`: Long,uniffi_out_err: UniffiRustCallStatus,
+): RustBuffer.ByValue
+fun ffi_radroots_sdk_ffi_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,uniffi_out_err: UniffiRustCallStatus,
+): RustBuffer.ByValue
+fun ffi_radroots_sdk_ffi_rustbuffer_free(`buf`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
+): Unit
+fun ffi_radroots_sdk_ffi_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Long,uniffi_out_err: UniffiRustCallStatus,
+): RustBuffer.ByValue
+fun ffi_radroots_sdk_ffi_rust_future_poll_u8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_u8(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_u8(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_u8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Byte
+fun ffi_radroots_sdk_ffi_rust_future_poll_i8(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_i8(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_i8(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_i8(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Byte
+fun ffi_radroots_sdk_ffi_rust_future_poll_u16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_u16(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_u16(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_u16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Short
+fun ffi_radroots_sdk_ffi_rust_future_poll_i16(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_i16(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_i16(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_i16(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Short
+fun ffi_radroots_sdk_ffi_rust_future_poll_u32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_u32(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_u32(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_u32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Int
+fun ffi_radroots_sdk_ffi_rust_future_poll_i32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_i32(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_i32(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_i32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Int
+fun ffi_radroots_sdk_ffi_rust_future_poll_u64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_u64(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_u64(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_u64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Long
+fun ffi_radroots_sdk_ffi_rust_future_poll_i64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_i64(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_i64(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_i64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Long
+fun ffi_radroots_sdk_ffi_rust_future_poll_f32(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_f32(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_f32(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_f32(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Float
+fun ffi_radroots_sdk_ffi_rust_future_poll_f64(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_f64(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_f64(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_f64(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Double
+fun ffi_radroots_sdk_ffi_rust_future_poll_pointer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_pointer(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_pointer(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_pointer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Pointer
+fun ffi_radroots_sdk_ffi_rust_future_poll_rust_buffer(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_rust_buffer(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_rust_buffer(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_rust_buffer(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): RustBuffer.ByValue
+fun ffi_radroots_sdk_ffi_rust_future_poll_void(`handle`: Long,`callback`: UniffiRustFutureContinuationCallback,`callbackData`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_cancel_void(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_free_void(`handle`: Long,
+): Unit
+fun ffi_radroots_sdk_ffi_rust_future_complete_void(`handle`: Long,uniffi_out_err: UniffiRustCallStatus,
+): Unit
+
+}
+
+private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) {
+ // Get the bindings contract version from our ComponentInterface
+ val bindings_contract_version = 29
+ // Get the scaffolding contract version by calling the into the dylib
+ val scaffolding_contract_version = lib.ffi_radroots_sdk_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: IntegrityCheckingUniffiLib) {
+ if (lib.uniffi_radroots_sdk_ffi_checksum_method_mobileclient_capabilities() != 23938.toShort()) {
+ throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
+ }
+ if (lib.uniffi_radroots_sdk_ffi_checksum_method_mobileclient_close() != 15210.toShort()) {
+ throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
+ }
+ if (lib.uniffi_radroots_sdk_ffi_checksum_method_mobileclient_is_closed() != 40205.toShort()) {
+ throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
+ }
+ if (lib.uniffi_radroots_sdk_ffi_checksum_constructor_mobileclient_memory() != 50243.toShort()) {
+ throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
+ }
+}
+
+/**
+ * @suppress
+ */
+public fun uniffiEnsureInitialized() {
+ UniffiLib.INSTANCE
+}
+
+// Async support
+// Async return type handlers
+
+internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
+internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()
+
+internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>()
+
+// FFI type for Rust future continuations
+internal object uniffiRustFutureContinuationCallbackImpl: UniffiRustFutureContinuationCallback {
+ override fun callback(data: Long, pollResult: Byte) {
+ uniffiContinuationHandleMap.remove(data).resume(pollResult)
+ }
+}
+
+internal suspend fun<T, F, E: kotlin.Exception> uniffiRustCallAsync(
+ rustFuture: Long,
+ pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit,
+ completeFunc: (Long, UniffiRustCallStatus) -> F,
+ freeFunc: (Long) -> Unit,
+ liftFunc: (F) -> T,
+ errorHandler: UniffiRustCallStatusErrorHandler<E>
+): T {
+ try {
+ do {
+ val pollResult = suspendCancellableCoroutine<Byte> { continuation ->
+ pollFunc(
+ rustFuture,
+ uniffiRustFutureContinuationCallbackImpl,
+ uniffiContinuationHandleMap.insert(continuation)
+ )
+ }
+ } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY);
+
+ return liftFunc(
+ uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
+ )
+ } finally {
+ freeFunc(rustFuture)
+ }
+}
+
+// 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?) {
+ for (arg in args) {
+ when (arg) {
+ is Disposable -> arg.destroy()
+ is ArrayList<*> -> {
+ for (idx in arg.indices) {
+ val element = arg[idx]
+ if (element is Disposable) {
+ element.destroy()
+ }
+ }
+ }
+ is Map<*, *> -> {
+ for (element in arg.values) {
+ if (element is Disposable) {
+ element.destroy()
+ }
+ }
+ }
+ is Iterable<*> -> {
+ for (element in arg) {
+ if (element is Disposable) {
+ element.destroy()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @suppress
+ */
+inline fun <T : Disposable?, R> 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
+/**
+ * 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()
+}
+
+/**
+ * @suppress
+ */
+public object FfiConverterBoolean: FfiConverter<Boolean, Byte> {
+ 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<String, RustBuffer.ByValue> {
+ // 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)
+ }
+}
+
+
+// This template implements a class for working with a Rust struct via a Pointer/Arc<T>
+// to the live Rust struct on the other side of the FFI.
+//
+// Each instance implements core operations for working with the Rust `Arc<T>` 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<T>` 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
+//
+
+
+/**
+ * Thread-safe mobile handle delegating lifecycle to [`radroots_sdk::Client`].
+ */
+public interface MobileClientInterface {
+
+ /**
+ * Returns stable capability DTOs without probing host resources.
+ */
+ fun `capabilities`(): List<CapabilityStatus>
+
+ /**
+ * Explicitly closes SDK-owned resources without installing a runtime.
+ */
+ suspend fun `close`()
+
+ /**
+ * Returns whether explicit SDK close completed across all clones.
+ */
+ fun `isClosed`(): kotlin.Boolean
+
+ companion object
+}
+
+/**
+ * Thread-safe mobile handle delegating lifecycle to [`radroots_sdk::Client`].
+ */
+open class MobileClient: Disposable, AutoCloseable, MobileClientInterface
+{
+
+ 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 <R> 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_radroots_sdk_ffi_fn_free_mobileclient(ptr, status)
+ }
+ }
+ }
+ }
+
+ fun uniffiClonePointer(): Pointer {
+ return uniffiRustCall() { status ->
+ UniffiLib.INSTANCE.uniffi_radroots_sdk_ffi_fn_clone_mobileclient(pointer!!, status)
+ }
+ }
+
+
+ /**
+ * Returns stable capability DTOs without probing host resources.
+ */override fun `capabilities`(): List<CapabilityStatus> {
+ return FfiConverterSequenceTypeCapabilityStatus.lift(
+ callWithPointer {
+ uniffiRustCall() { _status ->
+ UniffiLib.INSTANCE.uniffi_radroots_sdk_ffi_fn_method_mobileclient_capabilities(
+ it, _status)
+}
+ }
+ )
+ }
+
+
+
+ /**
+ * Explicitly closes SDK-owned resources without installing a runtime.
+ */
+ @Throws(Exception::class)
+ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+ override suspend fun `close`() {
+ return uniffiRustCallAsync(
+ callWithPointer { thisPtr ->
+ UniffiLib.INSTANCE.uniffi_radroots_sdk_ffi_fn_method_mobileclient_close(
+ thisPtr,
+
+ )
+ },
+ { future, callback, continuation -> UniffiLib.INSTANCE.ffi_radroots_sdk_ffi_rust_future_poll_void(future, callback, continuation) },
+ { future, continuation -> UniffiLib.INSTANCE.ffi_radroots_sdk_ffi_rust_future_complete_void(future, continuation) },
+ { future -> UniffiLib.INSTANCE.ffi_radroots_sdk_ffi_rust_future_free_void(future) },
+ // lift function
+ { Unit },
+
+ // Error FFI converter
+ Exception.ErrorHandler,
+ )
+ }
+
+
+ /**
+ * Returns whether explicit SDK close completed across all clones.
+ */override fun `isClosed`(): kotlin.Boolean {
+ return FfiConverterBoolean.lift(
+ callWithPointer {
+ uniffiRustCall() { _status ->
+ UniffiLib.INSTANCE.uniffi_radroots_sdk_ffi_fn_method_mobileclient_is_closed(
+ it, _status)
+}
+ }
+ )
+ }
+
+
+
+
+
+ companion object {
+
+ /**
+ * Creates a deterministic, local-only memory client without I/O.
+ */
+ @Throws(Exception::class) fun `memory`(): MobileClient {
+ return FfiConverterTypeMobileClient.lift(
+ uniffiRustCallWithError(Exception) { _status ->
+ UniffiLib.INSTANCE.uniffi_radroots_sdk_ffi_fn_constructor_mobileclient_memory(
+ _status)
+}
+ )
+ }
+
+
+
+ }
+
+}
+
+/**
+ * @suppress
+ */
+public object FfiConverterTypeMobileClient: FfiConverter<MobileClient, Pointer> {
+
+ override fun lower(value: MobileClient): Pointer {
+ return value.uniffiClonePointer()
+ }
+
+ override fun lift(value: Pointer): MobileClient {
+ return MobileClient(value)
+ }
+
+ override fun read(buf: ByteBuffer): MobileClient {
+ // 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: MobileClient) = 8UL
+
+ override fun write(value: MobileClient, 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)))
+ }
+}
+
+
+
+/**
+ * Versioned, presentation-independent capability observation.
+ */
+data class CapabilityStatus (
+ var `id`: kotlin.String,
+ var `compiled`: kotlin.Boolean,
+ var `configured`: kotlin.Boolean,
+ var `availability`: CapabilityAvailability,
+ var `maturity`: CapabilityMaturity
+) {
+
+ companion object
+}
+
+/**
+ * @suppress
+ */
+public object FfiConverterTypeCapabilityStatus: FfiConverterRustBuffer<CapabilityStatus> {
+ override fun read(buf: ByteBuffer): CapabilityStatus {
+ return CapabilityStatus(
+ FfiConverterString.read(buf),
+ FfiConverterBoolean.read(buf),
+ FfiConverterBoolean.read(buf),
+ FfiConverterTypeCapabilityAvailability.read(buf),
+ FfiConverterTypeCapabilityMaturity.read(buf),
+ )
+ }
+
+ override fun allocationSize(value: CapabilityStatus) = (
+ FfiConverterString.allocationSize(value.`id`) +
+ FfiConverterBoolean.allocationSize(value.`compiled`) +
+ FfiConverterBoolean.allocationSize(value.`configured`) +
+ FfiConverterTypeCapabilityAvailability.allocationSize(value.`availability`) +
+ FfiConverterTypeCapabilityMaturity.allocationSize(value.`maturity`)
+ )
+
+ override fun write(value: CapabilityStatus, buf: ByteBuffer) {
+ FfiConverterString.write(value.`id`, buf)
+ FfiConverterBoolean.write(value.`compiled`, buf)
+ FfiConverterBoolean.write(value.`configured`, buf)
+ FfiConverterTypeCapabilityAvailability.write(value.`availability`, buf)
+ FfiConverterTypeCapabilityMaturity.write(value.`maturity`, buf)
+ }
+}
+
+
+
+/**
+ * Current side-effect-free capability availability.
+ */
+
+enum class CapabilityAvailability {
+
+ AVAILABLE,
+ DEGRADED,
+ UNAVAILABLE,
+ UNSUPPORTED;
+ companion object
+}
+
+
+/**
+ * @suppress
+ */
+public object FfiConverterTypeCapabilityAvailability: FfiConverterRustBuffer<CapabilityAvailability> {
+ override fun read(buf: ByteBuffer) = try {
+ CapabilityAvailability.values()[buf.getInt() - 1]
+ } catch (e: IndexOutOfBoundsException) {
+ throw RuntimeException("invalid enum value, something is very wrong!!", e)
+ }
+
+ override fun allocationSize(value: CapabilityAvailability) = 4UL
+
+ override fun write(value: CapabilityAvailability, buf: ByteBuffer) {
+ buf.putInt(value.ordinal + 1)
+ }
+}
+
+
+
+
+
+/**
+ * Stable capability maturity independent of runtime availability.
+ */
+
+enum class CapabilityMaturity {
+
+ STABLE,
+ PREVIEW,
+ EXPERIMENTAL;
+ companion object
+}
+
+
+/**
+ * @suppress
+ */
+public object FfiConverterTypeCapabilityMaturity: FfiConverterRustBuffer<CapabilityMaturity> {
+ override fun read(buf: ByteBuffer) = try {
+ CapabilityMaturity.values()[buf.getInt() - 1]
+ } catch (e: IndexOutOfBoundsException) {
+ throw RuntimeException("invalid enum value, something is very wrong!!", e)
+ }
+
+ override fun allocationSize(value: CapabilityMaturity) = 4UL
+
+ override fun write(value: CapabilityMaturity, buf: ByteBuffer) {
+ buf.putInt(value.ordinal + 1)
+ }
+}
+
+
+
+
+
+
+
+/**
+ * Secret-safe SDK failure at the language boundary.
+ */
+sealed class Exception: kotlin.Exception() {
+
+ class Sdk(
+
+ val `code`: kotlin.String,
+
+ val `message`: kotlin.String,
+
+ val `retryable`: kotlin.Boolean
+ ) : Exception() {
+ override val message
+ get() = "code=${ `code` }, message=${ `message` }, retryable=${ `retryable` }"
+ }
+
+
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<Exception> {
+ override fun lift(error_buf: RustBuffer.ByValue): Exception = FfiConverterTypeError.lift(error_buf)
+ }
+
+
+}
+
+/**
+ * @suppress
+ */
+public object FfiConverterTypeError : FfiConverterRustBuffer<Exception> {
+ override fun read(buf: ByteBuffer): Exception {
+
+
+ return when(buf.getInt()) {
+ 1 -> Exception.Sdk(
+ FfiConverterString.read(buf),
+ FfiConverterString.read(buf),
+ FfiConverterBoolean.read(buf),
+ )
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+ }
+
+ override fun allocationSize(value: Exception): ULong {
+ return when(value) {
+ is Exception.Sdk -> (
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ 4UL
+ + FfiConverterString.allocationSize(value.`code`)
+ + FfiConverterString.allocationSize(value.`message`)
+ + FfiConverterBoolean.allocationSize(value.`retryable`)
+ )
+ }
+ }
+
+ override fun write(value: Exception, buf: ByteBuffer) {
+ when(value) {
+ is Exception.Sdk -> {
+ buf.putInt(1)
+ FfiConverterString.write(value.`code`, buf)
+ FfiConverterString.write(value.`message`, buf)
+ FfiConverterBoolean.write(value.`retryable`, buf)
+ Unit
+ }
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+
+}
+
+
+
+
+/**
+ * @suppress
+ */
+public object FfiConverterSequenceTypeCapabilityStatus: FfiConverterRustBuffer<List<CapabilityStatus>> {
+ override fun read(buf: ByteBuffer): List<CapabilityStatus> {
+ val len = buf.getInt()
+ return List<CapabilityStatus>(len) {
+ FfiConverterTypeCapabilityStatus.read(buf)
+ }
+ }
+
+ override fun allocationSize(value: List<CapabilityStatus>): ULong {
+ val sizeForLength = 4UL
+ val sizeForItems = value.map { FfiConverterTypeCapabilityStatus.allocationSize(it) }.sum()
+ return sizeForLength + sizeForItems
+ }
+
+ override fun write(value: List<CapabilityStatus>, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ value.iterator().forEach {
+ FfiConverterTypeCapabilityStatus.write(it, buf)
+ }
+ }
+}
diff --git a/tools/xtask/src/bindings.rs b/tools/xtask/src/bindings.rs
@@ -104,24 +104,17 @@ fn install_outputs(root: &Path, language: &'static str, temporary: &Path) -> Res
let output_dir = root.join("generated").join(language);
fs::create_dir_all(&output_dir)
.map_err(|error| format!("failed to create {}: {error}", output_dir.display()))?;
- let mut generated = fs::read_dir(temporary)
- .map_err(|error| format!("failed to read {}: {error}", temporary.display()))?
- .collect::<Result<Vec<_>, _>>()
- .map_err(|error| format!("failed to enumerate {}: {error}", temporary.display()))?;
- generated.sort_by_key(std::fs::DirEntry::file_name);
+ let mut generated = Vec::new();
+ collect_files(temporary, &mut generated)?;
+ generated.sort();
let mut outputs = BTreeMap::new();
- for entry in generated {
- let path = entry.path();
- if !path.is_file() {
- return Err(format!(
- "unexpected generated binding entry: {}",
- path.display()
- ));
- }
- let name = entry
- .file_name()
- .into_string()
- .map_err(|_| format!("non-UTF-8 generated binding path: {}", path.display()))?;
+ for path in generated {
+ let name = path
+ .strip_prefix(temporary)
+ .map_err(|error| format!("failed to relativize {}: {error}", path.display()))?
+ .to_str()
+ .ok_or_else(|| format!("non-UTF-8 generated binding path: {}", path.display()))?
+ .replace('\\', "/");
let contents = fs::read(&path)
.map_err(|error| format!("failed to read {}: {error}", path.display()))?;
let destination = output_dir.join(&name);
@@ -132,6 +125,10 @@ fn install_outputs(root: &Path, language: &'static str, temporary: &Path) -> Res
name.clone(),
format!("{:x}", Sha256::digest(text.as_bytes())),
);
+ if let Some(parent) = destination.parent() {
+ fs::create_dir_all(parent)
+ .map_err(|error| format!("failed to create {}: {error}", parent.display()))?;
+ }
write_if_changed(&destination, &text)?;
}
if outputs.is_empty() {
@@ -175,7 +172,38 @@ pub fn check(root: &Path) -> Result<(), String> {
"radroots_sdkFFI.h",
"radroots_sdkFFI.modulemap",
],
- )
+ )?;
+ check_language(root, "kotlin", &["uniffi/radroots_sdk/radroots_sdk.kt"])?;
+ check_kotlin_schema_inventory(root)
+}
+
+fn check_kotlin_schema_inventory(root: &Path) -> Result<(), String> {
+ let path = root.join("generated/kotlin/uniffi/radroots_sdk/radroots_sdk.kt");
+ let source = fs::read_to_string(&path)
+ .map_err(|error| format!("failed to read {}: {error}", path.display()))?;
+ for required in [
+ "class MobileClient",
+ "data class CapabilityStatus",
+ "enum class CapabilityAvailability",
+ "enum class CapabilityMaturity",
+ "sealed class Exception",
+ ] {
+ if !source.contains(required) {
+ return Err(format!(
+ "generated Kotlin schema inventory is missing {required}: {}",
+ path.display()
+ ));
+ }
+ }
+ for forbidden in ["Keychain", "BackgroundTask", "Presentation"] {
+ if source.contains(forbidden) {
+ return Err(format!(
+ "generated Kotlin bindings contain host-owned {forbidden} API: {}",
+ path.display()
+ ));
+ }
+ }
+ Ok(())
}
fn check_language(root: &Path, language: &'static str, expected: &[&str]) -> Result<(), String> {