From 7eb07078d367453e86702fc8f96a5015240ace0c Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 21 Oct 2022 11:42:29 +0200 Subject: [PATCH] Add first round of UniFFI bindings --- uniffi/ldk_lite.py | 1100 ++++++++++++++++++++++++++++ uniffi/ldk_lite.swift | 922 +++++++++++++++++++++++ uniffi/ldk_liteFFI.h | 120 +++ uniffi/ldk_liteFFI.modulemap | 6 + uniffi/uniffi/ldk_lite/ldk_lite.kt | 1083 +++++++++++++++++++++++++++ 5 files changed, 3231 insertions(+) create mode 100644 uniffi/ldk_lite.py create mode 100644 uniffi/ldk_lite.swift create mode 100644 uniffi/ldk_liteFFI.h create mode 100644 uniffi/ldk_liteFFI.modulemap create mode 100644 uniffi/uniffi/ldk_lite/ldk_lite.kt diff --git a/uniffi/ldk_lite.py b/uniffi/ldk_lite.py new file mode 100644 index 000000000..333741d21 --- /dev/null +++ b/uniffi/ldk_lite.py @@ -0,0 +1,1100 @@ +# This file was autogenerated by some hot garbage in the `uniffi` crate. +# Trust me, you don't want to mess with it! + +# Tell mypy (a type checker) to ignore all errors from this file. +# See https://mypy.readthedocs.io/en/stable/config_file.html?highlight=ignore-errors#confval-ignore_errors +# mypy: ignore-errors + +# Common helper code. +# +# Ideally this would live in a separate .py file where it can be unittested etc +# in isolation, and perhaps even published as a re-useable package. +# +# However, it's important that the details of how this helper code works (e.g. the +# way that different builtin types are passed across the FFI) exactly match what's +# expected by the rust code on the other side of the interface. In practice right +# now that means coming from the exact some version of `uniffi` that was used to +# compile the rust component. The easiest way to ensure this is to bundle the Python +# helpers directly inline like we're doing here. + +import os +import sys +import ctypes +import enum +import struct +import contextlib +import datetime + +# Used for default argument values +DEFAULT = object() + + +class RustBuffer(ctypes.Structure): + _fields_ = [ + ("capacity", ctypes.c_int32), + ("len", ctypes.c_int32), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + @staticmethod + def alloc(size): + return rust_call(_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_alloc, size) + + @staticmethod + def reserve(rbuf, additional): + return rust_call(_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_reserve, rbuf, additional) + + def free(self): + return rust_call(_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_free, self) + + def __str__(self): + return "RustBuffer(capacity={}, len={}, data={})".format( + self.capacity, + self.len, + self.data[0:self.len] + ) + + @contextlib.contextmanager + def allocWithBuilder(): + """Context-manger to allocate a buffer using a RustBufferBuilder. + + The allocated buffer will be automatically freed if an error occurs, ensuring that + we don't accidentally leak it. + """ + builder = RustBufferBuilder() + try: + yield builder + except: + builder.discard() + raise + + @contextlib.contextmanager + def consumeWithStream(self): + """Context-manager to consume a buffer using a RustBufferStream. + + The RustBuffer will be freed once the context-manager exits, ensuring that we don't + leak it even if an error occurs. + """ + try: + s = RustBufferStream(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer after consuming") + finally: + self.free() + + +class ForeignBytes(ctypes.Structure): + _fields_ = [ + ("len", ctypes.c_int32), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + def __str__(self): + return "ForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len]) + + +class RustBufferStream(object): + """ + Helper for structured reading of bytes from a RustBuffer + """ + + def __init__(self, rbuf): + self.rbuf = rbuf + self.offset = 0 + + def remaining(self): + return self.rbuf.len - self.offset + + def _unpack_from(self, size, format): + if self.offset + size > self.rbuf.len: + raise InternalError("read past end of rust buffer") + value = struct.unpack(format, self.rbuf.data[self.offset:self.offset+size])[0] + self.offset += size + return value + + def read(self, size): + if self.offset + size > self.rbuf.len: + raise InternalError("read past end of rust buffer") + data = self.rbuf.data[self.offset:self.offset+size] + self.offset += size + return data + + def readI8(self): + return self._unpack_from(1, ">b") + + def readU8(self): + return self._unpack_from(1, ">B") + + def readI16(self): + return self._unpack_from(2, ">h") + + def readU16(self): + return self._unpack_from(2, ">H") + + def readI32(self): + return self._unpack_from(4, ">i") + + def readU32(self): + return self._unpack_from(4, ">I") + + def readI64(self): + return self._unpack_from(8, ">q") + + def readU64(self): + return self._unpack_from(8, ">Q") + + def readFloat(self): + v = self._unpack_from(4, ">f") + return v + + def readDouble(self): + return self._unpack_from(8, ">d") + + +class RustBufferBuilder(object): + """ + Helper for structured writing of bytes into a RustBuffer. + """ + + def __init__(self): + self.rbuf = RustBuffer.alloc(16) + self.rbuf.len = 0 + + def finalize(self): + rbuf = self.rbuf + self.rbuf = None + return rbuf + + def discard(self): + if self.rbuf is not None: + rbuf = self.finalize() + rbuf.free() + + @contextlib.contextmanager + def _reserve(self, numBytes): + if self.rbuf.len + numBytes > self.rbuf.capacity: + self.rbuf = RustBuffer.reserve(self.rbuf, numBytes) + yield None + self.rbuf.len += numBytes + + def _pack_into(self, size, format, value): + with self._reserve(size): + # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out. + for i, byte in enumerate(struct.pack(format, value)): + self.rbuf.data[self.rbuf.len + i] = byte + + def write(self, value): + with self._reserve(len(value)): + for i, byte in enumerate(value): + self.rbuf.data[self.rbuf.len + i] = byte + + def writeI8(self, v): + self._pack_into(1, ">b", v) + + def writeU8(self, v): + self._pack_into(1, ">B", v) + + def writeI16(self, v): + self._pack_into(2, ">h", v) + + def writeU16(self, v): + self._pack_into(2, ">H", v) + + def writeI32(self, v): + self._pack_into(4, ">i", v) + + def writeU32(self, v): + self._pack_into(4, ">I", v) + + def writeI64(self, v): + self._pack_into(8, ">q", v) + + def writeU64(self, v): + self._pack_into(8, ">Q", v) + + def writeFloat(self, v): + self._pack_into(4, ">f", v) + + def writeDouble(self, v): + self._pack_into(8, ">d", v) +# A handful of classes and functions to support the generated data structures. +# This would be a good candidate for isolating in its own ffi-support lib. + +class InternalError(Exception): + pass + +class RustCallStatus(ctypes.Structure): + """ + Error runtime. + """ + _fields_ = [ + ("code", ctypes.c_int8), + ("error_buf", RustBuffer), + ] + + # These match the values from the uniffi::rustcalls module + CALL_SUCCESS = 0 + CALL_ERROR = 1 + CALL_PANIC = 2 + + def __str__(self): + if self.code == RustCallStatus.CALL_SUCCESS: + return "RustCallStatus(CALL_SUCCESS)" + elif self.code == RustCallStatus.CALL_ERROR: + return "RustCallStatus(CALL_ERROR)" + elif self.code == RustCallStatus.CALL_PANIC: + return "RustCallStatus(CALL_PANIC)" + else: + return "RustCallStatus()" + +def rust_call(fn, *args): + # Call a rust function + return rust_call_with_error(None, fn, *args) + +def rust_call_with_error(error_ffi_converter, fn, *args): + # Call a rust function and handle any errors + # + # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. + # error_ffi_converter must be set to the FFIConverter for the error class that corresponds to the result. + call_status = RustCallStatus(code=RustCallStatus.CALL_SUCCESS, error_buf=RustBuffer(0, 0, None)) + + args_with_error = args + (ctypes.byref(call_status),) + result = fn(*args_with_error) + if call_status.code == RustCallStatus.CALL_SUCCESS: + return result + elif call_status.code == RustCallStatus.CALL_ERROR: + if error_ffi_converter is None: + call_status.err_buf.contents.free() + raise InternalError("rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") + else: + raise error_ffi_converter.lift(call_status.error_buf) + elif call_status.code == RustCallStatus.CALL_PANIC: + # 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 call_status.error_buf.len > 0: + msg = FfiConverterString.lift(call_status.error_buf) + else: + msg = "Unknown rust panic" + raise InternalError(msg) + else: + raise InternalError("Invalid RustCallStatus code: {}".format( + call_status.code)) + +# A function pointer for a callback as defined by UniFFI. +# Rust definition `fn(handle: u64, method: u32, args: RustBuffer, buf_ptr: *mut RustBuffer) -> int` +FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, RustBuffer, ctypes.POINTER(RustBuffer)) +# Types conforming to `FfiConverterPrimitive` pass themselves directly over the FFI. +class FfiConverterPrimitive: + @classmethod + def lift(cls, value): + return value + + @classmethod + def lower(cls, value): + return value + +# Helper class for wrapper types that will always go through a RustBuffer. +# Classes should inherit from this and implement the `read` and `write` static methods. +class FfiConverterRustBuffer: + @classmethod + def lift(cls, rbuf): + with rbuf.consumeWithStream() as stream: + return cls.read(stream) + + @classmethod + def lower(cls, value): + with RustBuffer.allocWithBuilder() as builder: + cls.write(value, builder) + return builder.finalize() + +# Contains loading, initialization code, +# and the FFI Function declarations in a com.sun.jna.Library. +# This is how we find and load the dynamic library provided by the component. +# For now we just look it up by name. +# +# XXX TODO: This will probably grow some magic for resolving megazording in future. +# E.g. we might start by looking for the named component in `libuniffi.so` and if +# that fails, fall back to loading it separately from `lib${componentName}.so`. + +from pathlib import Path + +def loadIndirect(): + if sys.platform == "darwin": + libname = "lib{}.dylib" + elif sys.platform.startswith("win"): + # As of python3.8, ctypes does not seem to search $PATH when loading DLLs. + # We could use `os.add_dll_directory` to configure the search path, but + # it doesn't feel right to mess with application-wide settings. Let's + # assume that the `.dll` is next to the `.py` file and load by full path. + libname = os.path.join( + os.path.dirname(__file__), + "{}.dll", + ) + else: + # Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos + libname = "lib{}.so" + + lib = libname.format("uniffi_ldk_lite") + path = str(Path(__file__).parent / lib) + return ctypes.cdll.LoadLibrary(path) + +# A ctypes library to expose the extern-C FFI definitions. +# This is an implementation detail which will be called internally by the public API. + +_UniFFILib = loadIndirect() +_UniFFILib.ffi_ldk_lite_4a2d_Builder_object_free.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ffi_ldk_lite_4a2d_Builder_object_free.restype = None +_UniFFILib.ldk_lite_4a2d_Builder_new.argtypes = ( + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_Builder_new.restype = ctypes.c_void_p +_UniFFILib.ldk_lite_4a2d_Builder_build.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_Builder_build.restype = ctypes.c_void_p +_UniFFILib.ffi_ldk_lite_4a2d_LdkLite_object_free.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ffi_ldk_lite_4a2d_LdkLite_object_free.restype = None +_UniFFILib.ldk_lite_4a2d_LdkLite_start.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_start.restype = None +_UniFFILib.ldk_lite_4a2d_LdkLite_stop.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_stop.restype = None +_UniFFILib.ldk_lite_4a2d_LdkLite_next_event.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_next_event.restype = RustBuffer +_UniFFILib.ldk_lite_4a2d_LdkLite_event_handled.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_event_handled.restype = None +_UniFFILib.ldk_lite_4a2d_LdkLite_my_node_id.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_my_node_id.restype = RustBuffer +_UniFFILib.ldk_lite_4a2d_LdkLite_new_funding_address.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_new_funding_address.restype = RustBuffer +_UniFFILib.ldk_lite_4a2d_LdkLite_connect_open_channel.argtypes = ( + ctypes.c_void_p, + RustBuffer, + ctypes.c_uint64, + ctypes.c_int8, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_connect_open_channel.restype = None +_UniFFILib.ldk_lite_4a2d_LdkLite_send_payment.argtypes = ( + ctypes.c_void_p, + RustBuffer, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_send_payment.restype = RustBuffer +_UniFFILib.ldk_lite_4a2d_LdkLite_send_spontaneous_payment.argtypes = ( + ctypes.c_void_p, + ctypes.c_uint64, + RustBuffer, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_send_spontaneous_payment.restype = RustBuffer +_UniFFILib.ldk_lite_4a2d_LdkLite_receive_payment.argtypes = ( + ctypes.c_void_p, + RustBuffer, + RustBuffer, + ctypes.c_uint32, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ldk_lite_4a2d_LdkLite_receive_payment.restype = RustBuffer +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_alloc.argtypes = ( + ctypes.c_int32, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_alloc.restype = RustBuffer +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_from_bytes.argtypes = ( + ForeignBytes, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_from_bytes.restype = RustBuffer +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_free.argtypes = ( + RustBuffer, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_free.restype = None +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_reserve.argtypes = ( + RustBuffer, + ctypes.c_int32, + ctypes.POINTER(RustCallStatus), +) +_UniFFILib.ffi_ldk_lite_4a2d_rustbuffer_reserve.restype = RustBuffer + +# Public interface members begin here. + + +class FfiConverterUInt32(FfiConverterPrimitive): + @staticmethod + def read(buf): + return buf.readU32() + + @staticmethod + def write(value, buf): + buf.writeU32(value) + +class FfiConverterUInt64(FfiConverterPrimitive): + @staticmethod + def read(buf): + return buf.readU64() + + @staticmethod + def write(value, buf): + buf.writeU64(value) + +class FfiConverterBool: + @classmethod + def read(cls, buf): + return cls.lift(buf.readU8()) + + @classmethod + def write(cls, value, buf): + buf.writeU8(cls.lower(value)) + + @staticmethod + def lift(value): + return int(value) != 0 + + @staticmethod + def lower(value): + return 1 if value else 0 + +class FfiConverterString: + @staticmethod + def read(buf): + size = buf.readI32() + if size < 0: + raise InternalError("Unexpected negative string length") + utf8Bytes = buf.read(size) + return utf8Bytes.decode("utf-8") + + @staticmethod + def write(value, buf): + utf8Bytes = value.encode("utf-8") + buf.writeI32(len(utf8Bytes)) + buf.write(utf8Bytes) + + @staticmethod + def lift(buf): + with buf.consumeWithStream() as stream: + return stream.read(stream.remaining()).decode("utf-8") + + @staticmethod + def lower(value): + with RustBuffer.allocWithBuilder() as builder: + builder.write(value.encode("utf-8")) + return builder.finalize() + + + +class Builder(object): + def __init__(self, ): + self._pointer = rust_call(_UniFFILib.ldk_lite_4a2d_Builder_new,) + + def __del__(self): + # In case of partial initialization of instances. + pointer = getattr(self, "_pointer", None) + if pointer is not None: + rust_call(_UniFFILib.ffi_ldk_lite_4a2d_Builder_object_free, pointer) + + # Used by alternative constructors or any methods which return this type. + @classmethod + def _make_instance_(cls, pointer): + # Lightly yucky way to bypass the usual __init__ logic + # and just create a new instance with the required pointer. + inst = cls.__new__(cls) + inst._pointer = pointer + return inst + + + + def build(self, ): + return FfiConverterTypeLdkLite.lift( + rust_call(_UniFFILib.ldk_lite_4a2d_Builder_build,self._pointer,) + ) + + + +class FfiConverterTypeBuilder: + @classmethod + def read(cls, buf): + ptr = buf.readU64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) + + @classmethod + def write(cls, value, buf): + if not isinstance(value, Builder): + raise TypeError("Expected Builder instance, {} found".format(value.__class__.__name__)) + buf.writeU64(cls.lower(value)) + + @staticmethod + def lift(value): + return Builder._make_instance_(value) + + @staticmethod + def lower(value): + return value._pointer + + + +class LdkLite(object): + + def __del__(self): + # In case of partial initialization of instances. + pointer = getattr(self, "_pointer", None) + if pointer is not None: + rust_call(_UniFFILib.ffi_ldk_lite_4a2d_LdkLite_object_free, pointer) + + # Used by alternative constructors or any methods which return this type. + @classmethod + def _make_instance_(cls, pointer): + # Lightly yucky way to bypass the usual __init__ logic + # and just create a new instance with the required pointer. + inst = cls.__new__(cls) + inst._pointer = pointer + return inst + + + + def start(self, ): + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_start,self._pointer,) + + def stop(self, ): + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_stop,self._pointer,) + + def next_event(self, ): + return FfiConverterTypeEvent.lift( + rust_call(_UniFFILib.ldk_lite_4a2d_LdkLite_next_event,self._pointer,) + ) + def event_handled(self, ): + rust_call(_UniFFILib.ldk_lite_4a2d_LdkLite_event_handled,self._pointer,) + + def my_node_id(self, ): + return FfiConverterTypePublicKey.lift( + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_my_node_id,self._pointer,) + ) + def new_funding_address(self, ): + return FfiConverterTypeAddress.lift( + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_new_funding_address,self._pointer,) + ) + def connect_open_channel(self, node_pubkey_and_address,channel_amount_sats,announce_channel): + node_pubkey_and_address = node_pubkey_and_address + + channel_amount_sats = int(channel_amount_sats) + + announce_channel = bool(announce_channel) + + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_connect_open_channel,self._pointer, + FfiConverterString.lower(node_pubkey_and_address), + FfiConverterUInt64.lower(channel_amount_sats), + FfiConverterBool.lower(announce_channel)) + + def send_payment(self, invoice): + invoice = invoice + + return FfiConverterTypePaymentHash.lift( + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_send_payment,self._pointer, + FfiConverterTypeInvoice.lower(invoice)) + ) + def send_spontaneous_payment(self, amount_msat,node_id): + amount_msat = int(amount_msat) + + node_id = node_id + + return FfiConverterTypePaymentHash.lift( + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_send_spontaneous_payment,self._pointer, + FfiConverterUInt64.lower(amount_msat), + FfiConverterString.lower(node_id)) + ) + def receive_payment(self, amount_msat,description,expiry_secs): + amount_msat = (None if amount_msat is None else int(amount_msat)) + + description = description + + expiry_secs = int(expiry_secs) + + return FfiConverterTypeInvoice.lift( + rust_call_with_error( + FfiConverterTypeError,_UniFFILib.ldk_lite_4a2d_LdkLite_receive_payment,self._pointer, + FfiConverterOptionalUInt64.lower(amount_msat), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expiry_secs)) + ) + + + +class FfiConverterTypeLdkLite: + @classmethod + def read(cls, buf): + ptr = buf.readU64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) + + @classmethod + def write(cls, value, buf): + if not isinstance(value, LdkLite): + raise TypeError("Expected LdkLite instance, {} found".format(value.__class__.__name__)) + buf.writeU64(cls.lower(value)) + + @staticmethod + def lift(value): + return LdkLite._make_instance_(value) + + @staticmethod + def lower(value): + return value._pointer + + + + +class Event: + def __init__(self): + raise RuntimeError("Event cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class PAYMENT_SUCCESSFUL(object): + def __init__(self,payment_hash): + + self.payment_hash = payment_hash + + + def __str__(self): + return "Event.PAYMENT_SUCCESSFUL(payment_hash={})".format(self.payment_hash) + + def __eq__(self, other): + if not other.is_payment_successful(): + return False + if self.payment_hash != other.payment_hash: + return False + return True + class PAYMENT_FAILED(object): + def __init__(self,payment_hash): + + self.payment_hash = payment_hash + + + def __str__(self): + return "Event.PAYMENT_FAILED(payment_hash={})".format(self.payment_hash) + + def __eq__(self, other): + if not other.is_payment_failed(): + return False + if self.payment_hash != other.payment_hash: + return False + return True + class PAYMENT_RECEIVED(object): + def __init__(self,payment_hash, amount_msat): + + self.payment_hash = payment_hash + self.amount_msat = amount_msat + + + def __str__(self): + return "Event.PAYMENT_RECEIVED(payment_hash={}, amount_msat={})".format(self.payment_hash, self.amount_msat) + + def __eq__(self, other): + if not other.is_payment_received(): + return False + if self.payment_hash != other.payment_hash: + return False + if self.amount_msat != other.amount_msat: + return False + return True + class CHANNEL_CLOSED(object): + def __init__(self,channel_id): + + self.channel_id = channel_id + + + def __str__(self): + return "Event.CHANNEL_CLOSED(channel_id={})".format(self.channel_id) + + def __eq__(self, other): + if not other.is_channel_closed(): + return False + if self.channel_id != other.channel_id: + return False + return True + + + # For each variant, we have an `is_NAME` method for easily checking + # whether an instance is that variant. + def is_payment_successful(self): + return isinstance(self, Event.PAYMENT_SUCCESSFUL) + def is_payment_failed(self): + return isinstance(self, Event.PAYMENT_FAILED) + def is_payment_received(self): + return isinstance(self, Event.PAYMENT_RECEIVED) + def is_channel_closed(self): + return isinstance(self, Event.CHANNEL_CLOSED) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +Event.PAYMENT_SUCCESSFUL = type("Event.PAYMENT_SUCCESSFUL", (Event.PAYMENT_SUCCESSFUL, Event,), {}) +Event.PAYMENT_FAILED = type("Event.PAYMENT_FAILED", (Event.PAYMENT_FAILED, Event,), {}) +Event.PAYMENT_RECEIVED = type("Event.PAYMENT_RECEIVED", (Event.PAYMENT_RECEIVED, Event,), {}) +Event.CHANNEL_CLOSED = type("Event.CHANNEL_CLOSED", (Event.CHANNEL_CLOSED, Event,), {}) + + + + +class FfiConverterTypeEvent(FfiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.readI32() + if variant == 1: + return Event.PAYMENT_SUCCESSFUL( + FfiConverterTypePaymentHash.read(buf), + ) + if variant == 2: + return Event.PAYMENT_FAILED( + FfiConverterTypePaymentHash.read(buf), + ) + if variant == 3: + return Event.PAYMENT_RECEIVED( + FfiConverterTypePaymentHash.read(buf), + FfiConverterUInt64.read(buf), + ) + if variant == 4: + return Event.CHANNEL_CLOSED( + FfiConverterTypeChannelId.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + def write(value, buf): + if value.is_payment_successful(): + buf.writeI32(1) + FfiConverterTypePaymentHash.write(value.payment_hash, buf) + if value.is_payment_failed(): + buf.writeI32(2) + FfiConverterTypePaymentHash.write(value.payment_hash, buf) + if value.is_payment_received(): + buf.writeI32(3) + FfiConverterTypePaymentHash.write(value.payment_hash, buf) + FfiConverterUInt64.write(value.amount_msat, buf) + if value.is_channel_closed(): + buf.writeI32(4) + FfiConverterTypeChannelId.write(value.channel_id, buf) + + +class Error(Exception): + + # Each variant is a nested class of the error itself. + # It just carries a string error message, so no special implementation is necessary. + class AlreadyRunning(Exception): + pass + class NotRunning(Exception): + pass + class FundingTxCreationFailed(Exception): + pass + class ConnectionFailed(Exception): + pass + class AddressInvalid(Exception): + pass + class PublicKeyInvalid(Exception): + pass + class PaymentHashInvalid(Exception): + pass + class NonUniquePaymentHash(Exception): + pass + class InvoiceInvalid(Exception): + pass + class InvoiceCreationFailed(Exception): + pass + class ChannelIdInvalid(Exception): + pass + class RoutingFailed(Exception): + pass + class PeerInfoParseFailed(Exception): + pass + class ChannelCreationFailed(Exception): + pass + class ChannelClosingFailed(Exception): + pass + class PersistenceFailed(Exception): + pass + class WalletOperationFailed(Exception): + pass + class WalletSigningFailed(Exception): + pass + class ChainAccessFailed(Exception): + pass + +class FfiConverterTypeError(FfiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.readI32() + if variant == 1: + return Error.AlreadyRunning( + FfiConverterString.read(buf), + ) + if variant == 2: + return Error.NotRunning( + FfiConverterString.read(buf), + ) + if variant == 3: + return Error.FundingTxCreationFailed( + FfiConverterString.read(buf), + ) + if variant == 4: + return Error.ConnectionFailed( + FfiConverterString.read(buf), + ) + if variant == 5: + return Error.AddressInvalid( + FfiConverterString.read(buf), + ) + if variant == 6: + return Error.PublicKeyInvalid( + FfiConverterString.read(buf), + ) + if variant == 7: + return Error.PaymentHashInvalid( + FfiConverterString.read(buf), + ) + if variant == 8: + return Error.NonUniquePaymentHash( + FfiConverterString.read(buf), + ) + if variant == 9: + return Error.InvoiceInvalid( + FfiConverterString.read(buf), + ) + if variant == 10: + return Error.InvoiceCreationFailed( + FfiConverterString.read(buf), + ) + if variant == 11: + return Error.ChannelIdInvalid( + FfiConverterString.read(buf), + ) + if variant == 12: + return Error.RoutingFailed( + FfiConverterString.read(buf), + ) + if variant == 13: + return Error.PeerInfoParseFailed( + FfiConverterString.read(buf), + ) + if variant == 14: + return Error.ChannelCreationFailed( + FfiConverterString.read(buf), + ) + if variant == 15: + return Error.ChannelClosingFailed( + FfiConverterString.read(buf), + ) + if variant == 16: + return Error.PersistenceFailed( + FfiConverterString.read(buf), + ) + if variant == 17: + return Error.WalletOperationFailed( + FfiConverterString.read(buf), + ) + if variant == 18: + return Error.WalletSigningFailed( + FfiConverterString.read(buf), + ) + if variant == 19: + return Error.ChainAccessFailed( + FfiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def write(value, buf): + if isinstance(value, Error.AlreadyRunning): + buf.writeI32(1) + if isinstance(value, Error.NotRunning): + buf.writeI32(2) + if isinstance(value, Error.FundingTxCreationFailed): + buf.writeI32(3) + if isinstance(value, Error.ConnectionFailed): + buf.writeI32(4) + if isinstance(value, Error.AddressInvalid): + buf.writeI32(5) + if isinstance(value, Error.PublicKeyInvalid): + buf.writeI32(6) + if isinstance(value, Error.PaymentHashInvalid): + buf.writeI32(7) + if isinstance(value, Error.NonUniquePaymentHash): + buf.writeI32(8) + if isinstance(value, Error.InvoiceInvalid): + buf.writeI32(9) + if isinstance(value, Error.InvoiceCreationFailed): + buf.writeI32(10) + if isinstance(value, Error.ChannelIdInvalid): + buf.writeI32(11) + if isinstance(value, Error.RoutingFailed): + buf.writeI32(12) + if isinstance(value, Error.PeerInfoParseFailed): + buf.writeI32(13) + if isinstance(value, Error.ChannelCreationFailed): + buf.writeI32(14) + if isinstance(value, Error.ChannelClosingFailed): + buf.writeI32(15) + if isinstance(value, Error.PersistenceFailed): + buf.writeI32(16) + if isinstance(value, Error.WalletOperationFailed): + buf.writeI32(17) + if isinstance(value, Error.WalletSigningFailed): + buf.writeI32(18) + if isinstance(value, Error.ChainAccessFailed): + buf.writeI32(19) + + + +class FfiConverterOptionalUInt64(FfiConverterRustBuffer): + @classmethod + def write(cls, value, buf): + if value is None: + buf.writeU8(0) + return + + buf.writeU8(1) + FfiConverterUInt64.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.readU8() + if flag == 0: + return None + elif flag == 1: + return FfiConverterUInt64.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + +class FfiConverterTypeAddress: + @staticmethod + def write(value, buf): + FfiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return FfiConverterString.read(buf) + + @staticmethod + def lift(value): + return FfiConverterString.lift(value) + + @staticmethod + def lower(value): + return FfiConverterString.lower(value) + + +class FfiConverterTypeChannelId: + @staticmethod + def write(value, buf): + FfiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return FfiConverterString.read(buf) + + @staticmethod + def lift(value): + return FfiConverterString.lift(value) + + @staticmethod + def lower(value): + return FfiConverterString.lower(value) + + +class FfiConverterTypeInvoice: + @staticmethod + def write(value, buf): + FfiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return FfiConverterString.read(buf) + + @staticmethod + def lift(value): + return FfiConverterString.lift(value) + + @staticmethod + def lower(value): + return FfiConverterString.lower(value) + + +class FfiConverterTypePaymentHash: + @staticmethod + def write(value, buf): + FfiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return FfiConverterString.read(buf) + + @staticmethod + def lift(value): + return FfiConverterString.lift(value) + + @staticmethod + def lower(value): + return FfiConverterString.lower(value) + + +class FfiConverterTypePublicKey: + @staticmethod + def write(value, buf): + FfiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return FfiConverterString.read(buf) + + @staticmethod + def lift(value): + return FfiConverterString.lift(value) + + @staticmethod + def lower(value): + return FfiConverterString.lower(value) + +__all__ = [ + "InternalError", + "Event", + "Builder", + "LdkLite", + "Error", +] + diff --git a/uniffi/ldk_lite.swift b/uniffi/ldk_lite.swift new file mode 100644 index 000000000..12f47f783 --- /dev/null +++ b/uniffi/ldk_lite.swift @@ -0,0 +1,922 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +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(ldk_liteFFI) + import ldk_liteFFI +#endif + +private 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 from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_ldk_lite_4a2d_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_ldk_lite_4a2d_rustbuffer_free(self, $0) } + } +} + +private extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + 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 libray of its own. + +private extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// A helper class to read values out of a byte buffer. +private class Reader { + let data: Data + var offset: Data.Index + + init(data: Data) { + self.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. + func readInt() throws -> T { + let range = offset ..< offset + MemoryLayout.size + guard data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = data[offset] + offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value) { data.copyBytes(to: $0, from: range) } + 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 + func readBytes(count: Int) throws -> [UInt8] { + let range = offset ..< (offset + count) + guard data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer { buffer in + data.copyBytes(to: buffer, from: range) + } + offset = range.upperBound + return value + } + + // Reads a float at the current offset. + @inlinable + func readFloat() throws -> Float { + return Float(bitPattern: try readInt()) + } + + // Reads a float at the current offset. + @inlinable + func readDouble() throws -> Double { + return Double(bitPattern: try readInt()) + } + + // Indicates if the offset has reached the end of the buffer. + @inlinable + func hasRemaining() -> Bool { + return offset < data.count + } +} + +// A helper class to write values into a byte buffer. +private class Writer { + var bytes: [UInt8] + var offset: Array.Index + + init() { + bytes = [] + offset = 0 + } + + func writeBytes(_ byteArr: S) where S: Sequence, S.Element == UInt8 { + bytes.append(contentsOf: byteArr) + } + + // Writes an integer in big-endian order. + // + // Warning: make sure what you are trying to write + // is in the correct type! + func writeInt(_ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { bytes.append(contentsOf: $0) } + } + + @inlinable + func writeFloat(_ value: Float) { + writeInt(value.bitPattern) + } + + @inlinable + func writeDouble(_ value: Double) { + writeInt(value.bitPattern) + } +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous go the Rust trait of the same name. +private protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: Reader) throws -> SwiftType + static func write(_ value: SwiftType, into buf: Writer) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +private protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType {} + +extension FfiConverterPrimitive { + static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + + 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. +private protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { + static func lift(_ buf: RustBuffer) throws -> SwiftType { + let reader = Reader(data: Data(rustBuffer: buf)) + let value = try read(from: reader) + if reader.hasRemaining() { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + static func lower(_ value: SwiftType) -> RustBuffer { + let writer = Writer() + write(value, into: writer) + return RustBuffer(bytes: writer.bytes) + } +} + +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +private 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 + } + } +} + +private let CALL_SUCCESS: Int8 = 0 +private let CALL_ERROR: Int8 = 1 +private let CALL_PANIC: Int8 = 2 + +private extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: { + $0.deallocate() + return UniffiInternalError.unexpectedRustCallError + }) +} + +private func rustCallWithError +(_ errorFfiConverter: F.Type, _ callback: (UnsafeMutablePointer) -> T) throws -> T + where F.SwiftType: Error, F.FfiType == RustBuffer +{ + try makeRustCall(callback, errorHandler: { try errorFfiConverter.lift($0) }) +} + +private func makeRustCall(_ callback: (UnsafeMutablePointer) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T { + var callStatus = RustCallStatus() + let returnedVal = callback(&callStatus) + switch callStatus.code { + case CALL_SUCCESS: + return returnedVal + + case CALL_ERROR: + throw try errorHandler(callStatus.errorBuf) + + case CALL_PANIC: + // 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") + } + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} + +// Public interface members begin here. + +private struct FfiConverterUInt32: FfiConverterPrimitive { + typealias FfiType = UInt32 + typealias SwiftType = UInt32 + + static func read(from buf: Reader) throws -> UInt32 { + return try lift(buf.readInt()) + } + + static func write(_ value: SwiftType, into buf: Writer) { + buf.writeInt(lower(value)) + } +} + +private struct FfiConverterUInt64: FfiConverterPrimitive { + typealias FfiType = UInt64 + typealias SwiftType = UInt64 + + static func read(from buf: Reader) throws -> UInt64 { + return try lift(buf.readInt()) + } + + static func write(_ value: SwiftType, into buf: Writer) { + buf.writeInt(lower(value)) + } +} + +private struct FfiConverterBool: FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + static func read(from buf: Reader) throws -> Bool { + return try lift(buf.readInt()) + } + + static func write(_ value: Bool, into buf: Writer) { + buf.writeInt(lower(value)) + } +} + +private struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: value.data!, count: Int(value.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + 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) + } + } + } + + static func read(from buf: Reader) throws -> String { + let len: Int32 = try buf.readInt() + return String(bytes: try buf.readBytes(count: Int(len)), encoding: String.Encoding.utf8)! + } + + static func write(_ value: String, into buf: Writer) { + let len = Int32(value.utf8.count) + buf.writeInt(len) + buf.writeBytes(value.utf8) + } +} + +public protocol BuilderProtocol { + func build() -> LdkLite +} + +public class Builder: BuilderProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // 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`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + public convenience init() { + self.init(unsafeFromRawPointer: try! + + rustCall { + ldk_lite_4a2d_Builder_new($0) + }) + } + + deinit { + try! rustCall { ffi_ldk_lite_4a2d_Builder_object_free(pointer, $0) } + } + + public func build() -> LdkLite { + return try! FfiConverterTypeLdkLite.lift( + try! + rustCall { + ldk_lite_4a2d_Builder_build(self.pointer, $0) + } + ) + } +} + +private struct FfiConverterTypeBuilder: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = Builder + + static func read(from buf: Reader) throws -> Builder { + let v: UInt64 = try buf.readInt() + // 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!) + } + + static func write(_ value: Builder, into buf: Writer) { + // 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`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Builder { + return Builder(unsafeFromRawPointer: pointer) + } + + static func lower(_ value: Builder) -> UnsafeMutableRawPointer { + return value.pointer + } +} + +public protocol LdkLiteProtocol { + func start() throws + func stop() throws + func nextEvent() -> Event + func eventHandled() + func myNodeId() throws -> PublicKey + func newFundingAddress() throws -> Address + func connectOpenChannel(nodePubkeyAndAddress: String, channelAmountSats: UInt64, announceChannel: Bool) throws + func sendPayment(invoice: Invoice) throws -> PaymentHash + func sendSpontaneousPayment(amountMsat: UInt64, nodeId: String) throws -> PaymentHash + func receivePayment(amountMsat: UInt64?, description: String, expirySecs: UInt32) throws -> Invoice +} + +public class LdkLite: LdkLiteProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // 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`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { ffi_ldk_lite_4a2d_LdkLite_object_free(pointer, $0) } + } + + public func start() throws { + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_start(self.pointer, $0) + } + } + + public func stop() throws { + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_stop(self.pointer, $0) + } + } + + public func nextEvent() -> Event { + return try! FfiConverterTypeEvent.lift( + try! + rustCall { + ldk_lite_4a2d_LdkLite_next_event(self.pointer, $0) + } + ) + } + + public func eventHandled() { + try! + rustCall { + ldk_lite_4a2d_LdkLite_event_handled(self.pointer, $0) + } + } + + public func myNodeId() throws -> PublicKey { + return try FfiConverterTypePublicKey.lift( + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_my_node_id(self.pointer, $0) + } + ) + } + + public func newFundingAddress() throws -> Address { + return try FfiConverterTypeAddress.lift( + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_new_funding_address(self.pointer, $0) + } + ) + } + + public func connectOpenChannel(nodePubkeyAndAddress: String, channelAmountSats: UInt64, announceChannel: Bool) throws { + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_connect_open_channel(self.pointer, + FfiConverterString.lower(nodePubkeyAndAddress), + FfiConverterUInt64.lower(channelAmountSats), + FfiConverterBool.lower(announceChannel), $0) + } + } + + public func sendPayment(invoice: Invoice) throws -> PaymentHash { + return try FfiConverterTypePaymentHash.lift( + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_send_payment(self.pointer, + FfiConverterTypeInvoice.lower(invoice), $0) + } + ) + } + + public func sendSpontaneousPayment(amountMsat: UInt64, nodeId: String) throws -> PaymentHash { + return try FfiConverterTypePaymentHash.lift( + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_send_spontaneous_payment(self.pointer, + FfiConverterUInt64.lower(amountMsat), + FfiConverterString.lower(nodeId), $0) + } + ) + } + + public func receivePayment(amountMsat: UInt64?, description: String, expirySecs: UInt32) throws -> Invoice { + return try FfiConverterTypeInvoice.lift( + try + rustCallWithError(FfiConverterTypeError.self) { + ldk_lite_4a2d_LdkLite_receive_payment(self.pointer, + FfiConverterOptionUInt64.lower(amountMsat), + FfiConverterString.lower(description), + FfiConverterUInt32.lower(expirySecs), $0) + } + ) + } +} + +private struct FfiConverterTypeLdkLite: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = LdkLite + + static func read(from buf: Reader) throws -> LdkLite { + let v: UInt64 = try buf.readInt() + // 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!) + } + + static func write(_ value: LdkLite, into buf: Writer) { + // 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`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + static func lift(_ pointer: UnsafeMutableRawPointer) throws -> LdkLite { + return LdkLite(unsafeFromRawPointer: pointer) + } + + static func lower(_ value: LdkLite) -> UnsafeMutableRawPointer { + return value.pointer + } +} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum Event { + case paymentSuccessful(paymentHash: PaymentHash) + case paymentFailed(paymentHash: PaymentHash) + case paymentReceived(paymentHash: PaymentHash, amountMsat: UInt64) + case channelClosed(channelId: ChannelId) +} + +private struct FfiConverterTypeEvent: FfiConverterRustBuffer { + typealias SwiftType = Event + + static func read(from buf: Reader) throws -> Event { + let variant: Int32 = try buf.readInt() + switch variant { + case 1: return .paymentSuccessful( + paymentHash: try FfiConverterTypePaymentHash.read(from: buf) + ) + + case 2: return .paymentFailed( + paymentHash: try FfiConverterTypePaymentHash.read(from: buf) + ) + + case 3: return .paymentReceived( + paymentHash: try FfiConverterTypePaymentHash.read(from: buf), + amountMsat: try FfiConverterUInt64.read(from: buf) + ) + + case 4: return .channelClosed( + channelId: try FfiConverterTypeChannelId.read(from: buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + static func write(_ value: Event, into buf: Writer) { + switch value { + case let .paymentSuccessful(paymentHash): + buf.writeInt(Int32(1)) + FfiConverterTypePaymentHash.write(paymentHash, into: buf) + + case let .paymentFailed(paymentHash): + buf.writeInt(Int32(2)) + FfiConverterTypePaymentHash.write(paymentHash, into: buf) + + case let .paymentReceived(paymentHash, amountMsat): + buf.writeInt(Int32(3)) + FfiConverterTypePaymentHash.write(paymentHash, into: buf) + FfiConverterUInt64.write(amountMsat, into: buf) + + case let .channelClosed(channelId): + buf.writeInt(Int32(4)) + FfiConverterTypeChannelId.write(channelId, into: buf) + } + } +} + +extension Event: Equatable, Hashable {} + +public enum Error { + // Simple error enums only carry a message + case AlreadyRunning(message: String) + + // Simple error enums only carry a message + case NotRunning(message: String) + + // Simple error enums only carry a message + case FundingTxCreationFailed(message: String) + + // Simple error enums only carry a message + case ConnectionFailed(message: String) + + // Simple error enums only carry a message + case AddressInvalid(message: String) + + // Simple error enums only carry a message + case PublicKeyInvalid(message: String) + + // Simple error enums only carry a message + case PaymentHashInvalid(message: String) + + // Simple error enums only carry a message + case NonUniquePaymentHash(message: String) + + // Simple error enums only carry a message + case InvoiceInvalid(message: String) + + // Simple error enums only carry a message + case InvoiceCreationFailed(message: String) + + // Simple error enums only carry a message + case ChannelIdInvalid(message: String) + + // Simple error enums only carry a message + case RoutingFailed(message: String) + + // Simple error enums only carry a message + case PeerInfoParseFailed(message: String) + + // Simple error enums only carry a message + case ChannelCreationFailed(message: String) + + // Simple error enums only carry a message + case ChannelClosingFailed(message: String) + + // Simple error enums only carry a message + case PersistenceFailed(message: String) + + // Simple error enums only carry a message + case WalletOperationFailed(message: String) + + // Simple error enums only carry a message + case WalletSigningFailed(message: String) + + // Simple error enums only carry a message + case ChainAccessFailed(message: String) +} + +private struct FfiConverterTypeError: FfiConverterRustBuffer { + typealias SwiftType = Error + + static func read(from buf: Reader) throws -> Error { + let variant: Int32 = try buf.readInt() + switch variant { + case 1: return .AlreadyRunning( + message: try FfiConverterString.read(from: buf) + ) + + case 2: return .NotRunning( + message: try FfiConverterString.read(from: buf) + ) + + case 3: return .FundingTxCreationFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 4: return .ConnectionFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 5: return .AddressInvalid( + message: try FfiConverterString.read(from: buf) + ) + + case 6: return .PublicKeyInvalid( + message: try FfiConverterString.read(from: buf) + ) + + case 7: return .PaymentHashInvalid( + message: try FfiConverterString.read(from: buf) + ) + + case 8: return .NonUniquePaymentHash( + message: try FfiConverterString.read(from: buf) + ) + + case 9: return .InvoiceInvalid( + message: try FfiConverterString.read(from: buf) + ) + + case 10: return .InvoiceCreationFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 11: return .ChannelIdInvalid( + message: try FfiConverterString.read(from: buf) + ) + + case 12: return .RoutingFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 13: return .PeerInfoParseFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 14: return .ChannelCreationFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 15: return .ChannelClosingFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 16: return .PersistenceFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 17: return .WalletOperationFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 18: return .WalletSigningFailed( + message: try FfiConverterString.read(from: buf) + ) + + case 19: return .ChainAccessFailed( + message: try FfiConverterString.read(from: buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + static func write(_ value: Error, into buf: Writer) { + switch value { + case let .AlreadyRunning(message): + buf.writeInt(Int32(1)) + FfiConverterString.write(message, into: buf) + case let .NotRunning(message): + buf.writeInt(Int32(2)) + FfiConverterString.write(message, into: buf) + case let .FundingTxCreationFailed(message): + buf.writeInt(Int32(3)) + FfiConverterString.write(message, into: buf) + case let .ConnectionFailed(message): + buf.writeInt(Int32(4)) + FfiConverterString.write(message, into: buf) + case let .AddressInvalid(message): + buf.writeInt(Int32(5)) + FfiConverterString.write(message, into: buf) + case let .PublicKeyInvalid(message): + buf.writeInt(Int32(6)) + FfiConverterString.write(message, into: buf) + case let .PaymentHashInvalid(message): + buf.writeInt(Int32(7)) + FfiConverterString.write(message, into: buf) + case let .NonUniquePaymentHash(message): + buf.writeInt(Int32(8)) + FfiConverterString.write(message, into: buf) + case let .InvoiceInvalid(message): + buf.writeInt(Int32(9)) + FfiConverterString.write(message, into: buf) + case let .InvoiceCreationFailed(message): + buf.writeInt(Int32(10)) + FfiConverterString.write(message, into: buf) + case let .ChannelIdInvalid(message): + buf.writeInt(Int32(11)) + FfiConverterString.write(message, into: buf) + case let .RoutingFailed(message): + buf.writeInt(Int32(12)) + FfiConverterString.write(message, into: buf) + case let .PeerInfoParseFailed(message): + buf.writeInt(Int32(13)) + FfiConverterString.write(message, into: buf) + case let .ChannelCreationFailed(message): + buf.writeInt(Int32(14)) + FfiConverterString.write(message, into: buf) + case let .ChannelClosingFailed(message): + buf.writeInt(Int32(15)) + FfiConverterString.write(message, into: buf) + case let .PersistenceFailed(message): + buf.writeInt(Int32(16)) + FfiConverterString.write(message, into: buf) + case let .WalletOperationFailed(message): + buf.writeInt(Int32(17)) + FfiConverterString.write(message, into: buf) + case let .WalletSigningFailed(message): + buf.writeInt(Int32(18)) + FfiConverterString.write(message, into: buf) + case let .ChainAccessFailed(message): + buf.writeInt(Int32(19)) + FfiConverterString.write(message, into: buf) + } + } +} + +extension Error: Equatable, Hashable {} + +extension Error: Error {} + +private struct FfiConverterOptionUInt64: FfiConverterRustBuffer { + typealias SwiftType = UInt64? + + static func write(_ value: SwiftType, into buf: Writer) { + guard let value = value else { + buf.writeInt(Int8(0)) + return + } + buf.writeInt(Int8(1)) + FfiConverterUInt64.write(value, into: buf) + } + + static func read(from buf: Reader) throws -> SwiftType { + switch try buf.readInt() as Int8 { + case 0: return nil + case 1: return try FfiConverterUInt64.read(from: buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Address = String +private typealias FfiConverterTypeAddress = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias ChannelId = String +private typealias FfiConverterTypeChannelId = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Invoice = String +private typealias FfiConverterTypeInvoice = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias PaymentHash = String +private typealias FfiConverterTypePaymentHash = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias PublicKey = String +private typealias FfiConverterTypePublicKey = FfiConverterString + +/** + * Top level initializers and tear down methods. + * + * This is generated by uniffi. + */ +public enum LdkLiteLifecycle { + /** + * Initialize the FFI and Rust library. This should be only called once per application. + */ + func initialize() {} +} diff --git a/uniffi/ldk_liteFFI.h b/uniffi/ldk_liteFFI.h new file mode 100644 index 000000000..9cbfd70ca --- /dev/null +++ b/uniffi/ldk_liteFFI.h @@ -0,0 +1,120 @@ +// 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 +#include + +// 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 +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, RustBuffer, RustBuffer *_Nonnull); + +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 + +void ffi_ldk_lite_4a2d_Builder_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void*_Nonnull ldk_lite_4a2d_Builder_new( + + RustCallStatus *_Nonnull out_status + ); +void*_Nonnull ldk_lite_4a2d_Builder_build( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void ffi_ldk_lite_4a2d_LdkLite_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void ldk_lite_4a2d_LdkLite_start( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void ldk_lite_4a2d_LdkLite_stop( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ldk_lite_4a2d_LdkLite_next_event( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void ldk_lite_4a2d_LdkLite_event_handled( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ldk_lite_4a2d_LdkLite_my_node_id( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ldk_lite_4a2d_LdkLite_new_funding_address( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void ldk_lite_4a2d_LdkLite_connect_open_channel( + void*_Nonnull ptr,RustBuffer node_pubkey_and_address,uint64_t channel_amount_sats,int8_t announce_channel, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ldk_lite_4a2d_LdkLite_send_payment( + void*_Nonnull ptr,RustBuffer invoice, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ldk_lite_4a2d_LdkLite_send_spontaneous_payment( + void*_Nonnull ptr,uint64_t amount_msat,RustBuffer node_id, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ldk_lite_4a2d_LdkLite_receive_payment( + void*_Nonnull ptr,RustBuffer amount_msat,RustBuffer description,uint32_t expiry_secs, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_ldk_lite_4a2d_rustbuffer_alloc( + int32_t size, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_ldk_lite_4a2d_rustbuffer_from_bytes( + ForeignBytes bytes, + RustCallStatus *_Nonnull out_status + ); +void ffi_ldk_lite_4a2d_rustbuffer_free( + RustBuffer buf, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_ldk_lite_4a2d_rustbuffer_reserve( + RustBuffer buf,int32_t additional, + RustCallStatus *_Nonnull out_status + ); diff --git a/uniffi/ldk_liteFFI.modulemap b/uniffi/ldk_liteFFI.modulemap new file mode 100644 index 000000000..42be9b631 --- /dev/null +++ b/uniffi/ldk_liteFFI.modulemap @@ -0,0 +1,6 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +module ldk_liteFFI { + header "ldk_liteFFI.h" + export * +} \ No newline at end of file diff --git a/uniffi/uniffi/ldk_lite/ldk_lite.kt b/uniffi/uniffi/ldk_lite/ldk_lite.kt new file mode 100644 index 000000000..64e93310f --- /dev/null +++ b/uniffi/uniffi/ldk_lite/ldk_lite.kt @@ -0,0 +1,1083 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package uniffi.ldk_lite + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the detils of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.ptr.ByReference +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + + @JvmField var len: Int = 0 + + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + class ByReference : RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_lite_4a2d_rustbuffer_alloc(size, status).also { + if (it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=$size)") + } + } + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_lite_4a2d_rustbuffer_free(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} + +// The FfiConverter interface handles converter types to and from the FFI +// +// All implementing objects should be public to support external types. When a +// type is external we need to import it's FfiConverter. +public interface FfiConverter { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): Int + + // Write a Kotlin type to a `ByteBuffer` + fun write(value: KotlinType, buf: ByteBuffer) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +// FfiConverter that uses `RustBuffer` as the FfiType +public interface FfiConverterRustBuffer : FfiConverter { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} + +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. +// Error runtime. +@Structure.FieldOrder("code", "error_buf") +internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus() + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(FfiConverterString.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object NullCallStatusErrorHandler : CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback) +} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "uniffi_ldk_lite" +} + +private inline fun loadIndirect( + componentName: String +): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "ldk_lite") + } + } + + fun ffi_ldk_lite_4a2d_Builder_object_free( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_lite_4a2d_Builder_new( + _uniffi_out_err: RustCallStatus + ): Pointer + + fun ldk_lite_4a2d_Builder_build( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Pointer + + fun ffi_ldk_lite_4a2d_LdkLite_object_free( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_lite_4a2d_LdkLite_start( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_lite_4a2d_LdkLite_stop( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_lite_4a2d_LdkLite_next_event( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_lite_4a2d_LdkLite_event_handled( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_lite_4a2d_LdkLite_my_node_id( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_lite_4a2d_LdkLite_new_funding_address( + `ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_lite_4a2d_LdkLite_connect_open_channel( + `ptr`: Pointer, + `nodePubkeyAndAddress`: RustBuffer.ByValue, + `channelAmountSats`: Long, + `announceChannel`: Byte, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_lite_4a2d_LdkLite_send_payment( + `ptr`: Pointer, + `invoice`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_lite_4a2d_LdkLite_send_spontaneous_payment( + `ptr`: Pointer, + `amountMsat`: Long, + `nodeId`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_lite_4a2d_LdkLite_receive_payment( + `ptr`: Pointer, + `amountMsat`: RustBuffer.ByValue, + `description`: RustBuffer.ByValue, + `expirySecs`: Int, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_lite_4a2d_rustbuffer_alloc( + `size`: Int, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_lite_4a2d_rustbuffer_from_bytes( + `bytes`: ForeignBytes.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_lite_4a2d_rustbuffer_free( + `buf`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_ldk_lite_4a2d_rustbuffer_reserve( + `buf`: RustBuffer.ByValue, + `additional`: Int, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue +} + +// Public interface members begin here. + +public object FfiConverterUInt : FfiConverter { + override fun lift(value: Int): UInt { + return value.toUInt() + } + + override fun read(buf: ByteBuffer): UInt { + return lift(buf.getInt()) + } + + override fun lower(value: UInt): Int { + return value.toInt() + } + + override fun allocationSize(value: UInt) = 4 + + override fun write(value: UInt, buf: ByteBuffer) { + buf.putInt(value.toInt()) + } +} + +public object FfiConverterULong : FfiConverter { + override fun lift(value: Long): ULong { + return value.toULong() + } + + override fun read(buf: ByteBuffer): ULong { + return lift(buf.getLong()) + } + + override fun lower(value: ULong): Long { + return value.toLong() + } + + override fun allocationSize(value: ULong) = 8 + + override fun write(value: ULong, buf: ByteBuffer) { + buf.putLong(value.toLong()) + } +} + +public object FfiConverterBoolean : FfiConverter { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1 + + override fun write(value: Boolean, buf: ByteBuffer) { + buf.put(lower(value)) + } +} + +public object FfiConverterString : FfiConverter { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteArr = value.toByteArray(Charsets.UTF_8) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteArr.size) + rbuf.asByteBuffer()!!.put(byteArr) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per unicode codepoint which will always be + // enough. + override fun allocationSize(value: String): Int { + val sizeForLength = 4 + val sizeForString = value.length * 3 + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: ByteBuffer) { + val byteArr = value.toByteArray(Charsets.UTF_8) + buf.putInt(byteArr.size) + buf.put(byteArr) + } +} + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +// The base class for all UniFFI Object types. +// +// This class provides core operations for working with the Rust `Arc` pointer to +// the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an `FFIObject` instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so will +// leak the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// In the future we may be able to replace some of this with automatic finalization logic, such as using +// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is +// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also +// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], +// so there would still be some complexity here). +// +// Sigh...all of this for want of a robust finalization mechanism. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// +abstract class FFIObject( + protected val pointer: Pointer +) : Disposable, AutoCloseable { + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + protected open fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (!this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement aways matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} + +public interface BuilderInterface { + + fun `build`(): LdkLite +} + +class Builder( + pointer: Pointer +) : FFIObject(pointer), BuilderInterface { + constructor() : + this( + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_Builder_new(_status) + } + ) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + protected override fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_lite_4a2d_Builder_object_free(this.pointer, status) + } + } + + override fun `build`(): LdkLite = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_Builder_build(it, _status) + } + }.let { + FfiConverterTypeLdkLite.lift(it) + } +} + +public object FfiConverterTypeBuilder : FfiConverter { + override fun lower(value: Builder): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): Builder { + return Builder(value) + } + + override fun read(buf: ByteBuffer): Builder { + // 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: Builder) = 8 + + override fun write(value: Builder, 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))) + } +} + +public interface LdkLiteInterface { + + @Throws(Exception::class) + fun `start`() + + @Throws(Exception::class) + fun `stop`() + + fun `nextEvent`(): Event + + fun `eventHandled`() + + @Throws(Exception::class) + fun `myNodeId`(): PublicKey + + @Throws(Exception::class) + fun `newFundingAddress`(): Address + + @Throws(Exception::class) + fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) + + @Throws(Exception::class) + fun `sendPayment`(`invoice`: Invoice): PaymentHash + + @Throws(Exception::class) + fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash + + @Throws(Exception::class) + fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice +} + +class LdkLite( + pointer: Pointer +) : FFIObject(pointer), LdkLiteInterface { + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + protected override fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_lite_4a2d_LdkLite_object_free(this.pointer, status) + } + } + + @Throws(Exception::class) + override fun `start`() = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_start(it, _status) + } + } + + @Throws(Exception::class) + override fun `stop`() = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_stop(it, _status) + } + } + + override fun `nextEvent`(): Event = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_next_event(it, _status) + } + }.let { + FfiConverterTypeEvent.lift(it) + } + override fun `eventHandled`() = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_event_handled(it, _status) + } + } + + @Throws(Exception::class) + override fun `myNodeId`(): PublicKey = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_my_node_id(it, _status) + } + }.let { + FfiConverterTypePublicKey.lift(it) + } + + @Throws(Exception::class) + override fun `newFundingAddress`(): Address = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_new_funding_address(it, _status) + } + }.let { + FfiConverterTypeAddress.lift(it) + } + + @Throws(Exception::class) + override fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_connect_open_channel(it, FfiConverterString.lower(`nodePubkeyAndAddress`), FfiConverterULong.lower(`channelAmountSats`), FfiConverterBoolean.lower(`announceChannel`), _status) + } + } + + @Throws(Exception::class) + override fun `sendPayment`(`invoice`: Invoice): PaymentHash = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_send_payment(it, FfiConverterTypeInvoice.lower(`invoice`), _status) + } + }.let { + FfiConverterTypePaymentHash.lift(it) + } + + @Throws(Exception::class) + override fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_send_spontaneous_payment(it, FfiConverterULong.lower(`amountMsat`), FfiConverterString.lower(`nodeId`), _status) + } + }.let { + FfiConverterTypePaymentHash.lift(it) + } + + @Throws(Exception::class) + override fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice = + callWithPointer { + rustCallWithError(Exception) { _status -> + _UniFFILib.INSTANCE.ldk_lite_4a2d_LdkLite_receive_payment(it, FfiConverterOptionalULong.lower(`amountMsat`), FfiConverterString.lower(`description`), FfiConverterUInt.lower(`expirySecs`), _status) + } + }.let { + FfiConverterTypeInvoice.lift(it) + } +} + +public object FfiConverterTypeLdkLite : FfiConverter { + override fun lower(value: LdkLite): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): LdkLite { + return LdkLite(value) + } + + override fun read(buf: ByteBuffer): LdkLite { + // 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: LdkLite) = 8 + + override fun write(value: LdkLite, 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))) + } +} + +sealed class Event { + data class PaymentSuccessful( + val `paymentHash`: PaymentHash + ) : Event() + data class PaymentFailed( + val `paymentHash`: PaymentHash + ) : Event() + data class PaymentReceived( + val `paymentHash`: PaymentHash, + val `amountMsat`: ULong + ) : Event() + data class ChannelClosed( + val `channelId`: ChannelId + ) : Event() +} + +public object FfiConverterTypeEvent : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Event { + return when (buf.getInt()) { + 1 -> Event.PaymentSuccessful( + FfiConverterTypePaymentHash.read(buf) + ) + 2 -> Event.PaymentFailed( + FfiConverterTypePaymentHash.read(buf) + ) + 3 -> Event.PaymentReceived( + FfiConverterTypePaymentHash.read(buf), + FfiConverterULong.read(buf) + ) + 4 -> Event.ChannelClosed( + FfiConverterTypeChannelId.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: Event) = when (value) { + is Event.PaymentSuccessful -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`) + ) + } + is Event.PaymentFailed -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`) + ) + } + is Event.PaymentReceived -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`) + + FfiConverterULong.allocationSize(value.`amountMsat`) + ) + } + is Event.ChannelClosed -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + + FfiConverterTypeChannelId.allocationSize(value.`channelId`) + ) + } + } + + override fun write(value: Event, buf: ByteBuffer) { + when (value) { + is Event.PaymentSuccessful -> { + buf.putInt(1) + FfiConverterTypePaymentHash.write(value.`paymentHash`, buf) + Unit + } + is Event.PaymentFailed -> { + buf.putInt(2) + FfiConverterTypePaymentHash.write(value.`paymentHash`, buf) + Unit + } + is Event.PaymentReceived -> { + buf.putInt(3) + FfiConverterTypePaymentHash.write(value.`paymentHash`, buf) + FfiConverterULong.write(value.`amountMsat`, buf) + Unit + } + is Event.ChannelClosed -> { + buf.putInt(4) + FfiConverterTypeChannelId.write(value.`channelId`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +sealed class Exception(message: String) : Exception(message) { + // Each variant is a nested class + // Flat enums carries a string error message, so no special implementation is necessary. + class AlreadyRunning(message: String) : Exception(message) + class NotRunning(message: String) : Exception(message) + class FundingTxCreationFailed(message: String) : Exception(message) + class ConnectionFailed(message: String) : Exception(message) + class AddressInvalid(message: String) : Exception(message) + class PublicKeyInvalid(message: String) : Exception(message) + class PaymentHashInvalid(message: String) : Exception(message) + class NonUniquePaymentHash(message: String) : Exception(message) + class InvoiceInvalid(message: String) : Exception(message) + class InvoiceCreationFailed(message: String) : Exception(message) + class ChannelIdInvalid(message: String) : Exception(message) + class RoutingFailed(message: String) : Exception(message) + class PeerInfoParseFailed(message: String) : Exception(message) + class ChannelCreationFailed(message: String) : Exception(message) + class ChannelClosingFailed(message: String) : Exception(message) + class PersistenceFailed(message: String) : Exception(message) + class WalletOperationFailed(message: String) : Exception(message) + class WalletSigningFailed(message: String) : Exception(message) + class ChainAccessFailed(message: String) : Exception(message) + + companion object ErrorHandler : CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): Exception = FfiConverterTypeError.lift(error_buf) + } +} + +public object FfiConverterTypeError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): Exception { + return when (buf.getInt()) { + 1 -> Exception.AlreadyRunning(FfiConverterString.read(buf)) + 2 -> Exception.NotRunning(FfiConverterString.read(buf)) + 3 -> Exception.FundingTxCreationFailed(FfiConverterString.read(buf)) + 4 -> Exception.ConnectionFailed(FfiConverterString.read(buf)) + 5 -> Exception.AddressInvalid(FfiConverterString.read(buf)) + 6 -> Exception.PublicKeyInvalid(FfiConverterString.read(buf)) + 7 -> Exception.PaymentHashInvalid(FfiConverterString.read(buf)) + 8 -> Exception.NonUniquePaymentHash(FfiConverterString.read(buf)) + 9 -> Exception.InvoiceInvalid(FfiConverterString.read(buf)) + 10 -> Exception.InvoiceCreationFailed(FfiConverterString.read(buf)) + 11 -> Exception.ChannelIdInvalid(FfiConverterString.read(buf)) + 12 -> Exception.RoutingFailed(FfiConverterString.read(buf)) + 13 -> Exception.PeerInfoParseFailed(FfiConverterString.read(buf)) + 14 -> Exception.ChannelCreationFailed(FfiConverterString.read(buf)) + 15 -> Exception.ChannelClosingFailed(FfiConverterString.read(buf)) + 16 -> Exception.PersistenceFailed(FfiConverterString.read(buf)) + 17 -> Exception.WalletOperationFailed(FfiConverterString.read(buf)) + 18 -> Exception.WalletSigningFailed(FfiConverterString.read(buf)) + 19 -> Exception.ChainAccessFailed(FfiConverterString.read(buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + + @Suppress("UNUSED_PARAMETER") + override fun allocationSize(value: Exception): Int { + throw RuntimeException("Writing Errors is not supported") + } + + @Suppress("UNUSED_PARAMETER") + override fun write(value: Exception, buf: ByteBuffer) { + throw RuntimeException("Writing Errors is not supported") + } +} + +public object FfiConverterOptionalULong : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): ULong? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterULong.read(buf) + } + + override fun allocationSize(value: ULong?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterULong.allocationSize(value) + } + } + + override fun write(value: ULong?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterULong.write(value, buf) + } + } +} + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias Address = String +public typealias FfiConverterTypeAddress = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias ChannelId = String +public typealias FfiConverterTypeChannelId = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias Invoice = String +public typealias FfiConverterTypeInvoice = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias PaymentHash = String +public typealias FfiConverterTypePaymentHash = FfiConverterString + +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias PublicKey = String +public typealias FfiConverterTypePublicKey = FfiConverterString