diff --git a/library/src/main/java/libxmtp-version.txt b/library/src/main/java/libxmtp-version.txt index 1c1f9fb3..f1245271 100644 --- a/library/src/main/java/libxmtp-version.txt +++ b/library/src/main/java/libxmtp-version.txt @@ -1,3 +1,3 @@ -Version: 3103e7b9 +Version: 339483f3 Branch: main -Date: 2024-12-05 00:24:03 +0000 +Date: 2024-12-16 21:37:35 +0000 diff --git a/library/src/main/java/org/xmtp/android/library/Conversations.kt b/library/src/main/java/org/xmtp/android/library/Conversations.kt index 3c4fcbe8..470119c8 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversations.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversations.kt @@ -129,7 +129,8 @@ data class Conversations( // Sync all new and existing conversations data from the network suspend fun syncAllConversations(): UInt { - return ffiConversations.syncAllConversations() + // TODO: add consentState here + return ffiConversations.syncAllConversations(null) } suspend fun newConversation(peerAddress: String): Conversation { diff --git a/library/src/main/java/xmtpv3.kt b/library/src/main/java/xmtpv3.kt index 8ab3d5ce..53217545 100644 --- a/library/src/main/java/xmtpv3.kt +++ b/library/src/main/java/xmtpv3.kt @@ -52,8 +52,10 @@ open class RustBuffer : Structure() { // 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 @@ -140,6 +142,7 @@ class RustBufferByReference : ByReference(16) { internal open class ForeignBytes : Structure() { @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null @@ -236,6 +239,7 @@ internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() internal open class UniffiRustCallStatus : Structure() { @JvmField var code: Byte = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() @@ -780,6 +784,24 @@ internal interface UniffiCallbackInterfaceFfiMessageCallbackMethod1 : com.sun.jn ) } +internal interface UniffiCallbackInterfaceFfiPreferenceCallbackMethod0 : com.sun.jna.Callback { + fun callback( + `uniffiHandle`: Long, + `preference`: RustBuffer.ByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) +} + +internal interface UniffiCallbackInterfaceFfiPreferenceCallbackMethod1 : com.sun.jna.Callback { + fun callback( + `uniffiHandle`: Long, + `error`: RustBuffer.ByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) +} + internal interface UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0 : com.sun.jna.Callback { fun callback( `uniffiHandle`: Long, @@ -888,6 +910,30 @@ internal open class UniffiVTableCallbackInterfaceFfiMessageCallback( } +@Structure.FieldOrder("onPreferenceUpdate", "onError", "uniffiFree") +internal open class UniffiVTableCallbackInterfaceFfiPreferenceCallback( + @JvmField internal var `onPreferenceUpdate`: UniffiCallbackInterfaceFfiPreferenceCallbackMethod0? = null, + @JvmField internal var `onError`: UniffiCallbackInterfaceFfiPreferenceCallbackMethod1? = null, + @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null, +) : Structure() { + class UniffiByValue( + `onPreferenceUpdate`: UniffiCallbackInterfaceFfiPreferenceCallbackMethod0? = null, + `onError`: UniffiCallbackInterfaceFfiPreferenceCallbackMethod1? = null, + `uniffiFree`: UniffiCallbackInterfaceFree? = null, + ) : UniffiVTableCallbackInterfaceFfiPreferenceCallback( + `onPreferenceUpdate`, + `onError`, + `uniffiFree`, + ), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceFfiPreferenceCallback) { + `onPreferenceUpdate` = other.`onPreferenceUpdate` + `onError` = other.`onError` + `uniffiFree` = other.`uniffiFree` + } + +} + @Structure.FieldOrder("onMessage", "onError", "uniffiFree") internal open class UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback( @JvmField internal var `onMessage`: UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0? = null, @@ -926,6 +972,7 @@ internal interface UniffiLib : Library { uniffiCallbackInterfaceFfiConsentCallback.register(lib) uniffiCallbackInterfaceFfiConversationCallback.register(lib) uniffiCallbackInterfaceFfiMessageCallback.register(lib) + uniffiCallbackInterfaceFfiPreferenceCallback.register(lib) uniffiCallbackInterfaceFfiV2SubscriptionCallback.register(lib) uniffiCallbackInterfaceFfiInboxOwner.register(lib) } @@ -1224,12 +1271,20 @@ internal interface UniffiLib : Library { `ptr`: Pointer, `callback`: Pointer, ): Long + fun uniffi_xmtpv3_fn_method_fficonversations_stream_messages( + `ptr`: Pointer, `messageCallback`: Pointer, `conversationType`: RustBuffer.ByValue, + ): Long + + fun uniffi_xmtpv3_fn_method_fficonversations_stream_preferences( + `ptr`: Pointer, `callback`: Pointer, + ): Long + fun uniffi_xmtpv3_fn_method_fficonversations_sync( `ptr`: Pointer, ): Long fun uniffi_xmtpv3_fn_method_fficonversations_sync_all_conversations( - `ptr`: Pointer, + `ptr`: Pointer, `consentState`: RustBuffer.ByValue, ): Long fun uniffi_xmtpv3_fn_clone_ffigrouppermissions( @@ -1268,6 +1323,26 @@ internal interface UniffiLib : Library { `ptr`: Pointer, `error`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): Unit + fun uniffi_xmtpv3_fn_clone_ffipreferencecallback( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): Pointer + + fun uniffi_xmtpv3_fn_free_ffipreferencecallback( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_xmtpv3_fn_init_callback_vtable_ffipreferencecallback( + `vtable`: UniffiVTableCallbackInterfaceFfiPreferenceCallback, + ): Unit + + fun uniffi_xmtpv3_fn_method_ffipreferencecallback_on_preference_update( + `ptr`: Pointer, `preference`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_xmtpv3_fn_method_ffipreferencecallback_on_error( + `ptr`: Pointer, `error`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_xmtpv3_fn_clone_ffisignaturerequest( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer @@ -1440,6 +1515,10 @@ internal interface UniffiLib : Library { `ptr`: Pointer, `entityType`: RustBuffer.ByValue, `entity`: RustBuffer.ByValue, ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_get_hmac_keys( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_method_ffixmtpclient_get_latest_inbox_state( `ptr`: Pointer, `inboxId`: RustBuffer.ByValue, ): Long @@ -1456,10 +1535,6 @@ internal interface UniffiLib : Library { `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_xmtpv3_fn_method_ffixmtpclient_maybe_start_sync_worker( - `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, - ): Unit - fun uniffi_xmtpv3_fn_method_ffixmtpclient_message( `ptr`: Pointer, `messageId`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue @@ -2054,6 +2129,12 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_fficonversations_stream_groups( ): Short + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_messages( + ): Short + + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_preferences( + ): Short + fun uniffi_xmtpv3_checksum_method_fficonversations_sync( ): Short @@ -2072,6 +2153,12 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffimessagecallback_on_error( ): Short + fun uniffi_xmtpv3_checksum_method_ffipreferencecallback_on_preference_update( + ): Short + + fun uniffi_xmtpv3_checksum_method_ffipreferencecallback_on_error( + ): Short + fun uniffi_xmtpv3_checksum_method_ffisignaturerequest_add_ecdsa_signature( ): Short @@ -2159,6 +2246,9 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffixmtpclient_get_consent_state( ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_get_hmac_keys( + ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_get_latest_inbox_state( ): Short @@ -2171,9 +2261,6 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffixmtpclient_installation_id( ): Short - fun uniffi_xmtpv3_checksum_method_ffixmtpclient_maybe_start_sync_worker( - ): Short - fun uniffi_xmtpv3_checksum_method_ffixmtpclient_message( ): Short @@ -2446,7 +2533,7 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_all_messages() != 63519.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_consent() != 12642.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_consent() != 27123.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_dms() != 52710.toShort()) { @@ -2455,10 +2542,16 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_groups() != 11064.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_messages() != 30183.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_preferences() != 37452.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync() != 9054.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync_all_conversations() != 1140.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync_all_conversations() != 2613.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_ffigrouppermissions_policy_set() != 24928.toShort()) { @@ -2473,6 +2566,12 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffimessagecallback_on_error() != 32204.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffipreferencecallback_on_preference_update() != 19900.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_ffipreferencecallback_on_error() != 41454.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffisignaturerequest_add_ecdsa_signature() != 8706.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2560,6 +2659,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_get_consent_state() != 58208.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_get_hmac_keys() != 36015.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_get_latest_inbox_state() != 3165.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2572,9 +2674,6 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_installation_id() != 37173.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_maybe_start_sync_worker() != 56811.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_message() != 26932.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -5183,15 +5282,30 @@ public interface FfiConversationsInterface { suspend fun `streamAllMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser + /** + * Get notified when there is a new consent update either locally or is synced from another device + * allowing the user to re-render the new state appropriately + */ suspend fun `streamConsent`(`callback`: FfiConsentCallback): FfiStreamCloser suspend fun `streamDms`(`callback`: FfiConversationCallback): FfiStreamCloser suspend fun `streamGroups`(`callback`: FfiConversationCallback): FfiStreamCloser + suspend fun `streamMessages`( + `messageCallback`: FfiMessageCallback, + `conversationType`: FfiConversationType?, + ): FfiStreamCloser + + /** + * Get notified when a preference changes either locally or is synced from another device + * allowing the user to re-render the new state appropriately. + */ + suspend fun `streamPreferences`(`callback`: FfiPreferenceCallback): FfiStreamCloser + suspend fun `sync`() - suspend fun `syncAllConversations`(): kotlin.UInt + suspend fun `syncAllConversations`(`consentState`: FfiConsentState?): kotlin.UInt companion object } @@ -5612,6 +5726,10 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa } + /** + * Get notified when there is a new consent update either locally or is synced from another device + * allowing the user to re-render the new state appropriately + */ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun `streamConsent`(`callback`: FfiConsentCallback): FfiStreamCloser { return uniffiRustCallAsync( @@ -5705,6 +5823,76 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa } + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `streamMessages`( + `messageCallback`: FfiMessageCallback, + `conversationType`: FfiConversationType?, + ): FfiStreamCloser { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_messages( + thisPtr, + FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), + FfiConverterOptionalTypeFfiConversationType.lower(`conversationType`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + // lift function + { FfiConverterTypeFfiStreamCloser.lift(it) }, + // Error FFI converter + UniffiNullRustCallStatusErrorHandler, + ) + } + + + /** + * Get notified when a preference changes either locally or is synced from another device + * allowing the user to re-render the new state appropriately. + */ + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `streamPreferences`(`callback`: FfiPreferenceCallback): FfiStreamCloser { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_preferences( + thisPtr, + FfiConverterTypeFfiPreferenceCallback.lower(`callback`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + // lift function + { FfiConverterTypeFfiStreamCloser.lift(it) }, + // Error FFI converter + UniffiNullRustCallStatusErrorHandler, + ) + } + + @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun `sync`() { @@ -5740,13 +5928,13 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `syncAllConversations`(): kotlin.UInt { + override suspend fun `syncAllConversations`(`consentState`: FfiConsentState?): kotlin.UInt { return uniffiRustCallAsync( callWithPointer { thisPtr -> UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_sync_all_conversations( thisPtr, - - ) + FfiConverterOptionalTypeFfiConsentState.lower(`consentState`), + ) }, { future, callback, continuation -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_u32( @@ -6451,30 +6639,16 @@ public object FfiConverterTypeFfiMessageCallback : FfiConverter + fun `onPreferenceUpdate`(`preference`: List) - suspend fun `signatureText`(): kotlin.String + fun `onError`(`error`: FfiSubscribeException) companion object } -open class FfiSignatureRequest : Disposable, AutoCloseable, FfiSignatureRequestInterface { +open class FfiPreferenceCallbackImpl : Disposable, AutoCloseable, FfiPreferenceCallback { constructor(pointer: Pointer) { this.pointer = pointer @@ -6543,7 +6717,7 @@ open class FfiSignatureRequest : Disposable, AutoCloseable, FfiSignatureRequestI override fun run() { pointer?.let { ptr -> uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffisignaturerequest(ptr, status) + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffipreferencecallback(ptr, status) } } } @@ -6551,49 +6725,363 @@ open class FfiSignatureRequest : Disposable, AutoCloseable, FfiSignatureRequestI fun uniffiClonePointer(): Pointer { return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffisignaturerequest(pointer!!, status) + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffipreferencecallback(pointer!!, status) } } + override fun `onPreferenceUpdate`(`preference`: List) = + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffipreferencecallback_on_preference_update( + it, FfiConverterSequenceTypeFfiPreferenceUpdate.lower(`preference`), _status + ) + } + } + - @Throws(GenericException::class) - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `addEcdsaSignature`(`signatureBytes`: kotlin.ByteArray) { - return uniffiRustCallAsync( - callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffisignaturerequest_add_ecdsa_signature( - thisPtr, - FfiConverterByteArray.lower(`signatureBytes`), + override fun `onError`(`error`: FfiSubscribeException) = + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffipreferencecallback_on_error( + it, FfiConverterTypeFfiSubscribeError.lower(`error`), _status ) - }, - { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( - future, - callback, - continuation + } + } + + + companion object + +} + + +// Put the implementation in an object so we don't pollute the top-level namespace +internal object uniffiCallbackInterfaceFfiPreferenceCallback { + internal object `onPreferenceUpdate` : UniffiCallbackInterfaceFfiPreferenceCallbackMethod0 { + override fun callback( + `uniffiHandle`: Long, + `preference`: RustBuffer.ByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) { + val uniffiObj = FfiConverterTypeFfiPreferenceCallback.handleMap.get(uniffiHandle) + val makeCall = { -> + uniffiObj.`onPreferenceUpdate`( + FfiConverterSequenceTypeFfiPreferenceUpdate.lift(`preference`), ) - }, - { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( - future, - continuation + } + val writeReturn = { _: Unit -> Unit } + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + } + } + + internal object `onError` : UniffiCallbackInterfaceFfiPreferenceCallbackMethod1 { + override fun callback( + `uniffiHandle`: Long, + `error`: RustBuffer.ByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) { + val uniffiObj = FfiConverterTypeFfiPreferenceCallback.handleMap.get(uniffiHandle) + val makeCall = { -> + uniffiObj.`onError`( + FfiConverterTypeFfiSubscribeError.lift(`error`), ) - }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, - // lift function - { Unit }, + } + val writeReturn = { _: Unit -> Unit } + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + } + } - // Error FFI converter - GenericException.ErrorHandler, - ) + internal object uniffiFree : UniffiCallbackInterfaceFree { + override fun callback(handle: Long) { + FfiConverterTypeFfiPreferenceCallback.handleMap.remove(handle) + } } + internal var vtable = UniffiVTableCallbackInterfaceFfiPreferenceCallback.UniffiByValue( + `onPreferenceUpdate`, + `onError`, + uniffiFree, + ) - @Throws(GenericException::class) - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `addScwSignature`( - `signatureBytes`: kotlin.ByteArray, - `address`: kotlin.String, + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal fun register(lib: UniffiLib) { + lib.uniffi_xmtpv3_fn_init_callback_vtable_ffipreferencecallback(vtable) + } +} + +/** + * @suppress + */ +public object FfiConverterTypeFfiPreferenceCallback : FfiConverter { + internal val handleMap = UniffiHandleMap() + + override fun lower(value: FfiPreferenceCallback): Pointer { + return Pointer(handleMap.insert(value)) + } + + override fun lift(value: Pointer): FfiPreferenceCallback { + return FfiPreferenceCallbackImpl(value) + } + + override fun read(buf: ByteBuffer): FfiPreferenceCallback { + // 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: FfiPreferenceCallback) = 8UL + + override fun write(value: FfiPreferenceCallback, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface FfiSignatureRequestInterface { + + suspend fun `addEcdsaSignature`(`signatureBytes`: kotlin.ByteArray) + + suspend fun `addScwSignature`( + `signatureBytes`: kotlin.ByteArray, + `address`: kotlin.String, + `chainId`: kotlin.ULong, + `blockNumber`: kotlin.ULong?, + ) + + suspend fun `isReady`(): kotlin.Boolean + + /** + * missing signatures that are from [MemberKind::Address] + */ + suspend fun `missingAddressSignatures`(): List + + suspend fun `signatureText`(): kotlin.String + + companion object +} + +open class FfiSignatureRequest : Disposable, AutoCloseable, FfiSignatureRequestInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (!this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffisignaturerequest(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffisignaturerequest(pointer!!, status) + } + } + + + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `addEcdsaSignature`(`signatureBytes`: kotlin.ByteArray) { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffisignaturerequest_add_ecdsa_signature( + thisPtr, + FfiConverterByteArray.lower(`signatureBytes`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, + + // Error FFI converter + GenericException.ErrorHandler, + ) + } + + + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `addScwSignature`( + `signatureBytes`: kotlin.ByteArray, + `address`: kotlin.String, `chainId`: kotlin.ULong, `blockNumber`: kotlin.ULong?, ) { @@ -8229,6 +8717,8 @@ public interface FfiXmtpClientInterface { `entity`: kotlin.String, ): FfiConsentState + fun `getHmacKeys`(): List + suspend fun `getLatestInboxState`(`inboxId`: kotlin.String): FfiInboxState fun `inboxId`(): kotlin.String @@ -8243,11 +8733,6 @@ public interface FfiXmtpClientInterface { fun `installationId`(): kotlin.ByteArray - /** - * Starts the sync worker if the history sync url is present. - */ - fun `maybeStartSyncWorker`() - fun `message`(`messageId`: kotlin.ByteArray): FfiMessage suspend fun `registerIdentity`(`signatureRequest`: FfiSignatureRequest) @@ -8652,6 +9137,20 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { } + @Throws(GenericException::class) + override fun `getHmacKeys`(): List { + return FfiConverterSequenceTypeFfiHmacKey.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_get_hmac_keys( + it, _status + ) + } + } + ) + } + + @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun `getLatestInboxState`(`inboxId`: kotlin.String): FfiInboxState { @@ -8746,19 +9245,6 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { } - /** - * Starts the sync worker if the history sync url is present. - */ - override fun `maybeStartSyncWorker`() = - callWithPointer { - uniffiRustCall() { _status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_maybe_start_sync_worker( - it, _status - ) - } - } - - @Throws(GenericException::class) override fun `message`(`messageId`: kotlin.ByteArray): FfiMessage { return FfiConverterTypeFfiMessage.lift( @@ -9242,6 +9728,37 @@ public object FfiConverterTypeFfiEnvelope : FfiConverterRustBuffer } +data class FfiHmacKey( + var `key`: kotlin.ByteArray, + var `epoch`: kotlin.Long, +) { + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeFfiHmacKey : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FfiHmacKey { + return FfiHmacKey( + FfiConverterByteArray.read(buf), + FfiConverterLong.read(buf), + ) + } + + override fun allocationSize(value: FfiHmacKey) = ( + FfiConverterByteArray.allocationSize(value.`key`) + + FfiConverterLong.allocationSize(value.`epoch`) + ) + + override fun write(value: FfiHmacKey, buf: ByteBuffer) { + FfiConverterByteArray.write(value.`key`, buf) + FfiConverterLong.write(value.`epoch`, buf) + } +} + + data class FfiInboxState( var `inboxId`: kotlin.String, var `recoveryAddress`: kotlin.String, @@ -10061,6 +10578,54 @@ public object FfiConverterTypeFfiPermissionUpdateType : } +sealed class FfiPreferenceUpdate { + + data class Hmac( + val `key`: kotlin.ByteArray, + ) : FfiPreferenceUpdate() { + companion object + } + + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeFfiPreferenceUpdate : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FfiPreferenceUpdate { + return when (buf.getInt()) { + 1 -> FfiPreferenceUpdate.Hmac( + FfiConverterByteArray.read(buf), + ) + + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: FfiPreferenceUpdate) = when (value) { + is FfiPreferenceUpdate.Hmac -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4UL + + FfiConverterByteArray.allocationSize(value.`key`) + ) + } + } + + override fun write(value: FfiPreferenceUpdate, buf: ByteBuffer) { + when (value) { + is FfiPreferenceUpdate.Hmac -> { + buf.putInt(1) + FfiConverterByteArray.write(value.`key`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + enum class FfiSortDirection { UNSPECIFIED, @@ -10093,6 +10658,8 @@ sealed class FfiSubscribeException(message: String) : kotlin.Exception(message) class Subscribe(message: String) : FfiSubscribeException(message) + class Storage(message: String) : FfiSubscribeException(message) + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { override fun lift(error_buf: RustBuffer.ByValue): FfiSubscribeException = @@ -10108,6 +10675,7 @@ public object FfiConverterTypeFfiSubscribeError : FfiConverterRustBuffer FfiSubscribeException.Subscribe(FfiConverterString.read(buf)) + 2 -> FfiSubscribeException.Storage(FfiConverterString.read(buf)) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } @@ -10123,6 +10691,11 @@ public object FfiConverterTypeFfiSubscribeError : FfiConverterRustBuffer { + buf.putInt(2) + Unit + } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } @@ -10684,6 +11257,37 @@ public object FfiConverterOptionalTypeFfiConsentState : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FfiConversationType? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeFfiConversationType.read(buf) + } + + override fun allocationSize(value: FfiConversationType?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeFfiConversationType.allocationSize(value) + } + } + + override fun write(value: FfiConversationType?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeFfiConversationType.write(value, buf) + } + } +} + + /** * @suppress */ @@ -10965,6 +11569,32 @@ public object FfiConverterSequenceTypeFfiEnvelope : FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeFfiHmacKey.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.map { FfiConverterTypeFfiHmacKey.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeFfiHmacKey.write(it, buf) + } + } +} + + /** * @suppress */ @@ -11098,6 +11728,34 @@ public object FfiConverterSequenceTypeFfiV2QueryResponse : } +/** + * @suppress + */ +public object FfiConverterSequenceTypeFfiPreferenceUpdate : + FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypeFfiPreferenceUpdate.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = + value.map { FfiConverterTypeFfiPreferenceUpdate.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypeFfiPreferenceUpdate.write(it, buf) + } + } +} + + /** * @suppress */ @@ -11286,7 +11944,8 @@ suspend fun `getInboxIdForAddress`( UniffiLib.INSTANCE.uniffi_xmtpv3_fn_func_get_inbox_id_for_address( FfiConverterString.lower( `host` - ), FfiConverterBoolean.lower(`isSecure`), FfiConverterString.lower(`accountAddress`), + ), + FfiConverterBoolean.lower(`isSecure`), FfiConverterString.lower(`accountAddress`), ), { future, callback, continuation -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer( diff --git a/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so b/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so index 7d87e3d9..32c730dc 100644 Binary files a/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so b/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so index b82868d3..8eceed03 100644 Binary files a/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so b/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so index aa4eb251..510f8b67 100644 Binary files a/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so b/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so index 1983c289..462904bf 100644 Binary files a/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so differ