summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates')
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt44
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt15
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt78
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt129
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt110
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt111
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt9
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt83
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt161
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt34
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt57
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt161
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt138
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt42
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt87
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt53
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt38
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt51
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt109
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt77
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt57
37 files changed, 2072 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
new file mode 100644
index 0000000000..c6a32655f2
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
@@ -0,0 +1,44 @@
+// Async return type handlers
+
+internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort()
+internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort()
+
+internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>()
+
+// FFI type for Rust future continuations
+internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType {
+ override fun callback(continuationHandle: USize, pollResult: Short) {
+ uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult)
+ }
+
+ internal fun register(lib: _UniFFILib) {
+ lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this)
+ }
+}
+
+internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
+ rustFuture: Pointer,
+ pollFunc: (Pointer, USize) -> Unit,
+ completeFunc: (Pointer, RustCallStatus) -> F,
+ freeFunc: (Pointer) -> Unit,
+ liftFunc: (F) -> T,
+ errorHandler: CallStatusErrorHandler<E>
+): T {
+ try {
+ do {
+ val pollResult = suspendCancellableCoroutine<Short> { continuation ->
+ pollFunc(
+ rustFuture,
+ uniffiContinuationHandleMap.insert(continuation)
+ )
+ }
+ } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY);
+
+ return liftFunc(
+ rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
+ )
+ } finally {
+ freeFunc(rustFuture)
+ }
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
new file mode 100644
index 0000000000..8cfa2ce000
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
@@ -0,0 +1,19 @@
+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) = 1
+
+ override fun write(value: Boolean, buf: ByteBuffer) {
+ buf.put(lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
new file mode 100644
index 0000000000..4840a199b4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
@@ -0,0 +1,15 @@
+public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> {
+ override fun read(buf: ByteBuffer): ByteArray {
+ val len = buf.getInt()
+ val byteArr = ByteArray(len)
+ buf.get(byteArr)
+ return byteArr
+ }
+ override fun allocationSize(value: ByteArray): Int {
+ return 4 + value.size
+ }
+ override fun write(value: ByteArray, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ buf.put(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
new file mode 100644
index 0000000000..62a71e02f1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
@@ -0,0 +1,78 @@
+internal typealias Handle = Long
+internal class ConcurrentHandleMap<T>(
+ private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
+ private val rightMap: MutableMap<T, Handle> = mutableMapOf()
+) {
+ private val lock = java.util.concurrent.locks.ReentrantLock()
+ private val currentHandle = AtomicLong(0L)
+ private val stride = 1L
+
+ fun insert(obj: T): Handle =
+ lock.withLock {
+ rightMap[obj] ?:
+ currentHandle.getAndAdd(stride)
+ .also { handle ->
+ leftMap[handle] = obj
+ rightMap[obj] = handle
+ }
+ }
+
+ fun get(handle: Handle) = lock.withLock {
+ leftMap[handle]
+ }
+
+ fun delete(handle: Handle) {
+ this.remove(handle)
+ }
+
+ fun remove(handle: Handle): T? =
+ lock.withLock {
+ leftMap.remove(handle)?.let { obj ->
+ rightMap.remove(obj)
+ obj
+ }
+ }
+}
+
+interface ForeignCallback : com.sun.jna.Callback {
+ public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int
+}
+
+// Magic number for the Rust proxy to call using the same mechanism as every other method,
+// to free the callback once it's dropped by Rust.
+internal const val IDX_CALLBACK_FREE = 0
+// Callback return codes
+internal const val UNIFFI_CALLBACK_SUCCESS = 0
+internal const val UNIFFI_CALLBACK_ERROR = 1
+internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
+
+public abstract class FfiConverterCallbackInterface<CallbackInterface>(
+ protected val foreignCallback: ForeignCallback
+): FfiConverter<CallbackInterface, Handle> {
+ private val handleMap = ConcurrentHandleMap<CallbackInterface>()
+
+ // Registers the foreign callback with the Rust side.
+ // This method is generated for each callback interface.
+ internal abstract fun register(lib: _UniFFILib)
+
+ fun drop(handle: Handle): RustBuffer.ByValue {
+ return handleMap.remove(handle).let { RustBuffer.ByValue() }
+ }
+
+ override fun lift(value: Handle): CallbackInterface {
+ return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug")
+ }
+
+ override fun read(buf: ByteBuffer) = lift(buf.getLong())
+
+ override fun lower(value: CallbackInterface) =
+ handleMap.insert(value).also {
+ assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." }
+ }
+
+ override fun allocationSize(value: CallbackInterface) = 8
+
+ override fun write(value: CallbackInterface, buf: ByteBuffer) {
+ buf.putLong(lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
new file mode 100644
index 0000000000..5a29f0acc3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
@@ -0,0 +1,129 @@
+{%- let cbi = ci|get_callback_interface_definition(name) %}
+{%- let type_name = cbi|type_name(ci) %}
+{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %}
+
+{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
+{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
+{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }}
+{{- self.add_import("kotlin.concurrent.withLock") }}
+
+// Declaration and FfiConverters for {{ type_name }} Callback Interface
+
+public interface {{ type_name }} {
+ {% for meth in cbi.methods() -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+ companion object
+}
+
+// The ForeignCallback that is passed to Rust.
+internal class {{ foreign_callback }} : ForeignCallback {
+ @Suppress("TooGenericExceptionCaught")
+ override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
+ val cb = {{ ffi_converter_name }}.lift(handle)
+ return when (method) {
+ IDX_CALLBACK_FREE -> {
+ {{ ffi_converter_name }}.drop(handle)
+ // Successful return
+ // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
+ UNIFFI_CALLBACK_SUCCESS
+ }
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+ {{ loop.index }} -> {
+ // Call the method, write to outBuf and return a status code
+ // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info
+ try {
+ this.{{ method_name }}(cb, argsData, argsLen, outBuf)
+ } catch (e: Throwable) {
+ // Unexpected error
+ try {
+ // Try to serialize the error into a string
+ outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString()))
+ } catch (e: Throwable) {
+ // If that fails, then it's time to give up and just return
+ }
+ UNIFFI_CALLBACK_UNEXPECTED_ERROR
+ }
+ }
+ {% endfor %}
+ else -> {
+ // An unexpected error happened.
+ // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
+ try {
+ // Try to serialize the error into a string
+ outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index"))
+ } catch (e: Throwable) {
+ // If that fails, then it's time to give up and just return
+ }
+ UNIFFI_CALLBACK_UNEXPECTED_ERROR
+ }
+ }
+ }
+
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
+ @Suppress("UNUSED_PARAMETER")
+ private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
+ {%- if meth.arguments().len() > 0 %}
+ val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+ {%- endif %}
+
+ {%- match meth.return_type() %}
+ {%- when Some with (return_type) %}
+ fun makeCall() : Int {
+ val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}(
+ {%- for arg in meth.arguments() %}
+ {{ arg|read_fn }}(argsBuf)
+ {% if !loop.last %}, {% endif %}
+ {%- endfor %}
+ )
+ outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue))
+ return UNIFFI_CALLBACK_SUCCESS
+ }
+ {%- when None %}
+ fun makeCall() : Int {
+ kotlinCallbackInterface.{{ meth.name()|fn_name }}(
+ {%- for arg in meth.arguments() %}
+ {{ arg|read_fn }}(argsBuf)
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+ )
+ return UNIFFI_CALLBACK_SUCCESS
+ }
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ fun makeCallAndHandleError() : Int = makeCall()
+ {%- when Some(error_type) %}
+ fun makeCallAndHandleError() : Int = try {
+ makeCall()
+ } catch (e: {{ error_type|type_name(ci) }}) {
+ // Expected error, serialize it into outBuf
+ outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e))
+ UNIFFI_CALLBACK_ERROR
+ }
+ {%- endmatch %}
+
+ return makeCallAndHandleError()
+ }
+ {% endfor %}
+}
+
+// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust.
+public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>(
+ foreignCallback = {{ foreign_callback }}()
+) {
+ override fun register(lib: _UniFFILib) {
+ rustCall() { status ->
+ lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
new file mode 100644
index 0000000000..04150c5d78
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
@@ -0,0 +1,62 @@
+{%- match config.custom_types.get(name.as_str()) %}
+{%- when None %}
+{#- Define the type using typealiases to the builtin #}
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias {{ name }} = {{ builtin|type_name(ci) }}
+public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }}
+
+{%- when Some with (config) %}
+
+{%- let ffi_type_name=builtin|ffi_type|ffi_type_name_by_value %}
+
+{# When the config specifies a different type name, create a typealias for it #}
+{%- match config.type_name %}
+{%- when Some(concrete_type_name) %}
+/**
+ * Typealias from the type name used in the UDL file to the custom type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias {{ name }} = {{ concrete_type_name }}
+{%- else %}
+{%- endmatch %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_name }}> {
+ override fun lift(value: {{ ffi_type_name }}): {{ name }} {
+ val builtinValue = {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ override fun lower(value: {{ name }}): {{ ffi_type_name }} {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtinValue)
+ }
+
+ override fun read(buf: ByteBuffer): {{ name }} {
+ val builtinValue = {{ builtin|read_fn }}(buf)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ override fun allocationSize(value: {{ name }}): Int {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|allocation_size_fn }}(builtinValue)
+ }
+
+ override fun write(value: {{ name }}, buf: ByteBuffer) {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ {{ builtin|write_fn }}(builtinValue, buf)
+ }
+}
+{%- endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
new file mode 100644
index 0000000000..4237c6f9a8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
@@ -0,0 +1,36 @@
+public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> {
+ override fun read(buf: ByteBuffer): java.time.Duration {
+ // Type mismatch (should be u64) but we check for overflow/underflow below
+ val seconds = buf.getLong()
+ // Type mismatch (should be u32) but we check for overflow/underflow below
+ val nanoseconds = buf.getInt().toLong()
+ if (seconds < 0) {
+ throw java.time.DateTimeException("Duration exceeds minimum or maximum value supported by uniffi")
+ }
+ if (nanoseconds < 0) {
+ throw java.time.DateTimeException("Duration nanoseconds exceed minimum or maximum supported by uniffi")
+ }
+ return java.time.Duration.ofSeconds(seconds, nanoseconds)
+ }
+
+ // 8 bytes for seconds, 4 bytes for nanoseconds
+ override fun allocationSize(value: java.time.Duration) = 12
+
+ override fun write(value: java.time.Duration, buf: ByteBuffer) {
+ if (value.seconds < 0) {
+ // Rust does not support negative Durations
+ throw IllegalArgumentException("Invalid duration, must be non-negative")
+ }
+
+ if (value.nano < 0) {
+ // Java docs provide guarantee that nano will always be positive, so this should be impossible
+ // See: https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html
+ throw IllegalArgumentException("Invalid duration, nano value must be non-negative")
+ }
+
+ // Type mismatch (should be u64) but since Rust doesn't support negative durations we should be OK
+ buf.putLong(value.seconds)
+ // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK
+ buf.putInt(value.nano)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
new file mode 100644
index 0000000000..d4c4a1684a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
@@ -0,0 +1,110 @@
+{#
+// Kotlin's `enum class` construct doesn't support variants with associated data,
+// but is a little nicer for consumers than its `sealed class` enum pattern.
+// So, we switch here, using `enum class` for enums with no associated data
+// and `sealed class` for the general case.
+#}
+
+{%- if e.is_flat() %}
+
+enum class {{ type_name }} {
+ {% for variant in e.variants() -%}
+ {{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %}
+ {%- endfor %}
+ companion object
+}
+
+public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer) = try {
+ {{ type_name }}.values()[buf.getInt() - 1]
+ } catch (e: IndexOutOfBoundsException) {
+ throw RuntimeException("invalid enum value, something is very wrong!!", e)
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = 4
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ buf.putInt(value.ordinal + 1)
+ }
+}
+
+{% else %}
+
+sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} {
+ {% for variant in e.variants() -%}
+ {% if !variant.has_fields() -%}
+ object {{ variant|type_name(ci) }} : {{ type_name }}()
+ {% else -%}
+ data class {{ variant|type_name(ci) }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
+ {% endfor -%}
+ ) : {{ type_name }}() {
+ companion object
+ }
+ {%- endif %}
+ {% endfor %}
+
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ when(this) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant|type_name(ci) }} -> {
+ {%- if variant.has_fields() %}
+ {% call kt::destroy_fields(variant) %}
+ {% else -%}
+ // Nothing to destroy
+ {%- endif %}
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+ {% endif %}
+ companion object
+}
+
+public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant|type_name(ci) }}{% if variant.has_fields() %}(
+ {% for field in variant.fields() -%}
+ {{ field|read_fn }}(buf),
+ {% endfor -%}
+ ){%- endif -%}
+ {%- endfor %}
+ else -> throw RuntimeException("invalid enum value, something is very wrong!!")
+ }
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant|type_name(ci) }} -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ {%- for field in variant.fields() %}
+ + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ )
+ }
+ {%- endfor %}
+ }
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant|type_name(ci) }} -> {
+ buf.putInt({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ Unit
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+}
+
+{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
new file mode 100644
index 0000000000..986db5424d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
@@ -0,0 +1,111 @@
+{%- let type_name = type_|type_name(ci) %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+
+{% if e.is_flat() %}
+sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} {
+ // Each variant is a nested class
+ // Flat enums carries a string error message, so no special implementation is necessary.
+ {% for variant in e.variants() -%}
+ class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message)
+ {% endfor %}
+
+ companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
+ }
+}
+{%- else %}
+sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} {
+ // Each variant is a nested class
+ {% for variant in e.variants() -%}
+ {%- let variant_name = variant|error_variant_name %}
+ class {{ variant_name }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
+ {% endfor -%}
+ ) : {{ type_name }}() {
+ override val message
+ get() = "{%- for field in variant.fields() %}{{ field.name()|var_name|unquote }}=${ {{field.name()|var_name }} }{% if !loop.last %}, {% endif %}{% endfor %}"
+ }
+ {% endfor %}
+
+ companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
+ }
+
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ when(this) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant|error_variant_name }} -> {
+ {%- if variant.has_fields() %}
+ {% call kt::destroy_fields(variant) %}
+ {% else -%}
+ // Nothing to destroy
+ {%- endif %}
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+ {% endif %}
+}
+{%- endif %}
+
+public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ {% if e.is_flat() %}
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({{ Type::String.borrow()|read_fn }}(buf))
+ {%- endfor %}
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+ {% else %}
+
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({% if variant.has_fields() %}
+ {% for field in variant.fields() -%}
+ {{ field|read_fn }}(buf),
+ {% endfor -%}
+ {%- endif -%})
+ {%- endfor %}
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+ {%- endif %}
+ }
+
+ override fun allocationSize(value: {{ type_name }}): Int {
+ {%- if e.is_flat() %}
+ return 4
+ {%- else %}
+ return when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant|error_variant_name }} -> (
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ 4
+ {%- for field in variant.fields() %}
+ + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ )
+ {%- endfor %}
+ }
+ {%- endif %}
+ }
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant|error_variant_name }} -> {
+ buf.putInt({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ Unit
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
new file mode 100644
index 0000000000..0fade7a0bc
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
@@ -0,0 +1,9 @@
+{%- let package_name=self.external_type_package_name(module_path, namespace) %}
+{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %}
+{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %}
+{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %}
+{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %}
+
+{{- self.add_import(fully_qualified_type_name) }}
+{{- self.add_import(fully_qualified_ffi_converter_name) }}
+{{ self.add_import_as(fully_qualified_rustbuffer_name, local_rustbuffer_name) }}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
new file mode 100644
index 0000000000..3b2c9d225a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
@@ -0,0 +1,71 @@
+// 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.
+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): Int
+
+ // 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.toLong()).also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+ write(value, bbuf)
+ rbuf.writeField("len", bbuf.position())
+ 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
+public interface FfiConverterRustBuffer<KotlinType>: FfiConverter<KotlinType, RustBuffer.ByValue> {
+ override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value)
+ override fun lower(value: KotlinType) = lowerIntoRustBuffer(value)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
new file mode 100644
index 0000000000..eafec5d122
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterFloat: FfiConverter<Float, Float> {
+ override fun lift(value: Float): Float {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Float {
+ return buf.getFloat()
+ }
+
+ override fun lower(value: Float): Float {
+ return value
+ }
+
+ override fun allocationSize(value: Float) = 4
+
+ override fun write(value: Float, buf: ByteBuffer) {
+ buf.putFloat(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
new file mode 100644
index 0000000000..9fc2892c95
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterDouble: FfiConverter<Double, Double> {
+ override fun lift(value: Double): Double {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Double {
+ return buf.getDouble()
+ }
+
+ override fun lower(value: Double): Double {
+ return value
+ }
+
+ override fun allocationSize(value: Double) = 8
+
+ override fun write(value: Double, buf: ByteBuffer) {
+ buf.putDouble(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
new file mode 100644
index 0000000000..3544b2f9e6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
@@ -0,0 +1,83 @@
+{{ self.add_import("kotlinx.coroutines.CoroutineScope") }}
+{{ self.add_import("kotlinx.coroutines.delay") }}
+{{ self.add_import("kotlinx.coroutines.isActive") }}
+{{ self.add_import("kotlinx.coroutines.launch") }}
+
+internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte()
+internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte()
+internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte()
+internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte()
+internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte()
+
+// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then
+// invokes them.
+internal interface UniFfiRustTaskCallback : com.sun.jna.Callback {
+ fun callback(rustTaskData: Pointer?, statusCode: Byte)
+}
+
+internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback {
+ fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte {
+ if (rustTask == null) {
+ FfiConverterForeignExecutor.drop(handle)
+ return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
+ } else {
+ val coroutineScope = FfiConverterForeignExecutor.lift(handle)
+ if (coroutineScope.isActive) {
+ val job = coroutineScope.launch {
+ if (delayMs > 0) {
+ delay(delayMs.toLong())
+ }
+ rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS)
+ }
+ job.invokeOnCompletion { cause ->
+ if (cause != null) {
+ rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED)
+ }
+ }
+ return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
+ } else {
+ return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED
+ }
+ }
+ }
+}
+
+public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> {
+ internal val handleMap = UniFfiHandleMap<CoroutineScope>()
+
+ internal fun drop(handle: USize) {
+ handleMap.remove(handle)
+ }
+
+ internal fun register(lib: _UniFFILib) {
+ {%- match ci.ffi_foreign_executor_callback_set() %}
+ {%- when Some with (fn) %}
+ lib.{{ fn.name() }}(UniFfiForeignExecutorCallback)
+ {%- when None %}
+ {#- No foreign executor, we don't set anything #}
+ {% endmatch %}
+ }
+
+ // Number of live handles, exposed so we can test the memory management
+ public fun handleCount() : Int {
+ return handleMap.size
+ }
+
+ override fun allocationSize(value: CoroutineScope) = USize.size
+
+ override fun lift(value: USize): CoroutineScope {
+ return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift")
+ }
+
+ override fun read(buf: ByteBuffer): CoroutineScope {
+ return lift(USize.readFromBuffer(buf))
+ }
+
+ override fun lower(value: CoroutineScope): USize {
+ return handleMap.insert(value)
+ }
+
+ override fun write(value: CoroutineScope, buf: ByteBuffer) {
+ lower(value).writeToBuffer(buf)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
new file mode 100644
index 0000000000..382a5f7413
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
@@ -0,0 +1,161 @@
+// 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.
+// Error runtime.
+@Structure.FieldOrder("code", "error_buf")
+internal open class RustCallStatus : Structure() {
+ @JvmField var code: Byte = 0
+ @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
+
+ class ByValue: RustCallStatus(), Structure.ByValue
+
+ fun isSuccess(): Boolean {
+ return code == 0.toByte()
+ }
+
+ fun isError(): Boolean {
+ return code == 1.toByte()
+ }
+
+ fun isPanic(): Boolean {
+ return code == 2.toByte()
+ }
+}
+
+class InternalException(message: String) : Exception(message)
+
+// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
+interface CallStatusErrorHandler<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: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
+ var status = RustCallStatus();
+ val return_value = callback(status)
+ checkCallStatus(errorHandler, status)
+ return return_value
+}
+
+// Check RustCallStatus and throw an error if the call wasn't successful
+private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) {
+ 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({{ Type::String.borrow()|lift_fn }}(status.error_buf))
+ } else {
+ throw InternalException("Rust panic")
+ }
+ } else {
+ throw InternalException("Unknown rust call status: $status.code")
+ }
+}
+
+// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
+object NullCallStatusErrorHandler: CallStatusErrorHandler<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> rustCall(callback: (RustCallStatus) -> U): U {
+ return rustCallWithError(NullCallStatusErrorHandler, callback);
+}
+
+// IntegerType that matches Rust's `usize` / C's `size_t`
+public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) {
+ // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin.
+ override fun toByte() = toInt().toByte()
+ // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed.
+ @Deprecated("`toInt().toChar()` is deprecated")
+ override fun toChar() = toInt().toChar()
+ override fun toShort() = toInt().toShort()
+
+ fun writeToBuffer(buf: ByteBuffer) {
+ // Make sure we always write usize integers using native byte-order, since they may be
+ // casted to pointer values
+ buf.order(ByteOrder.nativeOrder())
+ try {
+ when (Native.SIZE_T_SIZE) {
+ 4 -> buf.putInt(toInt())
+ 8 -> buf.putLong(toLong())
+ else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
+ }
+ } finally {
+ buf.order(ByteOrder.BIG_ENDIAN)
+ }
+ }
+
+ companion object {
+ val size: Int
+ get() = Native.SIZE_T_SIZE
+
+ fun readFromBuffer(buf: ByteBuffer) : USize {
+ // Make sure we always read usize integers using native byte-order, since they may be
+ // casted from pointer values
+ buf.order(ByteOrder.nativeOrder())
+ try {
+ return when (Native.SIZE_T_SIZE) {
+ 4 -> USize(buf.getInt().toLong())
+ 8 -> USize(buf.getLong())
+ else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
+ }
+ } finally {
+ buf.order(ByteOrder.BIG_ENDIAN)
+ }
+ }
+ }
+}
+
+
+// Map handles to objects
+//
+// This is used when the Rust code expects an opaque pointer to represent some foreign object.
+// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an
+// object reference , nor does it support leaking a reference to Rust.
+//
+// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to
+// Rust when it needs an opaque pointer.
+//
+// TODO: refactor callbacks to use this class
+internal class UniFfiHandleMap<T: Any> {
+ private val map = ConcurrentHashMap<USize, T>()
+ // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible
+ // values seems like enough. If somehow we generate 4 billion handles, then this will wrap
+ // around back to zero and we can assume the first handle generated will have been dropped by
+ // then.
+ private val counter = java.util.concurrent.atomic.AtomicInteger(0)
+
+ val size: Int
+ get() = map.size
+
+ fun insert(obj: T): USize {
+ val handle = USize(counter.getAndAdd(1).toLong())
+ map.put(handle, obj)
+ return handle
+ }
+
+ fun get(handle: USize): T? {
+ return map.get(handle)
+ }
+
+ fun remove(handle: USize): T? {
+ return map.remove(handle)
+ }
+}
+
+// FFI type for Rust future continuations
+internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback {
+ fun callback(continuationHandle: USize, pollResult: Short);
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
new file mode 100644
index 0000000000..75564276be
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterShort: FfiConverter<Short, Short> {
+ override fun lift(value: Short): Short {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Short {
+ return buf.getShort()
+ }
+
+ override fun lower(value: Short): Short {
+ return value
+ }
+
+ override fun allocationSize(value: Short) = 2
+
+ override fun write(value: Short, buf: ByteBuffer) {
+ buf.putShort(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
new file mode 100644
index 0000000000..b7a8131c8b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterInt: FfiConverter<Int, Int> {
+ 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) = 4
+
+ override fun write(value: Int, buf: ByteBuffer) {
+ buf.putInt(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
new file mode 100644
index 0000000000..601cfc7c2c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterLong: FfiConverter<Long, Long> {
+ 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) = 8
+
+ override fun write(value: Long, buf: ByteBuffer) {
+ buf.putLong(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
new file mode 100644
index 0000000000..9237768dbf
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterByte: FfiConverter<Byte, Byte> {
+ override fun lift(value: Byte): Byte {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Byte {
+ return buf.get()
+ }
+
+ override fun lower(value: Byte): Byte {
+ return value
+ }
+
+ override fun allocationSize(value: Byte) = 1
+
+ override fun write(value: Byte, buf: ByteBuffer) {
+ buf.put(value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
new file mode 100644
index 0000000000..776c402727
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
@@ -0,0 +1,34 @@
+{%- let key_type_name = key_type|type_name(ci) %}
+{%- let value_type_name = value_type|type_name(ci) %}
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_name }}, {{ value_type_name }}>> {
+ override fun read(buf: ByteBuffer): Map<{{ key_type_name }}, {{ value_type_name }}> {
+ val len = buf.getInt()
+ return buildMap<{{ key_type_name }}, {{ value_type_name }}>(len) {
+ repeat(len) {
+ val k = {{ key_type|read_fn }}(buf)
+ val v = {{ value_type|read_fn }}(buf)
+ this[k] = v
+ }
+ }
+ }
+
+ override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int {
+ val spaceForMapSize = 4
+ val spaceForChildren = value.map { (k, v) ->
+ {{ key_type|allocation_size_fn }}(k) +
+ {{ value_type|allocation_size_fn }}(v)
+ }.sum()
+ return spaceForMapSize + spaceForChildren
+ }
+
+ override fun write(value: Map<{{ key_type_name }}, {{ value_type_name }}>, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ // The parens on `(k, v)` here ensure we're calling the right method,
+ // which is important for compatibility with older android devices.
+ // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/
+ value.forEach { (k, v) ->
+ {{ key_type|write_fn }}(k, buf)
+ {{ value_type|write_fn }}(v, buf)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
new file mode 100644
index 0000000000..6a3aeada35
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
@@ -0,0 +1,57 @@
+@Synchronized
+private fun findLibraryName(componentName: String): String {
+ val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
+ if (libOverride != null) {
+ return libOverride
+ }
+ return "{{ config.cdylib_name() }}"
+}
+
+private inline fun <reified Lib : Library> loadIndirect(
+ componentName: String
+): Lib {
+ return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
+}
+
+// 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<_UniFFILib>(componentName = "{{ ci.namespace() }}")
+ .also { lib: _UniFFILib ->
+ uniffiCheckContractApiVersion(lib)
+ uniffiCheckApiChecksums(lib)
+ {% for fn in self.initialization_fns() -%}
+ {{ fn }}(lib)
+ {% endfor -%}
+ }
+ }
+ }
+
+ {% for func in ci.iter_ffi_function_definitions() -%}
+ fun {{ func.name() }}(
+ {%- call kt::arg_list_ffi_decl(func) %}
+ ): {% match func.return_type() %}{% when Some with (return_type) %}{{ return_type.borrow()|ffi_type_name_by_value }}{% when None %}Unit{% endmatch %}
+ {% endfor %}
+}
+
+private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
+ // Get the bindings contract version from our ComponentInterface
+ val bindings_contract_version = {{ ci.uniffi_contract_version() }}
+ // Get the scaffolding contract version by calling the into the dylib
+ val scaffolding_contract_version = lib.{{ ci.ffi_uniffi_contract_version().name() }}()
+ 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) {
+ {%- for (name, expected_checksum) in ci.iter_checksums() %}
+ if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) {
+ throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
+ }
+ {%- endfor %}
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
new file mode 100644
index 0000000000..b9352c690f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
@@ -0,0 +1,161 @@
+// 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<Disposable>()
+ .forEach(Disposable::destroy)
+ }
+ }
+}
+
+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
+ }
+ }
+
+// The base class for all UniFFI Object types.
+//
+// This class provides core operations for working with the Rust `Arc<T>` pointer to
+// 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
+// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
+//
+// * Each `FFIObject` 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 `FFIObject` 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 `FFIObject` 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 will
+// leak 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.
+//
+// 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 `FFIObject` 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.
+//
+// In the future we may be able to replace some of this with automatic finalization logic, such as using
+// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
+// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
+// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
+// so there would still be some complexity here).
+//
+// Sigh...all of this for want of a robust finalization mechanism.
+//
+// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
+//
+abstract class FFIObject(
+ protected val pointer: Pointer
+): Disposable, AutoCloseable {
+
+ private val wasDestroyed = AtomicBoolean(false)
+ private val callCounter = AtomicLong(1)
+
+ open protected fun freeRustArcPtr() {
+ // To be overridden in subclasses.
+ }
+
+ 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) {
+ this.freeRustArcPtr()
+ }
+ }
+ }
+
+ @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.pointer)
+ } finally {
+ // This decrement always matches the increment we performed above.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ this.freeRustArcPtr()
+ }
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
new file mode 100644
index 0000000000..8ce27a5d04
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
@@ -0,0 +1,138 @@
+{%- let obj = ci|get_object_definition(name) %}
+{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %}
+{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
+{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+
+public interface {{ type_name }}Interface {
+ {% for meth in obj.methods() -%}
+ {%- match meth.throws_type() -%}
+ {%- when Some with (throwable) -%}
+ @Throws({{ throwable|type_name(ci) }}::class)
+ {%- when None -%}
+ {%- endmatch %}
+ {% if meth.is_async() -%}
+ suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- else -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- endif %}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
+ {%- when None -%}
+ {%- endmatch -%}
+
+ {% endfor %}
+ companion object
+}
+
+class {{ type_name }}(
+ pointer: Pointer
+) : FFIObject(pointer), {{ type_name }}Interface {
+
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ constructor({% call kt::arg_list_decl(cons) -%}) :
+ this({% call kt::to_ffi_call(cons) %})
+ {%- when None %}
+ {%- endmatch %}
+
+ /**
+ * Disconnect the object from the underlying Rust object.
+ *
+ * It can be called more than once, but once called, interacting with the object
+ * causes an `IllegalStateException`.
+ *
+ * Clients **must** call this method once done with the object, or cause a memory leak.
+ */
+ override protected fun freeRustArcPtr() {
+ rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status)
+ }
+ }
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.throws_type() -%}
+ {%- when Some with (throwable) %}
+ @Throws({{ throwable|type_name(ci) }}::class)
+ {%- else -%}
+ {%- endmatch -%}
+ {%- if meth.is_async() %}
+ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+ override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
+ return uniffiRustCallAsync(
+ callWithPointer { thisPtr ->
+ _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}(
+ thisPtr,
+ {% call kt::arg_list_lowered(meth) %}
+ )
+ },
+ {{ meth|async_poll(ci) }},
+ {{ meth|async_complete(ci) }},
+ {{ meth|async_free(ci) }},
+ // lift function
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ { {{ return_type|lift_fn }}(it) },
+ {%- when None %}
+ { Unit },
+ {% endmatch %}
+ // Error FFI converter
+ {%- match meth.throws_type() %}
+ {%- when Some(e) %}
+ {{ e|type_name(ci) }}.ErrorHandler,
+ {%- when None %}
+ NullCallStatusErrorHandler,
+ {%- endmatch %}
+ )
+ }
+ {%- else -%}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} =
+ callWithPointer {
+ {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ }.let {
+ {{ return_type|lift_fn }}(it)
+ }
+
+ {%- when None -%}
+ override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
+ callWithPointer {
+ {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ }
+ {% endmatch %}
+ {% endif %}
+ {% endfor %}
+
+ {% if !obj.alternate_constructors().is_empty() -%}
+ companion object {
+ {% for cons in obj.alternate_constructors() -%}
+ fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} =
+ {{ type_name }}({% call kt::to_ffi_call(cons) %})
+ {% endfor %}
+ }
+ {% else %}
+ companion object
+ {% endif %}
+}
+
+public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
+ override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it }
+
+ override fun lift(value: Pointer): {{ type_name }} {
+ return {{ type_name }}(value)
+ }
+
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ // 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: {{ type_name }}) = 8
+
+ override fun write(value: {{ type_name }}, 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)))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
new file mode 100644
index 0000000000..56cb5f87a5
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
@@ -0,0 +1,27 @@
+{%- let inner_type_name = inner_type|type_name(ci) %}
+
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> {
+ override fun read(buf: ByteBuffer): {{ inner_type_name }}? {
+ if (buf.get().toInt() == 0) {
+ return null
+ }
+ return {{ inner_type|read_fn }}(buf)
+ }
+
+ override fun allocationSize(value: {{ inner_type_name }}?): Int {
+ if (value == null) {
+ return 1
+ } else {
+ return 1 + {{ inner_type|allocation_size_fn }}(value)
+ }
+ }
+
+ override fun write(value: {{ inner_type_name }}?, buf: ByteBuffer) {
+ if (value == null) {
+ buf.put(0)
+ } else {
+ buf.put(1)
+ {{ inner_type|write_fn }}(value, buf)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
new file mode 100644
index 0000000000..b588ca1398
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
@@ -0,0 +1,42 @@
+{%- let rec = ci|get_record_definition(name) %}
+
+data class {{ type_name }} (
+ {%- for field in rec.fields() %}
+ var {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
+ {%- match field.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }}
+ {%- else %}
+ {%- endmatch -%}
+ {% if !loop.last %}, {% endif %}
+ {%- endfor %}
+) {% if contains_object_references %}: Disposable {% endif %}{
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ {% call kt::destroy_fields(rec) %}
+ }
+ {% endif %}
+ companion object
+}
+
+public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ return {{ type_name }}(
+ {%- for field in rec.fields() %}
+ {{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = (
+ {%- for field in rec.fields() %}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%}
+ {%- endfor %}
+ )
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ {%- for field in rec.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
new file mode 100644
index 0000000000..dfbea24074
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
@@ -0,0 +1,87 @@
+// 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.
+
+@Structure.FieldOrder("capacity", "len", "data")
+open class RustBuffer : Structure() {
+ @JvmField var capacity: Int = 0
+ @JvmField var len: Int = 0
+ @JvmField var data: Pointer? = null
+
+ class ByValue: RustBuffer(), Structure.ByValue
+ class ByReference: RustBuffer(), Structure.ByReference
+
+ companion object {
+ internal fun alloc(size: Int = 0) = rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status)
+ }.also {
+ if(it.data == null) {
+ throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
+ }
+ }
+
+ internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue {
+ var buf = RustBuffer.ByValue()
+ buf.capacity = capacity
+ buf.len = len
+ buf.data = data
+ return buf
+ }
+
+ internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(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.
+ */
+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.setInt(0, value.capacity)
+ pointer.setInt(4, value.len)
+ pointer.setPointer(8, value.data)
+ }
+
+ /**
+ * Get a `RustBuffer.ByValue` from this reference.
+ */
+ fun getValue(): RustBuffer.ByValue {
+ val pointer = getPointer()
+ val value = RustBuffer.ByValue()
+ value.writeField("capacity", pointer.getInt(0))
+ value.writeField("len", pointer.getInt(4))
+ value.writeField("data", pointer.getPointer(8))
+
+ 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")
+open class ForeignBytes : Structure() {
+ @JvmField var len: Int = 0
+ @JvmField var data: Pointer? = null
+
+ class ByValue : ForeignBytes(), Structure.ByValue
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
new file mode 100644
index 0000000000..876d1bc05e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
@@ -0,0 +1,23 @@
+{%- let inner_type_name = inner_type|type_name(ci) %}
+
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_type_name }}>> {
+ override fun read(buf: ByteBuffer): List<{{ inner_type_name }}> {
+ val len = buf.getInt()
+ return List<{{ inner_type_name }}>(len) {
+ {{ inner_type|read_fn }}(buf)
+ }
+ }
+
+ override fun allocationSize(value: List<{{ inner_type_name }}>): Int {
+ val sizeForLength = 4
+ val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum()
+ return sizeForLength + sizeForItems
+ }
+
+ override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ value.forEach {
+ {{ inner_type|write_fn }}(it, buf)
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
new file mode 100644
index 0000000000..68324be4f9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
@@ -0,0 +1,53 @@
+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)
+ 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())
+ 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): Int {
+ val sizeForLength = 4
+ val sizeForString = value.length * 3
+ return sizeForLength + sizeForString
+ }
+
+ override fun write(value: String, buf: ByteBuffer) {
+ val byteBuf = toUtf8(value)
+ buf.putInt(byteBuf.limit())
+ buf.put(byteBuf)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
new file mode 100644
index 0000000000..21069d7ce8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
@@ -0,0 +1,38 @@
+public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> {
+ override fun read(buf: ByteBuffer): java.time.Instant {
+ val seconds = buf.getLong()
+ // Type mismatch (should be u32) but we check for overflow/underflow below
+ val nanoseconds = buf.getInt().toLong()
+ if (nanoseconds < 0) {
+ throw java.time.DateTimeException("Instant nanoseconds exceed minimum or maximum supported by uniffi")
+ }
+ if (seconds >= 0) {
+ return java.time.Instant.EPOCH.plus(java.time.Duration.ofSeconds(seconds, nanoseconds))
+ } else {
+ return java.time.Instant.EPOCH.minus(java.time.Duration.ofSeconds(-seconds, nanoseconds))
+ }
+ }
+
+ // 8 bytes for seconds, 4 bytes for nanoseconds
+ override fun allocationSize(value: java.time.Instant) = 12
+
+ override fun write(value: java.time.Instant, buf: ByteBuffer) {
+ var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value)
+
+ var sign = 1
+ if (epochOffset.isNegative()) {
+ sign = -1
+ epochOffset = epochOffset.negated()
+ }
+
+ if (epochOffset.nano < 0) {
+ // Java docs provide guarantee that nano will always be positive, so this should be impossible
+ // See: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html
+ throw IllegalArgumentException("Invalid timestamp, nano value must be non-negative")
+ }
+
+ buf.putLong(sign * epochOffset.seconds)
+ // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK
+ buf.putInt(epochOffset.nano)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
new file mode 100644
index 0000000000..6a841d3484
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
@@ -0,0 +1,51 @@
+{%- if func.is_async() %}
+{%- match func.throws_type() -%}
+{%- when Some with (throwable) %}
+@Throws({{ throwable|type_name(ci) }}::class)
+{%- else -%}
+{%- endmatch %}
+
+@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
+ return uniffiRustCallAsync(
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}),
+ {{ func|async_poll(ci) }},
+ {{ func|async_complete(ci) }},
+ {{ func|async_free(ci) }},
+ // lift function
+ {%- match func.return_type() %}
+ {%- when Some(return_type) %}
+ { {{ return_type|lift_fn }}(it) },
+ {%- when None %}
+ { Unit },
+ {% endmatch %}
+ // Error FFI converter
+ {%- match func.throws_type() %}
+ {%- when Some(e) %}
+ {{ e|type_name(ci) }}.ErrorHandler,
+ {%- when None %}
+ NullCallStatusErrorHandler,
+ {%- endmatch %}
+ )
+}
+
+{%- else %}
+{%- match func.throws_type() -%}
+{%- when Some with (throwable) %}
+@Throws({{ throwable|type_name(ci) }}::class)
+{%- else -%}
+{%- endmatch -%}
+
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} {
+ return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %})
+}
+{% when None %}
+
+fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) =
+ {% call kt::to_ffi_call(func) %}
+
+{% endmatch %}
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
new file mode 100644
index 0000000000..103d444ea3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
@@ -0,0 +1,109 @@
+{%- import "macros.kt" as kt %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name(ci) %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+{%- let contains_object_references = ci.item_contains_object_references(type_) %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.kt" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.kt" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.kt" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.kt" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.kt" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.kt" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.kt" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.kt" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.kt" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.kt" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.kt" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.kt" %}
+
+{%- when Type::Bytes %}
+{%- include "ByteArrayHelper.kt" %}
+
+{%- when Type::Enum { name, module_path } %}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+{%- if !ci.is_name_used_as_error(name) %}
+{% include "EnumTemplate.kt" %}
+{%- else %}
+{% include "ErrorTemplate.kt" %}
+{%- endif -%}
+
+{%- when Type::Object { module_path, name, imp } %}
+{% include "ObjectTemplate.kt" %}
+
+{%- when Type::Record { name, module_path } %}
+{% include "RecordTemplate.kt" %}
+
+{%- when Type::Optional { inner_type } %}
+{% include "OptionalTemplate.kt" %}
+
+{%- when Type::Sequence { inner_type } %}
+{% include "SequenceTemplate.kt" %}
+
+{%- when Type::Map { key_type, value_type } %}
+{% include "MapTemplate.kt" %}
+
+{%- when Type::CallbackInterface { module_path, name } %}
+{% include "CallbackInterfaceTemplate.kt" %}
+
+{%- when Type::ForeignExecutor %}
+{% include "ForeignExecutorTemplate.kt" %}
+
+{%- when Type::Timestamp %}
+{% include "TimestampHelper.kt" %}
+
+{%- when Type::Duration %}
+{% include "DurationHelper.kt" %}
+
+{%- when Type::Custom { module_path, name, builtin } %}
+{% include "CustomTypeTemplate.kt" %}
+
+{%- when Type::External { module_path, name, namespace, kind, tagged } %}
+{% include "ExternalTypeTemplate.kt" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
+
+{%- if ci.has_async_fns() %}
+{# Import types needed for async support #}
+{{ self.add_import("kotlin.coroutines.resume") }}
+{{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }}
+{{ self.add_import("kotlinx.coroutines.CancellableContinuation") }}
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
new file mode 100644
index 0000000000..279a8fa91b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUShort: FfiConverter<UShort, Short> {
+ 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) = 2
+
+ override fun write(value: UShort, buf: ByteBuffer) {
+ buf.putShort(value.toShort())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
new file mode 100644
index 0000000000..da7b5b28d6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUInt: FfiConverter<UInt, Int> {
+ 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) = 4
+
+ override fun write(value: UInt, buf: ByteBuffer) {
+ buf.putInt(value.toInt())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
new file mode 100644
index 0000000000..44d27ad36b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterULong: FfiConverter<ULong, Long> {
+ 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) = 8
+
+ override fun write(value: ULong, buf: ByteBuffer) {
+ buf.putLong(value.toLong())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
new file mode 100644
index 0000000000..b6d176603e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUByte: FfiConverter<UByte, Byte> {
+ 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) = 1
+
+ override fun write(value: UByte, buf: ByteBuffer) {
+ buf.put(value.toByte())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
new file mode 100644
index 0000000000..6a95d6a66d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
@@ -0,0 +1,77 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `arg_list_lowered`
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name(ci) }})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status)
+}
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) %}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name(ci) }})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}(
+ {{- prefix }},
+ {% call arg_list_lowered(func) %}
+ _status)
+}
+{%- endmacro %}
+
+{%- macro arg_list_lowered(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg|lower_fn }}({{ arg.name()|var_name }}),
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in kotlin declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{% macro arg_list_protocol(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+{#-
+// Arglist as used in the _UniFFILib function declarations.
+// Note unfiltered name but ffi_type_name filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value -}},
+ {%- endfor %}
+ {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %}
+{%- endmacro -%}
+
+// Macro for destroying fields
+{%- macro destroy_fields(member) %}
+ Disposable.destroy(
+ {%- for field in member.fields() %}
+ this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%}
+ {% endfor -%})
+{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
new file mode 100644
index 0000000000..9ee4229018
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
@@ -0,0 +1,57 @@
+// 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 {{ config.package_name() }};
+
+// 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.ConcurrentHashMap
+
+{%- for req in self.imports() %}
+{{ req.render() }}
+{%- endfor %}
+
+{% include "RustBufferTemplate.kt" %}
+{% include "FfiConverterTemplate.kt" %}
+{% include "Helpers.kt" %}
+
+// Contains loading, initialization code,
+// and the FFI Function declarations in a com.sun.jna.Library.
+{% include "NamespaceLibraryTemplate.kt" %}
+
+// Async support
+{%- if ci.has_async_fns() %}
+{% include "Async.kt" %}
+{%- endif %}
+
+// Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.kt" %}
+{%- endfor %}
+
+{% import "macros.kt" as kt %}