commit 6a196b1ff23563d2b58662ed091f85208a995307
parent a752e21babfaf382383b5e1f3b7005d7890d64a0
Author: triesap <tyson@radroots.org>
Date: Mon, 3 Aug 2026 13:45:00 +0000
ios: regenerate sdk ffi bindings
- Add a governed UniFFI Swift generator routed through the private SDK FFI crate.
- Commit Swift source, C module artifacts, and an exact source-and-output lock.
- Enforce generated binding freshness through the SDK architecture gate.
- Verify repeat generation, Swift typechecking, xtask tests, and locked builds.
Diffstat:
11 files changed, 2116 insertions(+), 31 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -45,6 +45,12 @@ dependencies = [
]
[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
name = "anyhow"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -83,7 +89,7 @@ dependencies = [
"rustc-hash",
"serde",
"serde_derive",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -106,7 +112,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -382,6 +388,45 @@ dependencies = [
]
[[package]]
+name = "clap"
+version = "4.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -508,7 +553,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -559,7 +604,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
- "syn",
+ "syn 2.0.117",
"toml 0.8.23",
]
@@ -571,7 +616,7 @@ checksum = "c3cb9aafc704e13c470498fb9c351353af71c759ba810239bfb82f2d69658934"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -1647,7 +1692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2070,7 +2115,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
- "syn",
+ "syn 2.0.117",
"tar",
"tempfile",
"toml 0.8.23",
@@ -2448,7 +2493,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2544,7 +2589,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2724,7 +2769,7 @@ dependencies = [
"quote",
"sqlx-core",
"sqlx-macros-core",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2745,7 +2790,7 @@ dependencies = [
"sha2",
"sqlx-core",
"sqlx-sqlite",
- "syn",
+ "syn 2.0.117",
"tokio",
"url",
]
@@ -2787,6 +2832,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2804,6 +2855,17 @@ dependencies = [
]
[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2820,7 +2882,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2882,7 +2944,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2893,7 +2955,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2944,7 +3006,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -3111,7 +3173,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -3194,7 +3256,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f"
dependencies = [
"anyhow",
+ "camino",
"cargo_metadata",
+ "clap",
"uniffi_bindgen",
"uniffi_core",
"uniffi_macros",
@@ -3249,7 +3313,7 @@ dependencies = [
"indexmap",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -3264,7 +3328,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
- "syn",
+ "syn 2.0.117",
"toml 0.5.11",
"uniffi_meta",
]
@@ -3468,7 +3532,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"wasm-bindgen-shared",
]
@@ -3511,7 +3575,7 @@ checksum = "ee997551ce1ad5adda03f7ce37ec34b4140fe9f547fd07b46d55901d1ba1a06b"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -3653,7 +3717,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -3664,7 +3728,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -3892,7 +3956,7 @@ dependencies = [
"heck",
"indexmap",
"prettyplease",
- "syn",
+ "syn 2.0.117",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
@@ -3908,7 +3972,7 @@ dependencies = [
"prettyplease",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"wit-bindgen-core",
"wit-bindgen-rust",
]
@@ -3985,7 +4049,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"synstructure",
]
@@ -4006,7 +4070,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -4026,7 +4090,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"synstructure",
]
@@ -4066,7 +4130,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
diff --git a/crates/ffi/Cargo.toml b/crates/ffi/Cargo.toml
@@ -14,10 +14,14 @@ readme = "README.md"
[lib]
crate-type = ["lib", "staticlib", "cdylib"]
+[[bin]]
+name = "radroots-sdk-bindgen"
+path = "src/bin/bindgen.rs"
+
[dependencies]
radroots_sdk = { workspace = true, features = ["memory"] }
thiserror = { workspace = true }
-uniffi = { workspace = true }
+uniffi = { workspace = true, features = ["cli"] }
[dev-dependencies]
tokio = { workspace = true, features = ["macros", "rt"] }
diff --git a/crates/ffi/src/bin/bindgen.rs b/crates/ffi/src/bin/bindgen.rs
@@ -0,0 +1,3 @@
+fn main() {
+ uniffi::uniffi_bindgen_main();
+}
diff --git a/docs/platform/swift.md b/docs/platform/swift.md
@@ -0,0 +1,14 @@
+# Swift SDK FFI bindings
+
+`radroots_sdk_ffi` is the private Rust source of truth. Generate Swift source,
+the C header/module map, and `source.lock` from the SDK root with:
+
+```sh
+cargo extbuild run -- cargo xtask generate bindings swift
+```
+
+The generator builds the FFI library under the extbuild-owned target root,
+extracts UniFFI metadata, writes deterministic text artifacts under
+`generated/swift`, and binds every output hash to the exact FFI source hash.
+Mobile keychain prompts, background execution, scheduling, and presentation
+models are deliberately absent and remain owned by the Apple host.
diff --git a/generated/swift/radroots_sdk.swift b/generated/swift/radroots_sdk.swift
@@ -0,0 +1,1111 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+
+// swiftlint:disable all
+import Foundation
+
+// Depending on the consumer's build setup, the low-level FFI code
+// might be in a separate module, or it might be compiled inline into
+// this module. This is a bit of light hackery to work with both.
+#if canImport(radroots_sdkFFI)
+import radroots_sdkFFI
+#endif
+
+fileprivate extension RustBuffer {
+ // Allocate a new buffer, copying the contents of a `UInt8` array.
+ init(bytes: [UInt8]) {
+ let rbuf = bytes.withUnsafeBufferPointer { ptr in
+ RustBuffer.from(ptr)
+ }
+ self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
+ }
+
+ static func empty() -> RustBuffer {
+ RustBuffer(capacity: 0, len:0, data: nil)
+ }
+
+ static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer {
+ try! rustCall { ffi_radroots_sdk_ffi_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) }
+ }
+
+ // Frees the buffer in place.
+ // The buffer must not be used after this is called.
+ func deallocate() {
+ try! rustCall { ffi_radroots_sdk_ffi_rustbuffer_free(self, $0) }
+ }
+}
+
+fileprivate extension ForeignBytes {
+ init(bufferPointer: UnsafeBufferPointer<UInt8>) {
+ self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress)
+ }
+}
+
+// For every type used in the interface, we provide helper methods for conveniently
+// lifting and lowering that type from C-compatible data, and for reading and writing
+// values of that type in a buffer.
+
+// Helper classes/extensions that don't change.
+// Someday, this will be in a library of its own.
+
+fileprivate extension Data {
+ init(rustBuffer: RustBuffer) {
+ self.init(
+ bytesNoCopy: rustBuffer.data!,
+ count: Int(rustBuffer.len),
+ deallocator: .none
+ )
+ }
+}
+
+// Define reader functionality. Normally this would be defined in a class or
+// struct, but we use standalone functions instead in order to make external
+// types work.
+//
+// With external types, one swift source file needs to be able to call the read
+// method on another source file's FfiConverter, but then what visibility
+// should Reader have?
+// - If Reader is fileprivate, then this means the read() must also
+// be fileprivate, which doesn't work with external types.
+// - If Reader is internal/public, we'll get compile errors since both source
+// files will try define the same type.
+//
+// Instead, the read() method and these helper functions input a tuple of data
+
+fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) {
+ (data: data, offset: 0)
+}
+
+// Reads an integer at the current offset, in big-endian order, and advances
+// the offset on success. Throws if reading the integer would move the
+// offset past the end of the buffer.
+fileprivate func readInt<T: FixedWidthInteger>(_ reader: inout (data: Data, offset: Data.Index)) throws -> T {
+ let range = reader.offset..<reader.offset + MemoryLayout<T>.size
+ guard reader.data.count >= range.upperBound else {
+ throw UniffiInternalError.bufferOverflow
+ }
+ if T.self == UInt8.self {
+ let value = reader.data[reader.offset]
+ reader.offset += 1
+ return value as! T
+ }
+ var value: T = 0
+ let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)})
+ reader.offset = range.upperBound
+ return value.bigEndian
+}
+
+// Reads an arbitrary number of bytes, to be used to read
+// raw bytes, this is useful when lifting strings
+fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array<UInt8> {
+ let range = reader.offset..<(reader.offset+count)
+ guard reader.data.count >= range.upperBound else {
+ throw UniffiInternalError.bufferOverflow
+ }
+ var value = [UInt8](repeating: 0, count: count)
+ value.withUnsafeMutableBufferPointer({ buffer in
+ reader.data.copyBytes(to: buffer, from: range)
+ })
+ reader.offset = range.upperBound
+ return value
+}
+
+// Reads a float at the current offset.
+fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float {
+ return Float(bitPattern: try readInt(&reader))
+}
+
+// Reads a float at the current offset.
+fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double {
+ return Double(bitPattern: try readInt(&reader))
+}
+
+// Indicates if the offset has reached the end of the buffer.
+fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool {
+ return reader.offset < reader.data.count
+}
+
+// Define writer functionality. Normally this would be defined in a class or
+// struct, but we use standalone functions instead in order to make external
+// types work. See the above discussion on Readers for details.
+
+fileprivate func createWriter() -> [UInt8] {
+ return []
+}
+
+fileprivate func writeBytes<S>(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 {
+ writer.append(contentsOf: byteArr)
+}
+
+// Writes an integer in big-endian order.
+//
+// Warning: make sure what you are trying to write
+// is in the correct type!
+fileprivate func writeInt<T: FixedWidthInteger>(_ writer: inout [UInt8], _ value: T) {
+ var value = value.bigEndian
+ withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) }
+}
+
+fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) {
+ writeInt(&writer, value.bitPattern)
+}
+
+fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) {
+ writeInt(&writer, value.bitPattern)
+}
+
+// Protocol for types that transfer other types across the FFI. This is
+// analogous to the Rust trait of the same name.
+fileprivate protocol FfiConverter {
+ associatedtype FfiType
+ associatedtype SwiftType
+
+ static func lift(_ value: FfiType) throws -> SwiftType
+ static func lower(_ value: SwiftType) -> FfiType
+ static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType
+ static func write(_ value: SwiftType, into buf: inout [UInt8])
+}
+
+// Types conforming to `Primitive` pass themselves directly over the FFI.
+fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { }
+
+extension FfiConverterPrimitive {
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ public static func lift(_ value: FfiType) throws -> SwiftType {
+ return value
+ }
+
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ public static func lower(_ value: SwiftType) -> FfiType {
+ return value
+ }
+}
+
+// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`.
+// Used for complex types where it's hard to write a custom lift/lower.
+fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {}
+
+extension FfiConverterRustBuffer {
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ public static func lift(_ buf: RustBuffer) throws -> SwiftType {
+ var reader = createReader(data: Data(rustBuffer: buf))
+ let value = try read(from: &reader)
+ if hasRemaining(reader) {
+ throw UniffiInternalError.incompleteData
+ }
+ buf.deallocate()
+ return value
+ }
+
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ public static func lower(_ value: SwiftType) -> RustBuffer {
+ var writer = createWriter()
+ write(value, into: &writer)
+ return RustBuffer(bytes: writer)
+ }
+}
+// An error type for FFI errors. These errors occur at the UniFFI level, not
+// the library level.
+fileprivate enum UniffiInternalError: LocalizedError {
+ case bufferOverflow
+ case incompleteData
+ case unexpectedOptionalTag
+ case unexpectedEnumCase
+ case unexpectedNullPointer
+ case unexpectedRustCallStatusCode
+ case unexpectedRustCallError
+ case unexpectedStaleHandle
+ case rustPanic(_ message: String)
+
+ public var errorDescription: String? {
+ switch self {
+ case .bufferOverflow: return "Reading the requested value would read past the end of the buffer"
+ case .incompleteData: return "The buffer still has data after lifting its containing value"
+ case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1"
+ case .unexpectedEnumCase: return "Raw enum value doesn't match any cases"
+ case .unexpectedNullPointer: return "Raw pointer value was null"
+ case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code"
+ case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified"
+ case .unexpectedStaleHandle: return "The object in the handle map has been dropped already"
+ case let .rustPanic(message): return message
+ }
+ }
+}
+
+fileprivate extension NSLock {
+ func withLock<T>(f: () throws -> T) rethrows -> T {
+ self.lock()
+ defer { self.unlock() }
+ return try f()
+ }
+}
+
+fileprivate let CALL_SUCCESS: Int8 = 0
+fileprivate let CALL_ERROR: Int8 = 1
+fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2
+fileprivate let CALL_CANCELLED: Int8 = 3
+
+fileprivate extension RustCallStatus {
+ init() {
+ self.init(
+ code: CALL_SUCCESS,
+ errorBuf: RustBuffer.init(
+ capacity: 0,
+ len: 0,
+ data: nil
+ )
+ )
+ }
+}
+
+private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T {
+ let neverThrow: ((RustBuffer) throws -> Never)? = nil
+ return try makeRustCall(callback, errorHandler: neverThrow)
+}
+
+private func rustCallWithError<T, E: Swift.Error>(
+ _ errorHandler: @escaping (RustBuffer) throws -> E,
+ _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T {
+ try makeRustCall(callback, errorHandler: errorHandler)
+}
+
+private func makeRustCall<T, E: Swift.Error>(
+ _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T,
+ errorHandler: ((RustBuffer) throws -> E)?
+) throws -> T {
+ uniffiEnsureRadrootsSdkFfiInitialized()
+ var callStatus = RustCallStatus.init()
+ let returnedVal = callback(&callStatus)
+ try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler)
+ return returnedVal
+}
+
+private func uniffiCheckCallStatus<E: Swift.Error>(
+ callStatus: RustCallStatus,
+ errorHandler: ((RustBuffer) throws -> E)?
+) throws {
+ switch callStatus.code {
+ case CALL_SUCCESS:
+ return
+
+ case CALL_ERROR:
+ if let errorHandler = errorHandler {
+ throw try errorHandler(callStatus.errorBuf)
+ } else {
+ callStatus.errorBuf.deallocate()
+ throw UniffiInternalError.unexpectedRustCallError
+ }
+
+ case CALL_UNEXPECTED_ERROR:
+ // 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 callStatus.errorBuf.len > 0 {
+ throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf))
+ } else {
+ callStatus.errorBuf.deallocate()
+ throw UniffiInternalError.rustPanic("Rust panic")
+ }
+
+ case CALL_CANCELLED:
+ fatalError("Cancellation not supported yet")
+
+ default:
+ throw UniffiInternalError.unexpectedRustCallStatusCode
+ }
+}
+
+private func uniffiTraitInterfaceCall<T>(
+ callStatus: UnsafeMutablePointer<RustCallStatus>,
+ makeCall: () throws -> T,
+ writeReturn: (T) -> ()
+) {
+ do {
+ try writeReturn(makeCall())
+ } catch let error {
+ callStatus.pointee.code = CALL_UNEXPECTED_ERROR
+ callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error))
+ }
+}
+
+private func uniffiTraitInterfaceCallWithError<T, E>(
+ callStatus: UnsafeMutablePointer<RustCallStatus>,
+ makeCall: () throws -> T,
+ writeReturn: (T) -> (),
+ lowerError: (E) -> RustBuffer
+) {
+ do {
+ try writeReturn(makeCall())
+ } catch let error as E {
+ callStatus.pointee.code = CALL_ERROR
+ callStatus.pointee.errorBuf = lowerError(error)
+ } catch {
+ callStatus.pointee.code = CALL_UNEXPECTED_ERROR
+ callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error))
+ }
+}
+fileprivate final class UniffiHandleMap<T>: @unchecked Sendable {
+ // All mutation happens with this lock held, which is why we implement @unchecked Sendable.
+ private let lock = NSLock()
+ private var map: [UInt64: T] = [:]
+ private var currentHandle: UInt64 = 1
+
+ func insert(obj: T) -> UInt64 {
+ lock.withLock {
+ let handle = currentHandle
+ currentHandle += 1
+ map[handle] = obj
+ return handle
+ }
+ }
+
+ func get(handle: UInt64) throws -> T {
+ try lock.withLock {
+ guard let obj = map[handle] else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return obj
+ }
+ }
+
+ @discardableResult
+ func remove(handle: UInt64) throws -> T {
+ try lock.withLock {
+ guard let obj = map.removeValue(forKey: handle) else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return obj
+ }
+ }
+
+ var count: Int {
+ get {
+ map.count
+ }
+ }
+}
+
+
+// Public interface members begin here.
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+fileprivate struct FfiConverterBool : FfiConverter {
+ typealias FfiType = Int8
+ typealias SwiftType = Bool
+
+ public static func lift(_ value: Int8) throws -> Bool {
+ return value != 0
+ }
+
+ public static func lower(_ value: Bool) -> Int8 {
+ return value ? 1 : 0
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Bool, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+fileprivate struct FfiConverterString: FfiConverter {
+ typealias SwiftType = String
+ typealias FfiType = RustBuffer
+
+ public static func lift(_ value: RustBuffer) throws -> String {
+ defer {
+ value.deallocate()
+ }
+ if value.data == nil {
+ return String()
+ }
+ let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len))
+ return String(bytes: bytes, encoding: String.Encoding.utf8)!
+ }
+
+ public static func lower(_ value: String) -> RustBuffer {
+ return value.utf8CString.withUnsafeBufferPointer { ptr in
+ // The swift string gives us int8_t, we want uint8_t.
+ ptr.withMemoryRebound(to: UInt8.self) { ptr in
+ // The swift string gives us a trailing null byte, we don't want it.
+ let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1))
+ return RustBuffer.from(buf)
+ }
+ }
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String {
+ let len: Int32 = try readInt(&buf)
+ return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)!
+ }
+
+ public static func write(_ value: String, into buf: inout [UInt8]) {
+ let len = Int32(value.utf8.count)
+ writeInt(&buf, len)
+ writeBytes(&buf, value.utf8)
+ }
+}
+
+
+
+
+/**
+ * Thread-safe mobile handle delegating lifecycle to [`radroots_sdk::Client`].
+ */
+public protocol MobileClientProtocol: AnyObject, Sendable {
+
+ /**
+ * Returns stable capability DTOs without probing host resources.
+ */
+ func capabilities() -> [CapabilityStatus]
+
+ /**
+ * Explicitly closes SDK-owned resources without installing a runtime.
+ */
+ func close() async throws
+
+ /**
+ * Returns whether explicit SDK close completed across all clones.
+ */
+ func isClosed() -> Bool
+
+}
+/**
+ * Thread-safe mobile handle delegating lifecycle to [`radroots_sdk::Client`].
+ */
+open class MobileClient: MobileClientProtocol, @unchecked Sendable {
+ fileprivate let pointer: UnsafeMutableRawPointer!
+
+ /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly.
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ public struct NoPointer {
+ public init() {}
+ }
+
+ // TODO: We'd like this to be `private` but for Swifty reasons,
+ // we can't implement `FfiConverter` without making this `required` and we can't
+ // make it `required` without making it `public`.
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
+ self.pointer = pointer
+ }
+
+ // This constructor can be used to instantiate a fake object.
+ // - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject].
+ //
+ // - Warning:
+ // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash.
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ public init(noPointer: NoPointer) {
+ self.pointer = nil
+ }
+
+#if swift(>=5.8)
+ @_documentation(visibility: private)
+#endif
+ public func uniffiClonePointer() -> UnsafeMutableRawPointer {
+ return try! rustCall { uniffi_radroots_sdk_ffi_fn_clone_mobileclient(self.pointer, $0) }
+ }
+ // No primary constructor declared for this class.
+
+ deinit {
+ guard let pointer = pointer else {
+ return
+ }
+
+ try! rustCall { uniffi_radroots_sdk_ffi_fn_free_mobileclient(pointer, $0) }
+ }
+
+
+ /**
+ * Creates a deterministic, local-only memory client without I/O.
+ */
+public static func memory()throws -> MobileClient {
+ return try FfiConverterTypeMobileClient_lift(try rustCallWithError(FfiConverterTypeError_lift) {
+ uniffi_radroots_sdk_ffi_fn_constructor_mobileclient_memory($0
+ )
+})
+}
+
+
+
+ /**
+ * Returns stable capability DTOs without probing host resources.
+ */
+open func capabilities() -> [CapabilityStatus] {
+ return try! FfiConverterSequenceTypeCapabilityStatus.lift(try! rustCall() {
+ uniffi_radroots_sdk_ffi_fn_method_mobileclient_capabilities(self.uniffiClonePointer(),$0
+ )
+})
+}
+
+ /**
+ * Explicitly closes SDK-owned resources without installing a runtime.
+ */
+open func close()async throws {
+ return
+ try await uniffiRustCallAsync(
+ rustFutureFunc: {
+ uniffi_radroots_sdk_ffi_fn_method_mobileclient_close(
+ self.uniffiClonePointer()
+
+ )
+ },
+ pollFunc: ffi_radroots_sdk_ffi_rust_future_poll_void,
+ completeFunc: ffi_radroots_sdk_ffi_rust_future_complete_void,
+ freeFunc: ffi_radroots_sdk_ffi_rust_future_free_void,
+ liftFunc: { $0 },
+ errorHandler: FfiConverterTypeError_lift
+ )
+}
+
+ /**
+ * Returns whether explicit SDK close completed across all clones.
+ */
+open func isClosed() -> Bool {
+ return try! FfiConverterBool.lift(try! rustCall() {
+ uniffi_radroots_sdk_ffi_fn_method_mobileclient_is_closed(self.uniffiClonePointer(),$0
+ )
+})
+}
+
+
+}
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public struct FfiConverterTypeMobileClient: FfiConverter {
+
+ typealias FfiType = UnsafeMutableRawPointer
+ typealias SwiftType = MobileClient
+
+ public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> MobileClient {
+ return MobileClient(unsafeFromRawPointer: pointer)
+ }
+
+ public static func lower(_ value: MobileClient) -> UnsafeMutableRawPointer {
+ return value.uniffiClonePointer()
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileClient {
+ let v: UInt64 = try readInt(&buf)
+ // The Rust code won't compile if a pointer won't fit in a UInt64.
+ // We have to go via `UInt` because that's the thing that's the size of a pointer.
+ let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v))
+ if (ptr == nil) {
+ throw UniffiInternalError.unexpectedNullPointer
+ }
+ return try lift(ptr!)
+ }
+
+ public static func write(_ value: MobileClient, into buf: inout [UInt8]) {
+ // This fiddling is because `Int` is the thing that's the same size as a pointer.
+ // The Rust code won't compile if a pointer won't fit in a `UInt64`.
+ writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value)))))
+ }
+}
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeMobileClient_lift(_ pointer: UnsafeMutableRawPointer) throws -> MobileClient {
+ return try FfiConverterTypeMobileClient.lift(pointer)
+}
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeMobileClient_lower(_ value: MobileClient) -> UnsafeMutableRawPointer {
+ return FfiConverterTypeMobileClient.lower(value)
+}
+
+
+
+
+/**
+ * Versioned, presentation-independent capability observation.
+ */
+public struct CapabilityStatus {
+ public var id: String
+ public var compiled: Bool
+ public var configured: Bool
+ public var availability: CapabilityAvailability
+ public var maturity: CapabilityMaturity
+
+ // Default memberwise initializers are never public by default, so we
+ // declare one manually.
+ public init(id: String, compiled: Bool, configured: Bool, availability: CapabilityAvailability, maturity: CapabilityMaturity) {
+ self.id = id
+ self.compiled = compiled
+ self.configured = configured
+ self.availability = availability
+ self.maturity = maturity
+ }
+}
+
+#if compiler(>=6)
+extension CapabilityStatus: Sendable {}
+#endif
+
+
+extension CapabilityStatus: Equatable, Hashable {
+ public static func ==(lhs: CapabilityStatus, rhs: CapabilityStatus) -> Bool {
+ if lhs.id != rhs.id {
+ return false
+ }
+ if lhs.compiled != rhs.compiled {
+ return false
+ }
+ if lhs.configured != rhs.configured {
+ return false
+ }
+ if lhs.availability != rhs.availability {
+ return false
+ }
+ if lhs.maturity != rhs.maturity {
+ return false
+ }
+ return true
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(id)
+ hasher.combine(compiled)
+ hasher.combine(configured)
+ hasher.combine(availability)
+ hasher.combine(maturity)
+ }
+}
+
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public struct FfiConverterTypeCapabilityStatus: FfiConverterRustBuffer {
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CapabilityStatus {
+ return
+ try CapabilityStatus(
+ id: FfiConverterString.read(from: &buf),
+ compiled: FfiConverterBool.read(from: &buf),
+ configured: FfiConverterBool.read(from: &buf),
+ availability: FfiConverterTypeCapabilityAvailability.read(from: &buf),
+ maturity: FfiConverterTypeCapabilityMaturity.read(from: &buf)
+ )
+ }
+
+ public static func write(_ value: CapabilityStatus, into buf: inout [UInt8]) {
+ FfiConverterString.write(value.id, into: &buf)
+ FfiConverterBool.write(value.compiled, into: &buf)
+ FfiConverterBool.write(value.configured, into: &buf)
+ FfiConverterTypeCapabilityAvailability.write(value.availability, into: &buf)
+ FfiConverterTypeCapabilityMaturity.write(value.maturity, into: &buf)
+ }
+}
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeCapabilityStatus_lift(_ buf: RustBuffer) throws -> CapabilityStatus {
+ return try FfiConverterTypeCapabilityStatus.lift(buf)
+}
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeCapabilityStatus_lower(_ value: CapabilityStatus) -> RustBuffer {
+ return FfiConverterTypeCapabilityStatus.lower(value)
+}
+
+// Note that we don't yet support `indirect` for enums.
+// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
+/**
+ * Current side-effect-free capability availability.
+ */
+
+public enum CapabilityAvailability {
+
+ case available
+ case degraded
+ case unavailable
+ case unsupported
+}
+
+
+#if compiler(>=6)
+extension CapabilityAvailability: Sendable {}
+#endif
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public struct FfiConverterTypeCapabilityAvailability: FfiConverterRustBuffer {
+ typealias SwiftType = CapabilityAvailability
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CapabilityAvailability {
+ let variant: Int32 = try readInt(&buf)
+ switch variant {
+
+ case 1: return .available
+
+ case 2: return .degraded
+
+ case 3: return .unavailable
+
+ case 4: return .unsupported
+
+ default: throw UniffiInternalError.unexpectedEnumCase
+ }
+ }
+
+ public static func write(_ value: CapabilityAvailability, into buf: inout [UInt8]) {
+ switch value {
+
+
+ case .available:
+ writeInt(&buf, Int32(1))
+
+
+ case .degraded:
+ writeInt(&buf, Int32(2))
+
+
+ case .unavailable:
+ writeInt(&buf, Int32(3))
+
+
+ case .unsupported:
+ writeInt(&buf, Int32(4))
+
+ }
+ }
+}
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeCapabilityAvailability_lift(_ buf: RustBuffer) throws -> CapabilityAvailability {
+ return try FfiConverterTypeCapabilityAvailability.lift(buf)
+}
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeCapabilityAvailability_lower(_ value: CapabilityAvailability) -> RustBuffer {
+ return FfiConverterTypeCapabilityAvailability.lower(value)
+}
+
+
+extension CapabilityAvailability: Equatable, Hashable {}
+
+
+
+
+
+
+// Note that we don't yet support `indirect` for enums.
+// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
+/**
+ * Stable capability maturity independent of runtime availability.
+ */
+
+public enum CapabilityMaturity {
+
+ case stable
+ case preview
+ case experimental
+}
+
+
+#if compiler(>=6)
+extension CapabilityMaturity: Sendable {}
+#endif
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public struct FfiConverterTypeCapabilityMaturity: FfiConverterRustBuffer {
+ typealias SwiftType = CapabilityMaturity
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CapabilityMaturity {
+ let variant: Int32 = try readInt(&buf)
+ switch variant {
+
+ case 1: return .stable
+
+ case 2: return .preview
+
+ case 3: return .experimental
+
+ default: throw UniffiInternalError.unexpectedEnumCase
+ }
+ }
+
+ public static func write(_ value: CapabilityMaturity, into buf: inout [UInt8]) {
+ switch value {
+
+
+ case .stable:
+ writeInt(&buf, Int32(1))
+
+
+ case .preview:
+ writeInt(&buf, Int32(2))
+
+
+ case .experimental:
+ writeInt(&buf, Int32(3))
+
+ }
+ }
+}
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeCapabilityMaturity_lift(_ buf: RustBuffer) throws -> CapabilityMaturity {
+ return try FfiConverterTypeCapabilityMaturity.lift(buf)
+}
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeCapabilityMaturity_lower(_ value: CapabilityMaturity) -> RustBuffer {
+ return FfiConverterTypeCapabilityMaturity.lower(value)
+}
+
+
+extension CapabilityMaturity: Equatable, Hashable {}
+
+
+
+
+
+
+
+/**
+ * Secret-safe SDK failure at the language boundary.
+ */
+public enum Error: Swift.Error {
+
+
+
+ case Sdk(code: String, message: String, retryable: Bool
+ )
+}
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public struct FfiConverterTypeError: FfiConverterRustBuffer {
+ typealias SwiftType = Error
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Error {
+ let variant: Int32 = try readInt(&buf)
+ switch variant {
+
+
+
+
+ case 1: return .Sdk(
+ code: try FfiConverterString.read(from: &buf),
+ message: try FfiConverterString.read(from: &buf),
+ retryable: try FfiConverterBool.read(from: &buf)
+ )
+
+ default: throw UniffiInternalError.unexpectedEnumCase
+ }
+ }
+
+ public static func write(_ value: Error, into buf: inout [UInt8]) {
+ switch value {
+
+
+
+
+
+ case let .Sdk(code,message,retryable):
+ writeInt(&buf, Int32(1))
+ FfiConverterString.write(code, into: &buf)
+ FfiConverterString.write(message, into: &buf)
+ FfiConverterBool.write(retryable, into: &buf)
+
+ }
+ }
+}
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeError_lift(_ buf: RustBuffer) throws -> Error {
+ return try FfiConverterTypeError.lift(buf)
+}
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+public func FfiConverterTypeError_lower(_ value: Error) -> RustBuffer {
+ return FfiConverterTypeError.lower(value)
+}
+
+
+extension Error: Equatable, Hashable {}
+
+
+
+
+extension Error: Foundation.LocalizedError {
+ public var errorDescription: String? {
+ String(reflecting: self)
+ }
+}
+
+
+
+
+#if swift(>=5.8)
+@_documentation(visibility: private)
+#endif
+fileprivate struct FfiConverterSequenceTypeCapabilityStatus: FfiConverterRustBuffer {
+ typealias SwiftType = [CapabilityStatus]
+
+ public static func write(_ value: [CapabilityStatus], into buf: inout [UInt8]) {
+ let len = Int32(value.count)
+ writeInt(&buf, len)
+ for item in value {
+ FfiConverterTypeCapabilityStatus.write(item, into: &buf)
+ }
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [CapabilityStatus] {
+ let len: Int32 = try readInt(&buf)
+ var seq = [CapabilityStatus]()
+ seq.reserveCapacity(Int(len))
+ for _ in 0 ..< len {
+ seq.append(try FfiConverterTypeCapabilityStatus.read(from: &buf))
+ }
+ return seq
+ }
+}
+private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0
+private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1
+
+fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>()
+
+fileprivate func uniffiRustCallAsync<F, T>(
+ rustFutureFunc: () -> UInt64,
+ pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (),
+ completeFunc: (UInt64, UnsafeMutablePointer<RustCallStatus>) -> F,
+ freeFunc: (UInt64) -> (),
+ liftFunc: (F) throws -> T,
+ errorHandler: ((RustBuffer) throws -> Swift.Error)?
+) async throws -> T {
+ // Make sure to call the ensure init function since future creation doesn't have a
+ // RustCallStatus param, so doesn't use makeRustCall()
+ uniffiEnsureRadrootsSdkFfiInitialized()
+ let rustFuture = rustFutureFunc()
+ defer {
+ freeFunc(rustFuture)
+ }
+ var pollResult: Int8;
+ repeat {
+ pollResult = await withUnsafeContinuation {
+ pollFunc(
+ rustFuture,
+ uniffiFutureContinuationCallback,
+ uniffiContinuationHandleMap.insert(obj: $0)
+ )
+ }
+ } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY
+
+ return try liftFunc(makeRustCall(
+ { completeFunc(rustFuture, $0) },
+ errorHandler: errorHandler
+ ))
+}
+
+// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They
+// lift the return value or error and resume the suspended function.
+fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) {
+ if let continuation = try? uniffiContinuationHandleMap.remove(handle: handle) {
+ continuation.resume(returning: pollResult)
+ } else {
+ print("uniffiFutureContinuationCallback invalid handle")
+ }
+}
+
+private enum InitializationResult {
+ case ok
+ case contractVersionMismatch
+ case apiChecksumMismatch
+}
+// Use a global variable to perform the versioning checks. Swift ensures that
+// the code inside is only computed once.
+private let initializationResult: InitializationResult = {
+ // Get the bindings contract version from our ComponentInterface
+ let bindings_contract_version = 29
+ // Get the scaffolding contract version by calling the into the dylib
+ let scaffolding_contract_version = ffi_radroots_sdk_ffi_uniffi_contract_version()
+ if bindings_contract_version != scaffolding_contract_version {
+ return InitializationResult.contractVersionMismatch
+ }
+ if (uniffi_radroots_sdk_ffi_checksum_method_mobileclient_capabilities() != 23938) {
+ return InitializationResult.apiChecksumMismatch
+ }
+ if (uniffi_radroots_sdk_ffi_checksum_method_mobileclient_close() != 15210) {
+ return InitializationResult.apiChecksumMismatch
+ }
+ if (uniffi_radroots_sdk_ffi_checksum_method_mobileclient_is_closed() != 40205) {
+ return InitializationResult.apiChecksumMismatch
+ }
+ if (uniffi_radroots_sdk_ffi_checksum_constructor_mobileclient_memory() != 50243) {
+ return InitializationResult.apiChecksumMismatch
+ }
+
+ return InitializationResult.ok
+}()
+
+// Make the ensure init function public so that other modules which have external type references to
+// our types can call it.
+public func uniffiEnsureRadrootsSdkFfiInitialized() {
+ switch initializationResult {
+ case .ok:
+ break
+ case .contractVersionMismatch:
+ fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project")
+ case .apiChecksumMismatch:
+ fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
+ }
+}
+
+// swiftlint:enable all
diff --git a/generated/swift/radroots_sdkFFI.h b/generated/swift/radroots_sdkFFI.h
@@ -0,0 +1,594 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+// The following structs are used to implement the lowest level
+// of the FFI, and thus useful to multiple uniffied crates.
+// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
+#ifdef UNIFFI_SHARED_H
+ // We also try to prevent mixing versions of shared uniffi header structs.
+ // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
+ #ifndef UNIFFI_SHARED_HEADER_V4
+ #error Combining helper code from multiple versions of uniffi is not supported
+ #endif // ndef UNIFFI_SHARED_HEADER_V4
+#else
+#define UNIFFI_SHARED_H
+#define UNIFFI_SHARED_HEADER_V4
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
+
+typedef struct RustBuffer
+{
+ uint64_t capacity;
+ uint64_t len;
+ uint8_t *_Nullable data;
+} RustBuffer;
+
+typedef struct ForeignBytes
+{
+ int32_t len;
+ const uint8_t *_Nullable data;
+} ForeignBytes;
+
+// Error definitions
+typedef struct RustCallStatus {
+ int8_t code;
+ RustBuffer errorBuf;
+} RustCallStatus;
+
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
+#endif // def UNIFFI_SHARED_H
+#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
+#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
+typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE
+typedef void (*UniffiForeignFutureFree)(uint64_t
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
+#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
+typedef void (*UniffiCallbackInterfaceFree)(uint64_t
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE
+typedef struct UniffiForeignFuture {
+ uint64_t handle;
+ UniffiForeignFutureFree _Nonnull free;
+} UniffiForeignFuture;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8
+typedef struct UniffiForeignFutureStructU8 {
+ uint8_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructU8;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
+typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureStructU8
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8
+typedef struct UniffiForeignFutureStructI8 {
+ int8_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructI8;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
+typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureStructI8
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16
+typedef struct UniffiForeignFutureStructU16 {
+ uint16_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructU16;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
+typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureStructU16
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16
+typedef struct UniffiForeignFutureStructI16 {
+ int16_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructI16;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
+typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureStructI16
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32
+typedef struct UniffiForeignFutureStructU32 {
+ uint32_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructU32;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
+typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureStructU32
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32
+typedef struct UniffiForeignFutureStructI32 {
+ int32_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructI32;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
+typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureStructI32
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64
+typedef struct UniffiForeignFutureStructU64 {
+ uint64_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructU64;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
+typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureStructU64
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64
+typedef struct UniffiForeignFutureStructI64 {
+ int64_t returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructI64;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
+typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureStructI64
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32
+typedef struct UniffiForeignFutureStructF32 {
+ float returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructF32;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
+typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureStructF32
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64
+typedef struct UniffiForeignFutureStructF64 {
+ double returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructF64;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
+typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureStructF64
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER
+typedef struct UniffiForeignFutureStructPointer {
+ void*_Nonnull returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructPointer;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER
+typedef void (*UniffiForeignFutureCompletePointer)(uint64_t, UniffiForeignFutureStructPointer
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER
+typedef struct UniffiForeignFutureStructRustBuffer {
+ RustBuffer returnValue;
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructRustBuffer;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
+typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureStructRustBuffer
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID
+typedef struct UniffiForeignFutureStructVoid {
+ RustCallStatus callStatus;
+} UniffiForeignFutureStructVoid;
+
+#endif
+#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
+#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
+typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid
+ );
+
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CLONE_MOBILECLIENT
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CLONE_MOBILECLIENT
+void*_Nonnull uniffi_radroots_sdk_ffi_fn_clone_mobileclient(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_FREE_MOBILECLIENT
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_FREE_MOBILECLIENT
+void uniffi_radroots_sdk_ffi_fn_free_mobileclient(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CONSTRUCTOR_MOBILECLIENT_MEMORY
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CONSTRUCTOR_MOBILECLIENT_MEMORY
+void*_Nonnull uniffi_radroots_sdk_ffi_fn_constructor_mobileclient_memory(RustCallStatus *_Nonnull out_status
+
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CAPABILITIES
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CAPABILITIES
+RustBuffer uniffi_radroots_sdk_ffi_fn_method_mobileclient_capabilities(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CLOSE
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CLOSE
+uint64_t uniffi_radroots_sdk_ffi_fn_method_mobileclient_close(void*_Nonnull ptr
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_IS_CLOSED
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_IS_CLOSED
+int8_t uniffi_radroots_sdk_ffi_fn_method_mobileclient_is_closed(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_ALLOC
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_ALLOC
+RustBuffer ffi_radroots_sdk_ffi_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FROM_BYTES
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FROM_BYTES
+RustBuffer ffi_radroots_sdk_ffi_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FREE
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FREE
+void ffi_radroots_sdk_ffi_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_RESERVE
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_RESERVE
+RustBuffer ffi_radroots_sdk_ffi_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U8
+void ffi_radroots_sdk_ffi_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U8
+void ffi_radroots_sdk_ffi_rust_future_cancel_u8(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U8
+void ffi_radroots_sdk_ffi_rust_future_free_u8(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U8
+uint8_t ffi_radroots_sdk_ffi_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I8
+void ffi_radroots_sdk_ffi_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I8
+void ffi_radroots_sdk_ffi_rust_future_cancel_i8(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I8
+void ffi_radroots_sdk_ffi_rust_future_free_i8(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I8
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I8
+int8_t ffi_radroots_sdk_ffi_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U16
+void ffi_radroots_sdk_ffi_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U16
+void ffi_radroots_sdk_ffi_rust_future_cancel_u16(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U16
+void ffi_radroots_sdk_ffi_rust_future_free_u16(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U16
+uint16_t ffi_radroots_sdk_ffi_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I16
+void ffi_radroots_sdk_ffi_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I16
+void ffi_radroots_sdk_ffi_rust_future_cancel_i16(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I16
+void ffi_radroots_sdk_ffi_rust_future_free_i16(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I16
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I16
+int16_t ffi_radroots_sdk_ffi_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U32
+void ffi_radroots_sdk_ffi_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U32
+void ffi_radroots_sdk_ffi_rust_future_cancel_u32(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U32
+void ffi_radroots_sdk_ffi_rust_future_free_u32(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U32
+uint32_t ffi_radroots_sdk_ffi_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I32
+void ffi_radroots_sdk_ffi_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I32
+void ffi_radroots_sdk_ffi_rust_future_cancel_i32(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I32
+void ffi_radroots_sdk_ffi_rust_future_free_i32(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I32
+int32_t ffi_radroots_sdk_ffi_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U64
+void ffi_radroots_sdk_ffi_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U64
+void ffi_radroots_sdk_ffi_rust_future_cancel_u64(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U64
+void ffi_radroots_sdk_ffi_rust_future_free_u64(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U64
+uint64_t ffi_radroots_sdk_ffi_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I64
+void ffi_radroots_sdk_ffi_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I64
+void ffi_radroots_sdk_ffi_rust_future_cancel_i64(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I64
+void ffi_radroots_sdk_ffi_rust_future_free_i64(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I64
+int64_t ffi_radroots_sdk_ffi_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F32
+void ffi_radroots_sdk_ffi_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F32
+void ffi_radroots_sdk_ffi_rust_future_cancel_f32(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F32
+void ffi_radroots_sdk_ffi_rust_future_free_f32(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F32
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F32
+float ffi_radroots_sdk_ffi_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F64
+void ffi_radroots_sdk_ffi_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F64
+void ffi_radroots_sdk_ffi_rust_future_cancel_f64(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F64
+void ffi_radroots_sdk_ffi_rust_future_free_f64(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F64
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F64
+double ffi_radroots_sdk_ffi_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_POINTER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_POINTER
+void ffi_radroots_sdk_ffi_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_POINTER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_POINTER
+void ffi_radroots_sdk_ffi_rust_future_cancel_pointer(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_POINTER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_POINTER
+void ffi_radroots_sdk_ffi_rust_future_free_pointer(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_POINTER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_POINTER
+void*_Nonnull ffi_radroots_sdk_ffi_rust_future_complete_pointer(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_RUST_BUFFER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_RUST_BUFFER
+void ffi_radroots_sdk_ffi_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
+void ffi_radroots_sdk_ffi_rust_future_cancel_rust_buffer(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_RUST_BUFFER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_RUST_BUFFER
+void ffi_radroots_sdk_ffi_rust_future_free_rust_buffer(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
+RustBuffer ffi_radroots_sdk_ffi_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_VOID
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_VOID
+void ffi_radroots_sdk_ffi_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_VOID
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_VOID
+void ffi_radroots_sdk_ffi_rust_future_cancel_void(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_VOID
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_VOID
+void ffi_radroots_sdk_ffi_rust_future_free_void(uint64_t handle
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_VOID
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_VOID
+void ffi_radroots_sdk_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CAPABILITIES
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CAPABILITIES
+uint16_t uniffi_radroots_sdk_ffi_checksum_method_mobileclient_capabilities(void
+
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CLOSE
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CLOSE
+uint16_t uniffi_radroots_sdk_ffi_checksum_method_mobileclient_close(void
+
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_IS_CLOSED
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_IS_CLOSED
+uint16_t uniffi_radroots_sdk_ffi_checksum_method_mobileclient_is_closed(void
+
+);
+#endif
+#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_CONSTRUCTOR_MOBILECLIENT_MEMORY
+#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_CONSTRUCTOR_MOBILECLIENT_MEMORY
+uint16_t uniffi_radroots_sdk_ffi_checksum_constructor_mobileclient_memory(void
+
+);
+#endif
+#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_UNIFFI_CONTRACT_VERSION
+#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_UNIFFI_CONTRACT_VERSION
+uint32_t ffi_radroots_sdk_ffi_uniffi_contract_version(void
+
+);
+#endif
diff --git a/generated/swift/radroots_sdkFFI.modulemap b/generated/swift/radroots_sdkFFI.modulemap
@@ -0,0 +1,7 @@
+module radroots_sdkFFI {
+ header "radroots_sdkFFI.h"
+ export *
+ use "Darwin"
+ use "_Builtin_stdbool"
+ use "_Builtin_stdint"
+}
diff --git a/generated/swift/source.lock b/generated/swift/source.lock
@@ -0,0 +1,14 @@
+{
+ "schema_version": 1,
+ "source_package": "radroots_sdk_ffi",
+ "source_version": "0.1.0-alpha",
+ "source_sha256": "733c27226e29c3bea820e869e93f3916f1e1dbe9fc1cce30c15d31063ad5a033",
+ "generator": "uniffi",
+ "generator_version": "0.29.5",
+ "language": "swift",
+ "outputs": {
+ "radroots_sdk.swift": "0f81256c17243e485d9bca8f2dd4771c0ed2c2c1dc4fb03eb352389aab8599b0",
+ "radroots_sdkFFI.h": "511d3a22f969ef21d1a6d7357241756c2fda5d96adbb8bb17f2da5e8c65bcc54",
+ "radroots_sdkFFI.modulemap": "b18fa5af1567a940fc699afe371a3a4653dca206f3233bcdf322062ab8192170"
+ }
+}
diff --git a/tools/xtask/src/bindings.rs b/tools/xtask/src/bindings.rs
@@ -0,0 +1,253 @@
+use std::{
+ collections::BTreeMap,
+ env, fs,
+ path::{Path, PathBuf},
+ process::Command,
+};
+
+use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
+
+use crate::fs::{workspace_root, write_if_changed};
+
+const FFI_PACKAGE: &str = "radroots_sdk_ffi";
+const FFI_VERSION: &str = "0.1.0-alpha";
+const BINDGEN_VERSION: &str = "0.29.5";
+
+#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
+struct SourceLock {
+ schema_version: u16,
+ source_package: String,
+ source_version: String,
+ source_sha256: String,
+ generator: String,
+ generator_version: String,
+ language: String,
+ outputs: BTreeMap<String, String>,
+}
+
+pub fn generate(args: &[String]) -> Result<(), String> {
+ match args {
+ [language] if language == "swift" => generate_language("swift"),
+ [language] if language == "kotlin" => generate_language("kotlin"),
+ _ => Err("usage: cargo xtask generate bindings <swift|kotlin>".to_owned()),
+ }
+}
+
+fn generate_language(language: &'static str) -> Result<(), String> {
+ let root = workspace_root()?;
+ build_bindgen(&root)?;
+ let target_dir = env::var_os("CARGO_TARGET_DIR")
+ .map(PathBuf::from)
+ .ok_or_else(|| "CARGO_TARGET_DIR must be set by extbuild for FFI generation".to_owned())?;
+ let library = target_dir.join("debug").join(format!(
+ "{}radroots_sdk_ffi{}",
+ env::consts::DLL_PREFIX,
+ env::consts::DLL_SUFFIX
+ ));
+ if !library.is_file() {
+ return Err(format!("missing built FFI library: {}", library.display()));
+ }
+ let bindgen = target_dir
+ .join("debug")
+ .join(format!("radroots-sdk-bindgen{}", env::consts::EXE_SUFFIX));
+ if !bindgen.is_file() {
+ return Err(format!(
+ "missing FFI bindgen executable: {}",
+ bindgen.display()
+ ));
+ }
+ let temporary = tempfile::tempdir()
+ .map_err(|error| format!("failed to create binding output directory: {error}"))?;
+ let status = Command::new(&bindgen)
+ .args(["generate", "--library"])
+ .arg(&library)
+ .args(["--crate", FFI_PACKAGE, "--language", language, "--out-dir"])
+ .arg(temporary.path())
+ .current_dir(&root)
+ .status()
+ .map_err(|error| format!("failed to start {}: {error}", bindgen.display()))?;
+ if !status.success() {
+ return Err(format!(
+ "{language} binding generation failed with {status}"
+ ));
+ }
+ install_outputs(&root, language, temporary.path())?;
+ println!("generated {language} bindings for {FFI_PACKAGE}");
+ Ok(())
+}
+
+fn build_bindgen(root: &Path) -> Result<(), String> {
+ let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
+ let status = Command::new(cargo)
+ .args([
+ "build",
+ "--locked",
+ "-p",
+ FFI_PACKAGE,
+ "--lib",
+ "--bin",
+ "radroots-sdk-bindgen",
+ ])
+ .current_dir(root)
+ .status()
+ .map_err(|error| format!("failed to start Cargo for FFI generation: {error}"))?;
+ if !status.success() {
+ return Err(format!(
+ "FFI library and bindgen build failed with {status}"
+ ));
+ }
+ Ok(())
+}
+
+fn install_outputs(root: &Path, language: &'static str, temporary: &Path) -> Result<(), String> {
+ let output_dir = root.join("generated").join(language);
+ fs::create_dir_all(&output_dir)
+ .map_err(|error| format!("failed to create {}: {error}", output_dir.display()))?;
+ let mut generated = fs::read_dir(temporary)
+ .map_err(|error| format!("failed to read {}: {error}", temporary.display()))?
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|error| format!("failed to enumerate {}: {error}", temporary.display()))?;
+ generated.sort_by_key(std::fs::DirEntry::file_name);
+ let mut outputs = BTreeMap::new();
+ for entry in generated {
+ let path = entry.path();
+ if !path.is_file() {
+ return Err(format!(
+ "unexpected generated binding entry: {}",
+ path.display()
+ ));
+ }
+ let name = entry
+ .file_name()
+ .into_string()
+ .map_err(|_| format!("non-UTF-8 generated binding path: {}", path.display()))?;
+ let contents = fs::read(&path)
+ .map_err(|error| format!("failed to read {}: {error}", path.display()))?;
+ let destination = output_dir.join(&name);
+ let text = String::from_utf8(contents)
+ .map_err(|error| format!("generated text binding is not UTF-8: {error}"))?;
+ let text = normalize_generated_text(&text);
+ outputs.insert(
+ name.clone(),
+ format!("{:x}", Sha256::digest(text.as_bytes())),
+ );
+ write_if_changed(&destination, &text)?;
+ }
+ if outputs.is_empty() {
+ return Err(format!("{language} binding generator emitted no files"));
+ }
+ let lock = SourceLock {
+ schema_version: 1,
+ source_package: FFI_PACKAGE.to_owned(),
+ source_version: FFI_VERSION.to_owned(),
+ source_sha256: ffi_source_sha256(root)?,
+ generator: "uniffi".to_owned(),
+ generator_version: BINDGEN_VERSION.to_owned(),
+ language: language.to_owned(),
+ outputs,
+ };
+ let mut rendered = serde_json::to_string_pretty(&lock)
+ .map_err(|error| format!("failed to render {language} source lock: {error}"))?;
+ rendered.push('\n');
+ write_if_changed(&output_dir.join("source.lock"), &rendered).map(|_| ())
+}
+
+fn normalize_generated_text(contents: &str) -> String {
+ let mut normalized = contents
+ .lines()
+ .map(str::trim_end)
+ .collect::<Vec<_>>()
+ .join("\n");
+ while normalized.ends_with('\n') {
+ normalized.pop();
+ }
+ normalized.push('\n');
+ normalized
+}
+
+pub fn check(root: &Path) -> Result<(), String> {
+ check_language(
+ root,
+ "swift",
+ &[
+ "radroots_sdk.swift",
+ "radroots_sdkFFI.h",
+ "radroots_sdkFFI.modulemap",
+ ],
+ )
+}
+
+fn check_language(root: &Path, language: &'static str, expected: &[&str]) -> Result<(), String> {
+ let output_dir = root.join("generated").join(language);
+ let lock_path = output_dir.join("source.lock");
+ let raw = fs::read_to_string(&lock_path)
+ .map_err(|error| format!("failed to read {}: {error}", lock_path.display()))?;
+ let actual: SourceLock = serde_json::from_str(&raw)
+ .map_err(|error| format!("failed to parse {}: {error}", lock_path.display()))?;
+ let mut outputs = BTreeMap::new();
+ for name in expected {
+ let path = output_dir.join(name);
+ let contents = fs::read(&path)
+ .map_err(|error| format!("failed to read {}: {error}", path.display()))?;
+ outputs.insert(
+ (*name).to_owned(),
+ format!("{:x}", Sha256::digest(contents)),
+ );
+ }
+ let expected_lock = SourceLock {
+ schema_version: 1,
+ source_package: FFI_PACKAGE.to_owned(),
+ source_version: FFI_VERSION.to_owned(),
+ source_sha256: ffi_source_sha256(root)?,
+ generator: "uniffi".to_owned(),
+ generator_version: BINDGEN_VERSION.to_owned(),
+ language: language.to_owned(),
+ outputs,
+ };
+ if actual != expected_lock {
+ return Err(format!(
+ "stale generated {language} bindings: {}",
+ lock_path.display()
+ ));
+ }
+ Ok(())
+}
+
+fn ffi_source_sha256(root: &Path) -> Result<String, String> {
+ let crate_dir = root.join("crates/ffi");
+ let mut paths = vec![crate_dir.join("Cargo.toml"), crate_dir.join("README.md")];
+ collect_files(&crate_dir.join("src"), &mut paths)?;
+ paths.sort();
+ let mut hasher = Sha256::new();
+ for path in paths {
+ let relative = path
+ .strip_prefix(root)
+ .map_err(|error| format!("failed to relativize {}: {error}", path.display()))?;
+ hasher.update(relative.to_string_lossy().as_bytes());
+ hasher.update([0]);
+ hasher.update(
+ fs::read(&path)
+ .map_err(|error| format!("failed to read {}: {error}", path.display()))?,
+ );
+ hasher.update([0]);
+ }
+ Ok(format!("{:x}", hasher.finalize()))
+}
+
+fn collect_files(dir: &Path, paths: &mut Vec<PathBuf>) -> Result<(), String> {
+ let mut entries = fs::read_dir(dir)
+ .map_err(|error| format!("failed to read {}: {error}", dir.display()))?
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|error| format!("failed to enumerate {}: {error}", dir.display()))?;
+ entries.sort_by_key(std::fs::DirEntry::file_name);
+ for entry in entries {
+ let path = entry.path();
+ if path.is_dir() {
+ collect_files(&path, paths)?;
+ } else if path.is_file() {
+ paths.push(path);
+ }
+ }
+ Ok(())
+}
diff --git a/tools/xtask/src/check.rs b/tools/xtask/src/check.rs
@@ -154,6 +154,7 @@ pub fn check() -> Result<(), String> {
cli_host::check_cli_host()?;
check_package_build_artifacts(&root)?;
check_npm_pack_payloads(&root)?;
+ crate::bindings::check(&root)?;
Ok(())
}
@@ -166,7 +167,8 @@ pub fn architecture_ci(root: &Path) -> Result<(), String> {
check_forbidden_packages(root)?;
check_binding_crate_sources(root)?;
check_package_source_metadata(root)?;
- check_generated_outputs(root)
+ check_generated_outputs(root)?;
+ crate::bindings::check(root)
}
fn check_radroots_facade_conformance(root: &Path) -> Result<(), String> {
diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs
@@ -1,4 +1,5 @@
mod architecture;
+mod bindings;
mod check;
mod cli_host;
mod contracts;
@@ -23,6 +24,7 @@ enum CommandAction<'a> {
CheckApiBoundaries,
CheckDependencyBoundaries,
GenerateAll,
+ GenerateBindings(&'a [String]),
GenerateTs,
GenerateWasm(&'a [String]),
GeneratePackageMetadata,
@@ -54,6 +56,7 @@ fn run(args: impl IntoIterator<Item = String>) -> Result<(), String> {
architecture::validate_dependency_boundaries(&fs::workspace_root()?)
}
CommandAction::GenerateAll => generate::generate_all(),
+ CommandAction::GenerateBindings(rest) => bindings::generate(rest),
CommandAction::GenerateTs => generate::generate_ts(),
CommandAction::GenerateWasm(rest) => wasm::generate(rest),
CommandAction::GeneratePackageMetadata => generate::generate_package_metadata(),
@@ -78,6 +81,9 @@ fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> {
[command, target, rest @ ..] if command == "generate" && target == "wasm" => {
Ok(CommandAction::GenerateWasm(rest))
}
+ [command, target, rest @ ..] if command == "generate" && target == "bindings" => {
+ Ok(CommandAction::GenerateBindings(rest))
+ }
[command, target] if command == "generate" && target == "package-metadata" => {
Ok(CommandAction::GeneratePackageMetadata)
}
@@ -90,7 +96,7 @@ fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> {
}
fn usage() -> String {
- "usage: cargo xtask architecture | cargo xtask architecture-ci | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke facade-rust-local | sdk-rust-local | front-doors-rust-local | cargo xtask coverage run"
+ "usage: cargo xtask architecture | cargo xtask architecture-ci | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate bindings <swift|kotlin> | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke facade-rust-local | sdk-rust-local | front-doors-rust-local | cargo xtask coverage run"
.to_owned()
}
@@ -162,6 +168,19 @@ mod tests {
}
#[test]
+ fn accepts_generate_bindings() {
+ let args = [
+ "generate".to_owned(),
+ "bindings".to_owned(),
+ "swift".to_owned(),
+ ];
+ assert!(matches!(
+ command_action(&args).expect("action"),
+ CommandAction::GenerateBindings(rest) if rest == ["swift"]
+ ));
+ }
+
+ #[test]
fn accepts_generate_package_metadata() {
let args = ["generate".to_owned(), "package-metadata".to_owned()];
assert!(matches!(